Combine Object Files and Trace Symbols with ld.bfd

ld.bfd is the GNU linker underneath every cc invocation, and calling it directly shows you exactly what your compiler has been hiding. This guide combines two ELF object files into one relocatable object, inspects the result, and traces a symbol reference. Allow about 15 minutes. You need a shell, a C compiler, Binutils, and permission to write in a working directory. No elevated privileges are needed.

1. Know which linker you are running

Check the installed version and the available target emulation.

ld.bfd --version
ld.bfd -V

On the machine used for this guide, the installed packages are Binutils 2.42-4ubuntu2.10 and the program reports GNU ld 2.42. The command may also be named ld, x86_64-linux-gnu-ld, or aarch64-linux-gnu-ld. The target-specific name is useful when a build has more than one toolchain.

2. Make two objects

Create a small pair of source files. The second function calls a symbol defined by the first, which gives the linker something observable to resolve.

mkdir -p /tmp/ld-bfd-demo
cd /tmp/ld-bfd-demo
cat > left.c <<'EOF'
int left(void) { return 7; }
EOF
cat > right.c <<'EOF'
extern int left(void);
int right(void) { return left(); }
EOF
cc -c -o left.o left.c
cc -c -o right.o right.c

Now confirm the inputs really are object files before you link them:

file left.o right.o
nm left.o right.o

3. Produce a relocatable object

Combine the objects with -r:

ld.bfd -r -o combined.o left.o right.o

-r, also called a relocatable link, writes another object file instead of a final executable, so relocations and unresolved references can remain for a later link. It is a safe first experiment because it does not replace either input.

Inspect the output:

readelf -h combined.o | grep -E 'Class:|Machine:|Type:'
nm combined.o

For the x86-64 example, the header reports Type: REL (Relocatable file), and nm shows both T left and T right: proof the two input symbol tables were combined. Leave out -r and the linker attempts a final link instead, which will usually fail here since these objects supply no startup code or entry point.

4. Trace a confusing symbol

Ask the linker to report where one symbol is defined and referenced:

ld.bfd --trace-symbol=left -r -o traced.o left.o right.o

Expected diagnostics look like:

ld.bfd: left.o: definition of left
ld.bfd: right.o: reference to left

--trace-symbol earns its keep once several archives or generated objects all contain similarly named symbols and you need to know which one actually won. It is purely diagnostic output; it changes neither visibility nor resolution.

5. Use libraries and scripts deliberately

For a final executable, you need to pass the startup objects, libraries, and entry-point details appropriate to the target. In ordinary Linux builds, letting cc do the invoking is usually safer because it supplies those platform-specific pieces for you. If you pass linker options through the compiler driver, prefix them with -Wl,, for example:

cc left.o right.o -Wl,-Map=link.map -o demo

The option itself belongs to ld.bfd; cc just handles the comma-separated forwarding syntax. Check the map only once the command has succeeded:

test -s link.map && sed -n '1,30p' link.map

Linker options that name files are position-sensitive. In particular, -lNAME searches for a library at the exact point where it appears on the command line, so put objects and archives in an order that lets the linker see references before the library that satisfies them. If a static library has circular dependencies, the manpage documents --start-group and --end-group for repeated archive searching.

Common traps and recovery

Done means