为什么组装后的可执行文件大小相同
why assembled executable size is the same
在x86_64 体系结构中,可以将一些指令操作数组合更改为更短的指令操作数组合,以达到相同的效果,但可执行文件更小。
例如,通常这样写:
xor eax, eax
而不是:
xor rax, rax
我想测试一下,用汇编写了一个简单的程序:
segment .text
global main
main:
push rbp
mov rbp, rsp
xor rax, rax ; line in question
leave
ret
建成:
yasm -f elf64 -m amd64 -g dwarf2 main.asm; clang -o main main.o
检查尺寸:
stat main
得到:
....
Size: 9184
...
好的,将有问题的行更改为:
xor eax, eax
希望得到更小的可执行文件,但得到了相同的 9184 字节大小。
为什么使用较短的指令表尺寸没有减小?
使用size
命令找出二进制文件的大小。使用 ls
或 stat
是不准确的,因为部分二进制文件被 填充 到 2 的某个幂(例如,16 的下一个倍数)。
但是,在您的情况下仍然没有区别,因为来自 main.o
的文本段被填充为 16 字节的倍数,之后链接了启动代码 crt0.o
。因此代码大小没有差异。
反汇编:
31 c0 xor eax,eax ; 2 bytes opcode
48 31 c0 xor rax,rax ; 3 bytes opcode
可执行文件包含许多其他内容(正如其他人的评论中所解释的),并且您的代码也可能总体上保持不变,因为下一个代码可能会通过其他 nop
对齐。不要指望文件大小会对剃掉的每个字节的操作码做出反应。
当目标文件链接在一起时,链接器会在 main.o
的 .text 部分的末尾插入填充,因此 crt0.o
的文本部分的开始是从 16B 对齐边界开始的。
如果你像我建议的那样反汇编你的二进制文件,你会看到这个:
$ objdump -Mintel -drw main
...
0000000000400500 <main>:
400500: 55 push rbp
400501: 48 89 e5 mov rbp,rsp
400504: 48 31 c0 xor rax,rax
400507: c9 leave
400508: c3 ret
400509: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] <--- padding inserted by linker
0000000000400510 <__libc_csu_init>:
400510: 41 57 push r15
...
更改 main()
的大小只会更改 NOP 填充的大小,直到您通过 16B 边界。
有趣的是,如果你反汇编main.o
,ret
之后没有填充,所以我认为NOP一定是链接器插入的。
使用 readelf -aW main.o
显示:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
...
[ 4] .text PROGBITS 0000000000000000 000040 000009 00 AX 0 0 16
....
没有 -W,您可以看到完整的列名,而不是将它们打包在一行中。最后一列是 "alignment"。这就是 yasm 告诉链接器该对象的 .text 部分需要 32B 或链接器输出的文本段内的任何其他对齐方式的方式。
在 main:
之前添加 ALIGN 4096
会导致 .o 在 .text 的对齐列中具有 4096。它将 NOP 填充添加到链接二进制文件中函数 before main
的末尾,因此 main
位于 0x00402000。这确实改变了二进制文件的大小。
在x86_64 体系结构中,可以将一些指令操作数组合更改为更短的指令操作数组合,以达到相同的效果,但可执行文件更小。 例如,通常这样写:
xor eax, eax
而不是:
xor rax, rax
我想测试一下,用汇编写了一个简单的程序:
segment .text
global main
main:
push rbp
mov rbp, rsp
xor rax, rax ; line in question
leave
ret
建成:
yasm -f elf64 -m amd64 -g dwarf2 main.asm; clang -o main main.o
检查尺寸:
stat main
得到:
....
Size: 9184
...
好的,将有问题的行更改为:
xor eax, eax
希望得到更小的可执行文件,但得到了相同的 9184 字节大小。 为什么使用较短的指令表尺寸没有减小?
使用size
命令找出二进制文件的大小。使用 ls
或 stat
是不准确的,因为部分二进制文件被 填充 到 2 的某个幂(例如,16 的下一个倍数)。
但是,在您的情况下仍然没有区别,因为来自 main.o
的文本段被填充为 16 字节的倍数,之后链接了启动代码 crt0.o
。因此代码大小没有差异。
反汇编:
31 c0 xor eax,eax ; 2 bytes opcode
48 31 c0 xor rax,rax ; 3 bytes opcode
可执行文件包含许多其他内容(正如其他人的评论中所解释的),并且您的代码也可能总体上保持不变,因为下一个代码可能会通过其他 nop
对齐。不要指望文件大小会对剃掉的每个字节的操作码做出反应。
当目标文件链接在一起时,链接器会在 main.o
的 .text 部分的末尾插入填充,因此 crt0.o
的文本部分的开始是从 16B 对齐边界开始的。
如果你像我建议的那样反汇编你的二进制文件,你会看到这个:
$ objdump -Mintel -drw main
...
0000000000400500 <main>:
400500: 55 push rbp
400501: 48 89 e5 mov rbp,rsp
400504: 48 31 c0 xor rax,rax
400507: c9 leave
400508: c3 ret
400509: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0] <--- padding inserted by linker
0000000000400510 <__libc_csu_init>:
400510: 41 57 push r15
...
更改 main()
的大小只会更改 NOP 填充的大小,直到您通过 16B 边界。
有趣的是,如果你反汇编main.o
,ret
之后没有填充,所以我认为NOP一定是链接器插入的。
使用 readelf -aW main.o
显示:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
...
[ 4] .text PROGBITS 0000000000000000 000040 000009 00 AX 0 0 16
....
没有 -W,您可以看到完整的列名,而不是将它们打包在一行中。最后一列是 "alignment"。这就是 yasm 告诉链接器该对象的 .text 部分需要 32B 或链接器输出的文本段内的任何其他对齐方式的方式。
在 main:
之前添加 ALIGN 4096
会导致 .o 在 .text 的对齐列中具有 4096。它将 NOP 填充添加到链接二进制文件中函数 before main
的末尾,因此 main
位于 0x00402000。这确实改变了二进制文件的大小。