17

When you use cc(1) to link a program, it will invoke a default linker command. For instance, your version of the compiler may have been built to use /usr/bin/ld by default on a unix-like platform.

Is there a way to specify a different linker command that cc(1) (or c++(1)) should use (e.g., /usr/local/bin/ld instead of /usr/bin/ld)? I'm interested mostly in gcc and clang.

I'm not looking for methods that involve running the various compilation steps separately (e.g., pre-process, compile, assemble, link).

For example, I was hoping something like this might do the job:

env LD=/usr/local/bin/ld cc foo.c -o foo

But that doesn't work for gcc or clang. It would work, of course, if you had a makefile that built an object file first, then invoked ${LD} to link (e.g., env LD=/usr/local/bin/ld make)

Update (with one possible motivation): To easily test with a different linker than the default linker. For example, it would be nice to be able to do this:

cc --linker=/usr/local/bin/ld foo.c -o foo

Instead, you have to do generate the object file, run cc -v to figure out the arguments to ld, manually run the ld you want with those arguments:

cc -c foo.c
cc -v foo.c -o /dev/null

Now look at the linker invocation and manually copy/paste replacing linker and temporary object file. Something like this (example taken from a test on fedora 23) where you replace /usr/libexec/gcc/x86_64-redhat-linux/5.3.1/collect2 with /usr/local/bin/ld (although it's not exactly the same as collect2):

/usr/local/bin/ld -plugin /usr/libexec/gcc/x86_64-redhat-linux/5.3.1/liblto_plugin.so -plugin-opt=/usr/libexec/gcc/x86_64-redhat-linux/5.3.1/lto-wrapper -plugin-opt=-fresolution=/tmp/jhein/ccM2XKIg.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o c /usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64/crt1.o /usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/5.3.1/crtbegin.o -L/usr/lib/gcc/x86_64-redhat-linux/5.3.1 -L/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../.. c.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/5.3.1/crtend.o /usr/lib/gcc/x86_64-redhat-linux/5.3.1/../../../../lib64/crtn.o

As you can see, that's not easy. Note there is documentation in the gcc info page about how collect2 looks for a linker program. But according to those docs, the first place it looks is not an environment variable or something you can specify on the command line (e.g., --linker). The docs say it looks first for a "a hard coded linker file name". If that documentation is correct, to coerce it not to use that linker (i.e., trick it), you would have to rename the default linker (e.g., sudo mv /usr/bin/ld /usr/bin/ld.tmp-disable).

Update 2: Using -B seems to work well enough for my needs. See below where I posted an answer. I can't accept my own answer, but I would if I could - it seems to solve the issue well.

Juan
  • 1,204
  • 1
  • 11
  • 25
  • 1
    `cc` is just an alias for `gcc`, `clang`, or whatever default C compiler your system uses. – Eli Sadoff Nov 07 '16 at 17:23
  • Yes, I agree. That's usually the case, indeed. So do you have an answer to the question for gcc or clang? – Juan Nov 07 '16 at 17:28
  • You can try the `-c` flag in `gcc`. – Eli Sadoff Nov 07 '16 at 17:28
  • @Eli, using -c will give me an object file. I want to use `cc` to build a fully linked executable binary (and specify a different linker command than the default). – Juan Nov 07 '16 at 17:32
  • `cc` is the **C** **c**ompiler. A compiler is not a linker. If you want to use a specific linker, just split the phases and invoke `ld` from your build script. – too honest for this site Nov 07 '16 at 17:46
  • **Why do you ask?** Smells badly as an [XY problem](http://xyproblem.info/) so motivating your question by editing it would be beneficial – Basile Starynkevitch Nov 07 '16 at 17:57
  • 1
    @BasileStarynkevitch: I ask because I want to test a different linker than the one installed in /usr/bin. No XY problem here. Just testing against a different version of the linker. – Juan Nov 07 '16 at 18:12
  • 1
    @Olaf: Yes, I know what cc is. I specifically mentioned that I am already aware of methods to split up compiler/linker steps. This question is specifically about how to tell cc (which can execute all build stages with a single invocation of cc) how to use a different linker than the one that is specified by default when cc is built. Maybe you weren't aware that cc can invoke the linker (try building a simple C program with `cc -v helloworld.c`). – Juan Nov 07 '16 at 18:19
  • 1
    Looks like an XY problem. **Why** do you want to use a different than the default linker? For normal compilation, that should be what you want, for cross-compilation, you should use the appropriate toolchain which also includes the correct linker. – too honest for this site Nov 07 '16 at 20:19
  • @Olaf: Re: Why... already answered in these comments. – Juan Nov 07 '16 at 20:32
  • 1
    Comments are not part of the question. You should edit the question, see [ask] and read the FAQ. But yes, I oversaw that Basil already suspected the same. Still, I don't see what your problem is. `cc` is meant for default compilation/build. For any larger program you should use a build-tool anyway and they always allow to specify compiler and linker seperately. – too honest for this site Nov 07 '16 at 20:35
  • @Olaf: I added the motive. You're trying to read too much into this question. I truly want to know if there is a way to specify a non-default linker to `cc`. Maybe the answer is that there is no way at this time with current compiler front ends such as gcc & clang. As described in the OP, I am aware of available *alternative* methods to build a binary with a different linker, but this query is specifically focused on trying to learn whether there is a way to specify the linker directly when `cc` is called. I'm not trying to solve any other problem than that specific issue. – Juan Nov 07 '16 at 20:51

4 Answers4

16

The -B option allows you to specify an alternate search path for executables, libraries, include files & data files that the compiler will use. This works for some versions of gcc [1] and for clang (currently undocumented - in man pages of at least clang 3.7 & 3.8):

cc -B/usr/local/bin foo.c -o foo

Note that this will cause cc to search for other tools (e.g., the assembler) in the path specified by -B. So supposing you have a different version of binutils installed in /usr/local/bin, if you only want to use that linker (rather than /usr/local/bin/as, et. al.), you could do something like this:

mkdir /tmp/usemyld
ln -s /usr/local/bin/ld /tmp/usemyld
cc -B/tmp/usemyld foo.c -o foo

-B has its own set of rules that allow you to override different files that the gcc compiler tries to use (programs, libraries, include files, data files). This is documented as far back as at least gcc 2.95 - read the gcc man / info pages. I don't know how compatible the behavior of -B is for clang. As mentioned, it's not currently documented in the clang man page. But it worked well enough to allow me to select an alternate linker as shown above.

gcc also supports calling a script/program as specified by -wrapper. clang does not (currently). You could also use that and point at a wrapper script that alters what program the compiler is calling. I don't know if collect2 heeds the -wrapper option (and for gcc, collect2 is what calls the linker when compiling c/c++ programs at least).

[1] The linker search order documented in the gcc info page for collect2 says that it will search first for "a hard coded linker file name if GCC was configured with the '--with-ld' option"). So if your gcc was not configured with '--with-ld', then it will eventually search in the path specified by -B (if it doesn't find real-ld first). If your gcc was configured with --with-ld, then the -B option will not help you specify an alternate linker of your choosing.

Juan
  • 1,204
  • 1
  • 11
  • 25
  • 1
    used `gcc -v -B/usr/bin …` and still got a call to `/opt/local/bin/ld -syslibroot …` — so this didn't work either. – Martin Nov 04 '20 at 10:02
  • @Martin - Which gcc version? Is there a valid 'ld' in /usr/bin? Consider using a tracing tool (ktrace, strace) to see if gcc tries /usr/bin first. Was your gcc built --with-ld (see the footnote)? – Juan Jan 07 '21 at 17:13
15

Certain linkers are easy to use - just gcc -fuse-ld=lld main.c. This appears to have been added somewhere in gcc version 4. -fuse-ld also works with clang 10.0.1.

Supported linkers are listed on https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html

-fuse-ld=bfd

Use the bfd linker instead of the default linker.

-fuse-ld=gold

Use the gold linker instead of the default linker.

-fuse-ld=lld

Use the LLVM lld linker instead of the default linker.

Hopefully this helps people coming from searches, as OP long ago had their question answered.

asky
  • 1,520
  • 12
  • 20
  • This is a good find. gcc does not allow you to specify a path to a linker, however: `gcc9 -fuse-ld=/opt/ld.bfd x.c -o /dev/null gcc9: error: unrecognized command line option '-fuse-ld=/opt/ld.bfd' `. Sorry that should be split into two lines - I don't know how to do that in a comment. clang *does* allow a full path specification for `-fuse-ld`, however. That's nice. Note that `-fuse-ld` does not even show up with `clang --help`. I have not found where it is documented for llvm/clang yet. (as of llvm90). – Juan Feb 28 '20 at 16:33
  • That didn't work: `gcc: error: unrecognized command line option '-fuse-ld=/usr/bin/ld'` — continue looking. – Martin Nov 04 '20 at 09:58
  • 1
    As I mentioned in my answer, this only works in a few special cases. If you want to use bfd, you must `-fuse-ld=bfd`, and cannot use the whole path to the bfd linker. – asky Nov 05 '20 at 05:39
  • 1
    @Martin - As asky indicated, the argument to some of the current implementations of -fuse-ld is special. As mentioned in my earlier comment above, for gcc's implementation (e.g., gcc 9), it doesn't take an arbitrary path, but rather it takes the extension after ld (e.g. -fuse-ld=bfd in the example in my comment above). As mentioned, *a path works for clang*, though. The answer could be tweaked to make that a little more clear. – Juan Jan 07 '21 at 17:24
2

GCC uses internally spec files (to decide how the gcc program behaves, in particular how it is linking and with what linker). You can configure or change that with -specs= so have your own spec file and use it. Or use the -T option (of gcc which explicitly passes it to ld) to give a linker script.

The default spec is obtained with gcc -dumpspecs

Also, by strace(1)-ing some gcc command, you'll find out that it tries to access e.g. /usr/lib/gcc/x86_64-linux-gnu/specs; so put your own spec file there.

Those spec files are textual, so you should be able to write your own.

But I am not sure it is a good idea.

BTW, /usr/bin/cc is on Linux distribution a symlink (on Debian: /usr/bin/cc -> /etc/alternatives/cc -> /usr/bin/gcc), either to some gcc or to some clang. AFAIK, cc (and c99) is specified in POSIX (but of course, nothing is told about its relation to ld)

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 1
    The `-T` option is for `ld(1)`, not for `cc(1)`. I'll look at `-specs`. Looks like it is gcc-specific. And I agree it doesn't *feel* like the right solution. – Juan Nov 07 '16 at 18:07
  • The `-T` option is for `gcc` – Basile Starynkevitch Nov 07 '16 at 21:29
  • `-T` is for `ld(1)`. Similar to passing other options to the linker (such as `--startgroup`, for instance), to pass `-T' on to the linker using `gcc` (or `clang`), you can use `-Wl,-T -Wl,your_link_script_file.ld`. As it turns out, as a shortcut (similar to `-L`), you can pass -T to `gcc` or `clang`, but it's not documented anywhere that I see (man pages, info pages, --help). So, yes, you are right. Sorry - I made the mistake of looking in the docs for it. But, the linker script is read by ld, not cc - too late to point to a different command even if there were a way to specify it. – Juan Nov 07 '16 at 23:56
  • No, `-T` is *documented* as a `gcc` option (follow the link). Of course it is passed to `ld`, but `gcc` knows about it – Basile Starynkevitch Nov 08 '16 at 05:10
  • Yes, I see it in the online documentation now. It's still just passed directly to `/usr/bin/ld` (rather than parsed by `cc`) or whatever default linker was specified when `cc` was built. So it doesn't help solve the original problem. – Juan Nov 08 '16 at 13:25
  • p.s. and it was added to the man page / info page as well at some point (not on the system I originally looked at - which had gcc4.2 as it turns out - although `-T` was a valid option as far back as gcc 2.95 at least). – Juan Nov 08 '16 at 13:28
0

Put the directory which contains the alternative linker ld to the front of PATH environment variable.

For example, if an alternative ld is in ~/local/bin. export PATH=~/local/bin:$PATH will cause cc to use ~/local/bin/ld.

Jingguo Yao
  • 7,320
  • 6
  • 50
  • 63
  • Changing the PATH around is not always a very convenient. I'm here to look for a different solution. – Martin Nov 04 '20 at 09:54