对于小型程序,链接后的最小可执行文件大小现在比2年前大10倍? [英] Minimal executable size now 10x larger after linking than 2 years ago, for tiny programs?
问题描述
对于大学课程,如果使用gcc/clang与汇编语言编写和编译,我喜欢比较功能相似的程序的代码大小.在重新评估如何进一步缩小某些可执行文件的大小的过程中,当我两年前汇编/链接的同一汇编代码在再次构建后变得大于10倍时,我真不敢相信.不仅对于helloworld,对于多个程序都适用:
For a university course, I like to compare code-sizes of functionally similar programs if written and compiled using gcc/clang versus assembly. In the process of re-evaluating how to further shrink the size of some executables, I couldn't trust my eyes when the very same assembly code I assembled/linked 2 years ago now has grown >10x in size after building it again (which true for multiple programs, not only helloworld):
$ make
as -32 -o helloworld-asm-2020.o helloworld-asm-2020.s
ld -melf_i386 -o helloworld-asm-2020 helloworld-asm-2020.o
$ ls -l
-rwxr-xr-x 1 xxx users 708 Jul 18 2018 helloworld-asm-2018*
-rwxr-xr-x 1 xxx users 8704 Nov 25 15:00 helloworld-asm-2020*
-rwxr-xr-x 1 xxx users 4724 Nov 25 15:00 helloworld-asm-2020-n*
-rwxr-xr-x 1 xxx users 4228 Nov 25 15:00 helloworld-asm-2020-n-sstripped*
-rwxr-xr-x 1 xxx users 604 Nov 25 15:00 helloworld-asm-2020.o*
-rw-r--r-- 1 xxx users 498 Nov 25 14:44 helloworld-asm-2020.s
汇编代码为:
.code32
.section .data
msg: .ascii "Hello, world!\n"
len = . - msg
.section .text
.globl _start
_start:
movl $len, %edx # EDX = message length
movl $msg, %ecx # ECX = address of message
movl $1, %ebx # EBX = file descriptor (1 = stdout)
movl $4, %eax # EAX = syscall number (4 = write)
int $0x80 # call kernel by interrupt
# and exit
movl $0, %ebx # return code is zero
movl $1, %eax # exit syscall number (1 = exit)
int $0x80 # call kernel again
使用GNU as
和GNU ld
(始终使用32位汇编)编译的同一hello world程序当时为708字节,现在已增长到8.5K.即使告诉链接器关闭页面对齐方式( ld -n
),它仍然有将近4.2K. strip
ping/ strip
ping也不起作用.
The same hello world program, compiled using GNU as
and GNU ld
(always using 32-bit assembly) was 708 bytes then, and has grown to 8.5K now. Even when telling the linker to turn off page alignment (ld -n
), it still has almost 4.2K. strip
ping/sstrip
ping doesn't pay off either.
readelf
告诉我,节头的开始在代码中要晚得多(字节468 vs 8464),但是我不知道为什么.它运行在与2018年相同的Arch系统上,Makefile是相同的,我没有链接到任何库(尤其是不是libc).我猜关于 ld
的事情已经发生了变化,原因是目标文件仍然很小,但是为什么?为什么?
readelf
tells me that the start of section headers is much later in the code (byte 468 vs 8464), but I have no idea why. It's running on the same arch system as in 2018, the Makefile is the same and I'm not linking against any libraries (especially not libc). I guess something regarding ld
has changed due to the fact that the object file is still quite small, but what and why?
免责声明:我正在x86-64机器上构建32位可执行文件.
Disclaimer: I'm building 32-bit executables on an x86-64 machine.
我正在使用GNU binutils(及ld)版本2.35.1,这是一个base64编码的归档文件,其中包括源代码和两个可执行文件(旧的小文件,新的大文件):
I'm using GNU binutils (as & ld) version 2.35.1 Here is a base64-encoded archive which includes the source and both executables (small old one, large new one) :
cat << EOF | base64 -d | tar xj
QlpoOTFBWSZTWVaGrEQABBp////xebj/7//Xf+a8RP/v3/rAAEVARARAeEADBAAAoCAI0AQ+NAam
ytMpCGmpDVPU0aNpGmh6Rpo9QAAeoBoADQaNAADQ09IAACSSGUwaJpTNQGE9QZGhoADQPUAA0AAA
AA0aA4AAAABoAAAAA0GgAAAAZAGgAHAAAAANAAAAAGg0AAAADIA0AASJCBIyE8hHpqPVPUPU/VAa
fqn6o0ep6BB6TQaNGj0j1ABobU00yeU9JYiuVVZKYE+dKNa3wls6x81yBpGAN71NoylDUvNryWiW
E4ER8XkfpaJcPb6ND12ULEqkQX3eaBHP70Apa5uFhWNDy+U3Ekj+OLx5MtDHxQHQLfMcgCHrGayE
Dc76F4ZC4rcRkvTW4S2EbJAsbBGbQxSbx5o48zkyk5iPBBhJowtCSwDBsQBc0koYRSO6SgJNL0Bg
EmCoxCDAs5QkEmTGmQUgqZNIoxsmwDmDQe0NIDI0KjQ64leOr1fVk6AaVhjOAJjLrEYkYy4cDbyS
iXSuILWohNh+PA9Izk0YUM4TQQGEYNgn4oEjGmAByO+kzmDIxEC3Txni6E1WdswBJLKYiANdiQ2K
00jU/zpMzuIhjTbgiBqE24dZWBcNBBAAioiEhCQEIfAR8Vir4zNQZFgvKZa67Jckh6EHZWAWuf6Q
kGy1lOtA2h9fsyD/uPPI2kjvoYL+w54IUKBEEYFBIWRNCNpuyY86v3pNiHEB7XyCX5wDjZUSF2tO
w0PVlY2FQNcLQcbZjmMhZdlCGkVHojuICHMMMB5kQQSZRwNJkYTKz6stT/MTWmozDCcj+UjtB9Cf
CUqAqqRlgJdREtMtSO4S4GpJE2I/P8vuO9ckqCM2+iSJCLRWx2Gi8VSR8BIkVX6stqIDmtG8xSVU
kk7BnC5caZXTIynyI0doXiFY1+/Csw2RUQJroC0lCNiIqVVUkTqTRMYqKNVGtCJ5yfo7e3ZpgECk
PYUEihPU0QVgfQ76JA8Eb16KCbSzP3WYiVApqmfDhUk0aVc+jyBJH13uKztUuva8F4YdbpmzomjG
kSJmP+vCFdKkHU384LdRoO0LdN7VJlywJ2xJdM+TMQ0KhMaicvRqfC5pHSu+gVDVjfiss+S00ikI
DeMgatVKKtcjsVDX09XU3SzowLWXXunnFZp/fP3eN9Rj1ubiLc0utMl3CUUkcYsmwbKKrWhaZiLO
u67kMSsW20jVBcZ5tZUKgdRtu0UleWOs1HK2QdMpyKMxTRHWhhHwMnVEsWIUEjIfFEbWhRTRMJXn
oIBSEa2Q0llTBfJV0LEYEQTBTFsDKIxhgqNwZB2dovl/kiW4TLp6aGXxmoIpVeWTEXqg1PnyKwux
caORGyBhTEPV2G7/O3y+KeAL9mUM4Zjl1DsDKyTZy8vgn31EDY08rY+64Z/LO5tcRJHttMYsz0Fh
CRN8LTYJL/I/4u5IpwoSCtDViIA=
EOF
更新:当使用 ld.gold
而不是 ld.bfd
(默认将/usr/bin/ld
链接到ld.bfd )时,可执行文件的大小变得像预期的那样小:
Update:
When using ld.gold
instead of ld.bfd
(to which /usr/bin/ld
is symlinked to by default), the executable size becomes as small as expected:
$ cat Makefile
TARGET=helloworld
all:
as -32 -o ${TARGET}-asm.o ${TARGET}-asm.s
ld.bfd -melf_i386 -o ${TARGET}-asm-bfd ${TARGET}-asm.o
ld.gold -melf_i386 -o ${TARGET}-asm-gold ${TARGET}-asm.o
rm ${TARGET}-asm.o
$ make -q
$ ls -l
total 68
-rw-r--r-- 1 eso eso 200 Dec 1 13:57 Makefile
-rwxrwxr-x 1 eso eso 8700 Dec 1 13:57 helloworld-asm-bfd
-rwxrwxr-x 1 eso eso 732 Dec 1 13:57 helloworld-asm-gold
-rw-r--r-- 1 eso eso 498 Dec 1 13:44 helloworld-asm.s
也许我以前只是在不知不觉中使用过 gold
.
Maybe I just used gold
previously without being aware.
推荐答案
通常来说,它不是10倍,这是Jester所说的几个部分的页面对齐方式,这是对 ld
的默认链接器所做的更改出于安全原因的脚本:
It's not 10x in general, it's page-alignment of a couple sections as Jester says, per changes to ld
's default linker script for security reasons:
- 第一个更改:确保
.data
中的数据没有出现在.text
的任何映射中,因此所有静态数据都不可用于ROP/在可执行页面中幽灵小工具.(在较早的ld
中,这意味着程序头两次将同一磁盘块映射到实际.data节的RW-without-exec段中.可执行程序映射仍然是只读的.) - 最近的更改:将
.rodata
与.text
分离为单独的段,因此,静态数据也不会映射到可执行页中.以前,可以将const char code [] = {...}
强制转换为函数指针并调用,而无需mprotect或gcc -z execstack
或其他技巧(如果需要)您想以这种方式测试shellcode.
- First change: Making sure data from
.data
isn't present in any of the mapping of.text
, so none of that static data is available for ROP / Spectre gadgets in an executable page. (In olderld
, that meant the program-headers mapped the same disk-block twice, also into a RW-without-exec segment for the actual .data section. The executable mapping was still read-only.) - More recent change: Separate
.rodata
from.text
into separate segments, again so static data isn't mapped into an executable page. Previously,const char code[]= {...}
could be cast to a function pointer and called, without needing mprotect orgcc -z execstack
or other tricks, if you wanted to test shellcode that way.
多余的空间只是 00
填充,并且可以在 .tar.gz
或其他任何格式中很好地压缩.
That extra space is just 00
padding and will compress well in a .tar.gz
or whatever.
因此,它的最坏情况上限约为2x 4k页,微小的可执行文件也接近该最坏情况.
gcc -Wl,-nmagic
将关闭节的页面对齐.(请参见 ld(1)
手册页)我不知道为什么这不能将所有内容压缩到原来的大小.也许检查默认的链接描述文件可能会有所帮助,但这很长.运行 ld --verbose
进行查看.
gcc -Wl,--nmagic
will turn off page-alignment of sections if you want that for some reason. (see the ld(1)
man page) I don't know why that doesn't pack everything down to the old size. Perhaps checking the default linker script would shed some light, but it's pretty long. Run ld --verbose
to see it.
条带
ping对部分的填充无济于事;我认为它只能删除整个部分.
strip
ping won't help for padding that's part of a section; I think it can only remove whole sections.
这篇关于对于小型程序,链接后的最小可执行文件大小现在比2年前大10倍?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!