gcc:通过 gcc 将 -nostartfiles 传递给 ld 以获得最小二进制大小

gcc: passing -nostartfiles to ld via gcc for minimal binary size

考虑以下 64 位的 gas(GNU 汇编程序)文件 linux

go.s

        .global _start
        .text

_start:
        mov     , %rax               # system call 60 is exit
        xor     %rdi, %rdi              # we want return code 0
        syscall  

现在编译:

rm -f go go.o
as -o go.o go.s
ld -o go -s -nostartfiles go.o
ls -l go                                            # -> 344 bytes

我们得到了 344 字节的超小尺寸。

太棒了!使用 gcc 和单独的 ld 命令:

# 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

但是如何只使用一个 gcc 命令就可以得到这么小的尺寸

# single gcc command                                                                                                                                                                     
rm -f go go.o
gcc -xassembler -s -static -nostartfiles -o go go.s
ls -l go                                            # -> 4400 bytes  too big!!!

该死的 4400 字节!哪个单行 gcc 调用将给出 344 个字节?

运行 上面带有 -v 标志的单行...

gcc -v -xassembler -s -static -nostartfiles -o go go.s

...表明 -nostartfiles 未传递给 collect2 link 命令。

嗯……

所以任务:显示给出最小大小的单行 gcc 调用!

谢谢。


使用 -v 标志进行试验:

gcc -v -xassembler -s -static -nostartfiles -o go go.s

执行以下操作:

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

/usr/lib/gcc/x86_64-linux-gnu/8/collect2 命令(上面)中手动添加 -nostartfiles(直接在 collect2 之后)给出 520 字节的大小。

手动添加 -nostartfiles(直接在 collect2 之后)并删除 --build-id 得到 344 字节的大小。我们可以看到 here-Wl,--build-id=none 应该删除构建 ID。

但是如何指示 gcc 将 -nostartfiles 传递给 ldcollect2


回复 Jester 的评论:

正在执行以下操作 (-Wl,-nostartfiles):

gcc -Wl,-nostartfiles,--build-id=none -v -xassembler -s -static -nostartfiles -o go go.s

会在collect2命令的末尾添加-nostartfiles太远,结果输出二进制go不是甚至创造。如何给出命令,以便 -nostartfilescollect2 ... 命令中更早出现:我知道它有效,如果我手动将 -nostartfiles 构造为标志 开头.

正在寻找here,有人声称-Wl,-nostartfiles不会被正确处理。但是,如果我只使用 gcc -nostartflags ...,那么它不会传递给 linker:Maby,这是 gcc 中的错误??

也许所有的代码都被优化掉了……什么都没有留下??参见 here

-nostartfiles 不是 ld 选项。 解析为ld -n -o startfiles.

我试过你的命令,它们没有创建名为 go 的文件,而是创建了一个名为 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

你的 go 一定是你的 ld -s -nostartfiles -o go go.o 遗留下来的,其中 -o go 是命令行上 -o 的最后一个实例,没有被 -o startfiles.


使二进制文件变小的选项是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".

相关:Minimal executable size now 10x larger after linking than 2 years ago, for tiny programs?


作为 GCC 选项,-nostartfiles 告诉 gcc 前端 而不是 到 link crt*.o 文件。如果您是 运行 ld 手动,则只需省略提及它们。没有必要告诉 ld 你没有 link 什么,ld 命令本身不会 link 任何它没有明确告诉的事情。链接 CRT 文件和 libc / libgcc 是 gcc 默认值,而不是 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

您希望 -nostdlib 省略库以及 CRT 启动文件。
-nostartfiles 只是 -nostdlib 的一部分。
(虽然当静态 linking 时,ld 不会从 libc.alibgcc.a 中提取任何代码,因为你的文件没有引用任何外部符号。所以你实际上仍然使用 -nostartfiles 获得与 -nostdlib 相同的 344 字节文件。但是 -nostdlib 更准确地复制您的手动 ld 命令,不传递任何额外的文件。)

您需要 -static 才能在 -pie 是默认值的 GCC 上动态地 link。 (这也意味着 -no-pie;默认情况下不会启用 -static-pie。)
--nmagic 失败并显示 错误:如果您让 GCC 尝试动态 link PIE 可执行文件(即使没有共享库),PHDR 段未被 LOAD 段覆盖