一次意外的 hello world 探索之旅

1. 问题描述

C 语言编写 hello world如下:

编译之后,发现在 debian testing 下生成的二进制文件过大(约17KB)

Distro Release GCC Version LD Version Libc Version ABI FileSize
Debian buster 7.4.0/8.2.0 2.31.1 2.28-5 3.2.0 17 KB
Debian stretch 6.3.0-18 2.28 2.24-11 2.6.32 8.5 KB
Ubuntu 18.04 7.3.0 2.30 2.27-3 3.2.0 8.2 KB
Arch 2019.01 8.2.1 2.32.1 2.28-5 3.2.0 17KB

2. 排查过程

2.1 检查 gcc 优化级别、尝试 strip 移除符号

  • 不同的 gcc -O 优化级别对 ELF 文件大小基本无影响,问题未解决
  • strip 移除符号,文件体积大概减少 2KB,问题未解决

2.2 比较 ELF 头部

使用 readelf、nm、objdump 等工具对比 ELF 文件信息,甚至使用了 010 editor 查看文件内容
下面列一下对两个文件执行 readelf -S 的输出,左侧为 debian stretch 下编译的 8.5KB 小文件,右侧为 debian buster 下编译的 17KB 大文件。
可以看出来的是,右侧文件很多section 都以 4096(0x1000) 为长度进行了对齐,链接器似乎是问题的关键
file

2.3 排查 ld 的问题

1) 使用 gcc -c 或者 nasm 编译汇编,生成中间文件 .o

随后使用 ld hello.o 对文件进行链接,发现在 debian buster 和 debian stretch 下的文件大小分别为 9 KB 和 1.1 KB

2) 检查链接过程
使用命令 ld --verbose hello.o,输出详细的链接过程信息,并对比不同系统下的输出,发现一些明显不同的地方(右侧为 ld 2.31 的输出)
file

3)改变 ld align 参数
参考链接:https://stackoverflow.com/questions/15400910/ld-linker-script-producing-huge-binary

  • 方案一: ld --nmagic(-n)或者 ld --omagic(-N)
    实际测试表明,使用 -n/-N 参数,可以将汇编写的hello.asm文件体积控制在1KB左右,同时 ELF 二进制文件正常运行;
    但是,文档说这个参数会禁用动态链接,将此参数通过 gcc 传递给 ld 链接 C 库时确实发生了错误:

  • 方案二:gcc -z max-page-size=0x1000
    实际测试结果:
    debian buster 上 ELF 文件体积无显著变化,减小该值 ELF 二进制无法运行; 增大该值,ELF 正常运行,文件体积增大 (系统PAGESIZE大小: getconf PAGESIZE ==> 4096)
    debian stable 上减少该值 ELF 文件体积稍微减小,低于一定数值ELF二进制文件则无法运行; 增大该值,文件体积不变 (系统PAGESIZE大小:getconf PAGESIZE=4096)

  • 方案三: 使用 gold 替代 ld
    手动使用 gold 进行链接,或者 gcc hello.c -fuse-ld=gold , 在 debian buster 上文件大小为 7.8 KB,ELF 文件正常运行
    备注:系统里 gold 版本同 ld; 使用 gcc -fuse-ld=bfd即可使用默认的 ld

2.4 最终的答案

Binutils 2.31 will enable -z separate-code by default for x86 to avoid
mixing code pages with data to improve cache performance as well as
security. To reduce x86-64 executable and shared object sizes, the
maximum page size is reduced from 2MB to 4KB. But x86-64 kernel must
be aligned to 2MB. Pass -z max-page-size=0x200000 to linker to force
2MB page size regardless of the default page size used by linker.

Tested with Linux kernel 4.15.6 on x86-64.

参考链接:https://lore.kernel.org/patchwork/patch/934898/

这段话说的很明白了,因此如果我们想要获得较小体积的二进制文件,同时又不能减小 max-page-size 的话(上述实验可知,不合适的max-page-size会导致链接后的程序无法运行),我们可以选择关闭 separate-code 选项; 因此最终解决方案如下:
gcc hello.c -z noseparate-code

发表评论

电子邮件地址不会被公开。 必填项已用*标注