consider the following gas (GNU assembler) file for 64 bit linux
go.s
.global _start
.text
_start:
mov $60, %rax # system call 60 is exit
xor %rdi, %rdi # we want return code 0
syscall
Now to compile:
rm -f go go.o
as -o go.o go.s
ld -o go -s -nostartfiles go.o
ls -l go # -> 344 bytes
We get a super-small size of 344 bytes.
Great! With gcc and separate ld
command:
# gcc with separate link
rm -f go go.o
gcc -xassembler -c go.s
ld -s -nostartfiles -o go go.o
ls -l go # -> 344 bytes
But how does one get that small size, using only a single gcc command?
# single gcc command
rm -f go go.o
gcc -xassembler -s -static -nostartfiles -o go go.s
ls -l go # -> 4400 bytes too big!!!
Damn 4400 bytes! Which single line gcc invocation will give 344 bytes?
Running the above single line with the -v
flag...
gcc -v -xassembler -s -static -nostartfiles -o go go.s
... shows that
-nostartfiles
is not passed to the collect2
link command.
Hmmm...
So task: show me the single-line gcc invocation giving the minimal size!
Thanks.
Experimenting with the -v
flag:
gcc -v -xassembler -s -static -nostartfiles -o go go.s
does the following:
as -v --64 -o go.o go.s
# will give 4400 bytes
/usr/lib/gcc/x86_64-linux-gnu/8/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/8/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/8/lto-wrapper -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_eh -plugin-opt=-pass-through=-lc --build-id -m elf_x86_64 --hash-style=gnu -static -o go -s -L/usr/lib/gcc/x86_64-linux-gnu/8 -L/usr/lib/gcc/x86_64-linux-gnu/8/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/8/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/8/../../.. go.o --start-group -lgcc -lgcc_eh -lc --end-group
Manually adding -nostartfiles
(directly after collect2
) in the /usr/lib/gcc/x86_64-linux-gnu/8/collect2
command (above) gives a size of 520 bytes.
Manually adding -nostartfiles
(directly after collect2
) and removing --build-id
gives a size of 344 bytes. As we can see here, -Wl,--build-id=none
should remove the build-id.
But how does one instruct gcc to pass -nostartfiles
to ld
or collect2
?
In response to Jester's comment:
Doing the following (-Wl,-nostartfiles
):
gcc -Wl,-nostartfiles,--build-id=none -v -xassembler -s -static -nostartfiles -o go go.s
will add -nostartfiles
too far at the end of the collect2
command, with the result that the output binary go
is not even created. How does one give the command, so that -nostartfiles
occurs earlier in the collect2 ...
command: I know it works, if I manually construct -nostartfiles
to be a flag right at the beginning.
Looking here, there is a claim that -Wl,-nostartfiles
will not be handled correctly. But if I just use gcc -nostartflags ...
, then it is not passed to the linker: Maby this is a bug in gcc??
Maby all code gets optimized away... leaving nothing at all?? See here
-nostartfiles
is not an ld
option. It parses as ld -n -o startfiles
.
I tried your commands, and they don't create a file called go
, they create an executable called startfiles
.
$ cat > go.s
paste + control-d
$ as -o go.o go.s
$ ld -o go -s -nostartfiles go.o
$ ll go
ls: cannot access 'go': No such file or directory
$ ll -clrt
-rw-r--r-- 1 peter peter 193 May 13 11:33 go.s
-rw-r--r-- 1 peter peter 704 May 13 11:33 go.o
-rwxr-xr-x 1 peter peter 344 May 13 11:33 startfiles
Your go
must have been left over from your ld -s -nostartfiles -o go go.o
where -o go
was the last instance of -o
on the command line, not overridden by -o startfiles
.
The option that makes your binary small is ld -n
:
-n
--nmagic
Turn off page alignment of sections, and disable linking against shared libraries. If the output format supports Unix style magic numbers, mark the output as "NMAGIC".
Related: Minimal executable size now 10x larger after linking than 2 years ago, for tiny programs?
As a GCC option, -nostartfiles
tells the gcc
front-end not to link crt*.o
files. If you're running ld
manually, you just omit mentioning them. There's no need to tell ld
what you're not linking, the ld
command itself doesn't link anything it's not explicitly told to. Linking CRT files and libc / libgcc are gcc
defaults, not ld
.
$ gcc -s -nostdlib -static -Wl,--nmagic,--build-id=none go.s
$ ll a.out
-rwxr-xr-x 1 peter peter 344 May 13 12:35 a.out
You want -nostdlib
to omit libraries as well as CRT start files.
-nostartfiles
is only a subset of what -nostdlib
does.
(Although when statically linking, ld
doesn't pull in any code from libc.a
or libgcc.a
because your file doesn't reference any external symbols. So you actually still get the same 344 byte file from using -nostartfiles
as -nostdlib
. But -nostdlib
replicates your manual ld
command more exactly, not passing any extra files.)
You need -static
to not dynamically link, on a GCC where -pie
is the default. (This also implies -no-pie
; -static-pie
won't be enabled by default.)
--nmagic
fails with error: PHDR segment not covered by LOAD segment if you let GCC try to dynamically link a PIE executable (even with no shared libraries).