How to set -dynamic-linker from the outside?

Our project is compiled using various systems (like Yocto, home-grown a’la Maven, etc.) for different platforms, including both native (for example run tests) and target machines (cross-compilation).

One of the challenges we are facing is setting --rpath and -dynamic-linker properties. So far we have been doing it like

add_link_options(
    LINKER:--rpath,<rpath-path>
    LINKER:-dynamic-linker,<dynamic-linker-path>
)

where the exact values depended on what is building us and for what platform. Resulting in a rather messy CMake code that dealt mostly with the environment rather than the project itself.

I would like to change that and force specific settings outside of CMake to keep project CMake clean and embed platform and system-specific settings in “recipes” for that configuration. (And with Yocto for example it means no-op since it comes configured correctly.)

It seems the --rpath part can be addressed by providing CMAKE_BUILD_RPATH upon CMake configuration. Hence, the LINKER:--rpath part disappears. Yay!

[Side note] It seems with CMAKE_BUILD_RPATH also --rpath-link is set. As of now, I’m not sure about the consequences, but it seems to work fine.

However, what about the -dynamic-linker part? What to provide to CMake during the configuration stage to enforce specific -dynamic-linker? Since by itself it seems to be detecting one from the machine I happen to use for building, rather than the one I expect it to use.

Of course, I could use a custom CMake configuration variable to pass the value. But I guess there must be some standard variables or paths checked when looking for this value and it would be better to change the accordingly instead of “cluttering” my CMake code with custom variables.

I don’t think CMake provides an abstraction for -dynamic-linker. What does it normally do?

Well, I expect it doesn’t have anything direct. I think I would have found it already if it had…

But it does find it somehow since I see that without providing it explicitly with add_link_options a different one (local to the build host machine) is used. So, where CMake looks for it? Some sysroot? Prefix? What else?

AFAIK the compiler/linker does it by itself (CMake does not mess with that by itself). One has to use -dynamic-linker only if one wants to override what the toolchains’ default is. Even in cross-compilation scenario, where you don’t necessarily have the dynamic linker for the target, your toolchain writes something as the dynamic linker into ELF headers.

I have built a project and then grep-ed over generated files. The dynamic-linker shows in two files:


CMakeFiles/CMakeOutput.log (one file, many occurrences)

This one shows the same regardless of whether I have the add_link_options or not.

I’m not sure what this file really contains or how to interpret this. But I can see cases like:

COLLECT_GCC_OPTIONS=(...)
 (...) -dynamic-linker /lib64/ld-linux-x86-64.so.2 (...)
COLLECT_GCC_OPTIONS=(...)
Parsed C implicit link information from the above output:
(...)
  link line: [ (...) -dynamic-linker /lib64/ld-linux-x86-64.so.2 (...)]
    (...)
    arg [-dynamic-linker] ==> ignore
    arg [/lib64/ld-linux-x86-64.so.2] ==> ignore
    (...)

Then another iteration of “the same” for CXX.


(...)/link.txt (many files, single occurrence)

This one shows only if I have the add_link_options. Otherwise, the option is missing.

All cases here seem to be like:

/usr/bin/nice (...) -Wl,-dynamic-linker,<path> (...)

where the <path> is the one I provided rather than the /lib64/ld-linux-x86-64.so.2.


Without the add_link_options the resulting binaries do not load.

It seems like if I could impact the “implicit link information” from CMakeOutput.log so that it would pick the right linker and not ignore it I would have it working. But how to?

Those are informations provided by your toolchain. Quite possibly extracted from the output of gcc -dumpspecs or something like that.

I think your toolchain assumes something about the target that simply is not true (the dynamic loader lacation). To influence that you’d have to use specs files or re-compile your toolchain with some special parameters (both of which is annoying and can be quite difficult to get right).

I’d expect this is the kind of thing that should be somehow possible to set globally in a cmake toolchain file.

By trial and error, I found out that my current (yeah, it should be -rpath rather than --rpath…):

add_link_options(
    LINKER:-rpath,<rpath-path>
    LINKER:-dynamic-linker,<dynamic-linker-path>
)

could be replaced in a few ways revolving around various variables.


As of now, I’m going to use:

LDFLAGS="${LDFLAGS} -Wl,-dynamic-linker,<dynamic-linker-path>"
CMAKE_BUILD_RPATH=<rpath-path>

LDFLAGS is an environment variable while CMAKE_BUILD_RPATH is provided as -D<var>=<value> command-line argument do cmake configuration call.

However, this particular choice is dictated mostly by the setup we have right now. In a different setup perhaps a different approach would be better.


For completeness, I will list here variables that seem to be related to the topic:

TBH, I’m somewhat lost in all those options… However, I will share some thoughts on them I have after spending significant time on this topic:

  1. I don’t like setting environment variable LDFLAGS (because I don’t like dependency on environment variables in general).
    – However, it does save me from setting three variables: CMAKE_EXE_LINKER_FLAGS_INIT, CMAKE_MODULE_LINKER_FLAGS_INIT, and CMAKE_SHARED_LINKER_FLAGS_INIT.
    – If using a CMAKE_TOOLCHAIN_FILE, as already suggested by @fenrir, maybe I would opt for separate variables, but having to pass them all via -D<var>=<value> seems a bit too verbose.
    – We use LDFLAGS already anyway for other flags, I’m not sure how it would behave if I would separately set the CMAKE_<TYPE>_LINKER_FLAGS_INIT variables but I expect it would overwrite everything that would be taken from LDFLAGS which is not desired.
  2. I don’t know if it is better to set rpath by linker flags or by CMake variables.
    – I assume CMake variables are better since it will lead to a cleaner situation from CMake’s point of view and avoid conflicts.
    – And also they are more fine-grained. It is easier to have a single variable with single path of clear purpose rather than append to a cumulative options variable.
  3. The add_link_options has a nice LINKER: mode that covers specifics of the compiler (for example, -Wl for GCC and -Xlinker for Clang).
    – When setting the variables manually within CMake you can still use CMAKE_LANG_LINKER_WRAPPER_FLAG and CMAKE_LANG_LINKER_WRAPPER_FLAG_SEP. Probably non-trivial and verbose to use it correctly, but at least possible.
    – However, when setting them from the outside (which is the whole point of setting them in the first place) you now must cover the compiler specifics on your own and the same settings will not work with both GCC and Clang.

It sounds like you should be using a toolchain file, which by definition will be specific to one toolchain (so one file for gcc, another file for clang, etc.). In that toolchain file, you can set CMAKE_EXE_LINKER_FLAGS_INIT, CMAKE_SHARED_LINKER_FLAGS_INIT and CMAKE_MODULE_LINKER_FLAGS_INIT to hold whatever custom linker flags you want to enforce. In your case, that would be to add -Wl,-dynamic-linker,<somePath> or similar. I’d recommend using CMAKE_BUILD_RPATH if you need to enforce a particular RPATH/RUNPATH for binaries in the build directory. That seems a little odd that you’d need to do that though. Normally CMake works out what the build directory RPATH needs to be for each executable, but I don’t know your specific situation.

1 Like