Pre-link static binaries/remove

Simon Richter Simon.Richter@hogyros.de
Tue Nov 11 14:32:57 GMT 2025


Hi,

On 11/11/25 9:22 PM, Thomas Richter wrote:

> Clearly, as long as a.o and b.o exist as independent objects in the 
> final library, bar() cannot be removed. It will be removed in the final 
> step when creating a .so. However, would it be possible to merge a.o and 
> b.o into "merged.o" - a single unit - and by doing so, resolve the call 
> of "bar()" already by replacing it by a "relative branch" from one part 
> of the unit to another.

You can merge all the objects of a shared library into one, e.g.

     $ ld -o /tmp/test.o -r --whole-archive /lib/x86_64-linux-gnu/libc.a

but this does not resolve any relocations because that doesn't really 
gain us anything. The pass that replaces jump/call instructions with 
shorter forms ("linker relaxation") needs to be able to adjust any 
relocations that cross a relocation it performs, so we cannot remove the 
relocation information yet. If we were to "pre-link" and relax some of 
the calls, a later linking step could still make a relocation possible, 
e.g. assuming 16 bit opcodes and 32 bit operands:

     a:
         call hidden_b	-- 6 bytes
         call ext_a	-- 6 bytes
         jmp a		-- 6 bytes
     hidden_b:
         ret		-- 2 bytes

replacing this with

     a:
         call pc+10	-- 2 bytes
         call ext_a	-- 6 bytes
	jmp pc-8	-- 2 bytes
         ret		-- 2 bytes

would mean that if we link "ext_a" directly behind it, we can no longer 
replace the 6-byte "call ext_a" with a shorter form, because we need to 
adjust the offsets in the first call and the jmp instruction again here.

In principle, we could partially relax here and replace the relocation:

     a:
     1:
         call 2f		-- 2 bytes
         call ext_a	-- 6 bytes
         jmp 1b		-- 2 bytes
     2:
         ret		-- 2 bytes

but all we've gained here is to make the symbol local. Final relocation 
will still only happen during final linking, and we need a relaxation 
pass there anyway to replace the second call instruction.

If you want to turn the hidden symbols local, there is objcopy

     $ objcopy --localize-hidden test.o test2.o

That will still not resolve these symbols or apply relaxations, but that 
is fine, all of that is handled during final link.

For more aggressive optimization, use -flto to link before compiling. I 
think it should be possible to combine that with "ld -r" and "objcopy 
--localize-hidden", and it should also remove unused functions.

    Simon


More information about the Binutils mailing list