How to link C++ object files with ld
Asked Answered
D

2

48

I'm trying to link the output of C++ using ld and not g++. I'm only doing this to learn how to do it, not for practical purposes, so please don't suggest just to do it with g++.

Looking at this question, the person gets the same error when they run the ld command:

$ ld test.o -o test.out
ld: warning: cannot find entry symbol _start; defaulting to 00000000004000e8
test.o: In function `main':
test.cpp:(.text+0x1c): undefined reference to `strcasecmp'
test.cpp:(.text+0x23): undefined reference to `std::cout'
test.cpp:(.text+0x28): undefined reference to `std::ostream::operator<<(int)'
test.cpp:(.text+0x2d): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'
test.cpp:(.text+0x35): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
test.o: In function `__static_initialization_and_destruction_0(int, int)':
test.cpp:(.text+0x75): undefined reference to `std::ios_base::Init::Init()'
test.cpp:(.text+0x7a): undefined reference to `__dso_handle'
test.cpp:(.text+0x84): undefined reference to `std::ios_base::Init::~Init()'
test.cpp:(.text+0x89): undefined reference to `__cxa_atexit'
ld: test.out: hidden symbol `__dso_handle' isn't defined
ld: final link failed: Bad value

The answers in the linked post suggest that adding the C++ library as a linker argument will fix the problem, so I tried

ld test.o -o test.out -llibstd++

which is what they suggested, and I also tried a lot of other library names like libstdc++ or stdc++. But I'll always get an error that looks like

ld: cannot find -llibstd++

What am I doing wrong and how can I link my object files using ld?

Defray answered 4/1, 2013 at 19:1 Comment(2)
-lstdc++ is correct, but you may also need to use -L dir to tell the linker where to find that library.Henriquez
I'm just going to give another huge shoutout to @gsingh2011 for asking how to do this. It is not at all a naive or stupid request. Even if you would never do this in real life, knowing that you can do it is a crucial exercise (I believe) in understanding how the toolchain works.Lyford
S
56

If you run g++ with the -v flag, you'll see the link line it uses. Here's a simple example program:

#include <iostream>

int main(void)
{
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

And the output from running g++ -v -o example example.cpp:

Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5.1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 
COLLECT_GCC_OPTIONS='-v' '-o' 'example' '-shared-libgcc' '-mtune=generic'
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/cc1plus -quiet -v -D_GNU_SOURCE example.cpp -D_FORTIFY_SOURCE=2 -quiet -dumpbase example.cpp -mtune=generic -auxbase example -version -fstack-protector -o /tmp/ccV8qjvd.s
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../x86_64-linux-gnu/include"
ignoring nonexistent directory "/usr/include/x86_64-linux-gnu"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/4.4
 /usr/include/c++/4.4/x86_64-linux-gnu
 /usr/include/c++/4.4/backward
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/include
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/include-fixed
 /usr/include
End of search list.
GNU C++ (Ubuntu/Linaro 4.4.4-14ubuntu5.1) version 4.4.5 (x86_64-linux-gnu)
    compiled by GNU C version 4.4.5, GMP version 4.3.2, MPFR version 3.0.0-p3.
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: d92fbc2d715a3b7e0f4133f0c40053e4
COLLECT_GCC_OPTIONS='-v' '-o' 'example' '-shared-libgcc' '-mtune=generic'
 as -V -Qy -o /tmp/ccGHR0pc.o /tmp/ccV8qjvd.s
GNU assembler version 2.20.51 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.20.51-system.20100908
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../:/lib/:/usr/lib/:/usr/lib/x86_64-linux-gnu/
COLLECT_GCC_OPTIONS='-v' '-o' 'example' '-shared-libgcc' '-mtune=generic'
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/collect2 --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o example -z relro /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../.. -L/usr/lib/x86_64-linux-gnu /tmp/ccGHR0pc.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crtn.o

Wow, what a mess. Conveniently the link line is the last one there, so you can see what's happening pretty easily.

As you noticed in your comment below, the front-end is using collect2 rather than ld. Luckily, collect2 is just an alias for ld. Here's an example using it:

First let's generate an object file:

$ ls
example.cpp
$ c++ -c example.cpp
$ ls
example.cpp example.o

Then we'll use the front-end to link it to see the link line:

$ c++ -v -o example example.o
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.4.4-14ubuntu5.1' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.4 --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/:/lib/../lib/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../:/lib/:/usr/lib/:/usr/lib/x86_64-linux-gnu/
COLLECT_GCC_OPTIONS='-v' '-o' 'example' '-shared-libgcc' '-mtune=generic'
 /usr/lib/gcc/x86_64-linux-gnu/4.4.5/collect2 --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o example -z relro /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5 -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib -L/lib/../lib -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../.. -L/usr/lib/x86_64-linux-gnu example.o -lstdc++ -lm -lgcc_s -lgcc -lc -lgcc_s -lgcc /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crtn.o

Then throw away the binary, and link ourselves (normally, I would have just copy/pasted the line, but to make it easier to read I did it the multiline way with \s):

$ ls
example  example.cpp  example.o
$ rm example
$ ls
example.cpp example.o
$ ld                                                              \
> --build-id                                                      \
> --eh-frame-hdr                                                  \
> -m elf_x86_64                                                   \
> --hash-style=gnu                                                \
> -dynamic-linker                                                 \
> /lib64/ld-linux-x86-64.so.2                                     \
> -o example                                                      \
> -z relro                                                        \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o      \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crti.o      \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o                  \
> -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5                           \
> -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5                           \
> -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib           \
> -L/lib/../lib                                                   \
> -L/usr/lib/../lib                                               \
> -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../..                  \
> -L/usr/lib/x86_64-linux-gnu                                     \
> example.o                                                       \
> -lstdc++                                                        \
> -lm                                                             \
> -lgcc_s                                                         \
> -lgcc                                                           \
> -lc                                                             \
> -lgcc_s                                                         \
> -lgcc                                                           \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o                    \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crtn.o

Finally, run it!

$ ls
example example.cpp example.o
$ ./example 
Hello, world!

You can probably significantly shorten that link line by removing some arguments. Here's the minimal set I came up with after some experimentation:

$ ld                                                              \
> -dynamic-linker                                                 \
> /lib64/ld-linux-x86-64.so.2                                     \
> -o example                                                      \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crt1.o      \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crti.o      \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtbegin.o                  \
> example.o                                                       \
> -L/usr/lib/gcc/x86_64-linux-gnu/4.4.5                           \
> -lstdc++                                                        \
> -lc                                                             \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/crtend.o                    \
> /usr/lib/gcc/x86_64-linux-gnu/4.4.5/../../../../lib/crtn.o

This set of flags and libraries will of course depend on what library functions and language features your program uses.

Shadbush answered 4/1, 2013 at 19:3 Comment(7)
Can't I use ld instead of collect2? What's the difference?Defray
@gsingh2011 - collect2 is ld. Check out my added example.Shadbush
@gsingh2011 You can use ld instead of g++, but you shouldn't do that without a very good reason. The command that g++ uses can change from one release of g++ to the next, and if you don't update your ld command to match, you may be in for many nasty surprises.Selectivity
@CarlNorum Thats exactly the solution and beautiful explanation and also note even -### could be used which works similar to -v.Ambrogino
@AngelusMortis, the -lstdc++ flag, presumably.Shadbush
@AngelusMortis, probably not. You'll almost certainly need the runtime files too. The list above is the minimum set I came up with for this particular test. You can do an experiment of your own to compare. Why don't you just use the compiler driver to link like you're supposed to?Shadbush
Just wondering why you change the g++ to c++ later, i think they are refer to the same compiler.Sarre
G
0

I managed to compile like this

ld -s hello.o crt2.o -o hello.exe libstdc++.a libgcc.a libmingw32.a libmingwex.a libmsvcrt.a libkernel32.a
Graff answered 10/5, 2022 at 7:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.