-
-
[旧帖] [翻译]42字节可执行文件;ELF介绍;求Kx 0.00雪花
-
发表于: 2012-9-22 11:42 1114
-
Size is Everything!
本文介绍了给程序瘦身的过程,同时也介绍ELF文件格式。本文的例子运行在386架构,Linux系统。为了展示这个过程,我们需要一个小程序。小程序如下:
/*tiny.c*/
int main(void) {return 42;}
编译执行 $gcc -Wall tiny.c
$./a.out ; echo $?
a.out这个可执行文件的大小: $wc -c a.out
3998 a.out
(不同平台可能有变化)
看起来并不大,但是还是有很多水分可以挤。
第一步,试一下编译时优化。
$ gcc -Wall -s -03 tiny.c
$ wc -c a.out
2616 a.out
(似乎没有-o3结果是一样的,还没弄明白gcc的优化原理是啥)
看起来没什么可优化的;
2. 第二步,在即使只有一行的C程序中,也都没有啥可压缩的,使用汇编,避免C自动带来的开销。让程序返回42,也即需要将累加寄存器,eax,赋值为42,然后返回。
; tiny.asm
BITS 32
GLOBAL main
SECTION .text
main:
mov eax, 42;
ret
编译如下:
$ nasm -f elf tiny.asm
$gcc -s tiny.o
$./a.out;echo $?
结果正确,大小 2604
结果影响不大,原因在于main()接口带来的开销,链接器仍然会添加与OS的接口,正是这个接口调用了main()。如何去掉这个接口呢?
如果不使用main,而自定义_start如何?
;tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
mov eax,42
ret
GCC编译:
$nasm -f elf tiny.asm
$gcc -s tiny.o
报错如下:
tiny.o(.text+0x0): multiple definition of `_start'
/usr/lib/crt1.o(.text+0x0): first defined here
/usr/lib/crt1.o(.text+0x36): undefined reference to `main'
GCC手册告诉我们,如果使用参数-nostartfiles,则链接过程中不用标准系统入口,其它正常。
那么编译如下:
$nasm -f elf tiny.asm
$gcc -s -nostartfiles tiny.o
$.a.out; echo $?
Segmentation fault
139
GCC没有报错,但是程序出错了。为什么呢?
原因在于我们把_start作为一个C程序,并希望从它返回。但是,它并不是一个程序,它只是一个符号用来告诉链接器程序的入口地址。如果我们查看的话,会发现栈顶的位置是1,当然不是一个地址。实际上,它是我们的argc的值。栈顶没有返回地址。
那么如何从_start返回呢?调用exit()函数!
准确地说,调用的是_exit()函数。因为,exit()另外还有其他一些功能,但因为我们希望绕开库函数启动代码,我们也得避免库的退出代码,直接使用OS的退出处理方式。
版本三:调用_exit()。_exit()接收一个整数值作为参数,我们需要将42压入栈,然后调用。
;tiny.asm
BITS 32
EXTERN _exit
GLOBAL _start
SECTION .text
_start:
push dword 42
call _exit
编译如下:
$nasm -f elf tiny.asm
$gcc -s -nostartfiles tiny.o
$./a.out;echo $?
成功;大小1340;缩小了一半。
本文介绍了给程序瘦身的过程,同时也介绍ELF文件格式。本文的例子运行在386架构,Linux系统。为了展示这个过程,我们需要一个小程序。小程序如下:
/*tiny.c*/
int main(void) {return 42;}
编译执行 $gcc -Wall tiny.c
$./a.out ; echo $?
a.out这个可执行文件的大小: $wc -c a.out
3998 a.out
(不同平台可能有变化)
看起来并不大,但是还是有很多水分可以挤。
第一步,试一下编译时优化。
$ gcc -Wall -s -03 tiny.c
$ wc -c a.out
2616 a.out
(似乎没有-o3结果是一样的,还没弄明白gcc的优化原理是啥)
看起来没什么可优化的;
2. 第二步,在即使只有一行的C程序中,也都没有啥可压缩的,使用汇编,避免C自动带来的开销。让程序返回42,也即需要将累加寄存器,eax,赋值为42,然后返回。
; tiny.asm
BITS 32
GLOBAL main
SECTION .text
main:
mov eax, 42;
ret
编译如下:
$ nasm -f elf tiny.asm
$gcc -s tiny.o
$./a.out;echo $?
结果正确,大小 2604
结果影响不大,原因在于main()接口带来的开销,链接器仍然会添加与OS的接口,正是这个接口调用了main()。如何去掉这个接口呢?
如果不使用main,而自定义_start如何?
;tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
mov eax,42
ret
GCC编译:
$nasm -f elf tiny.asm
$gcc -s tiny.o
报错如下:
tiny.o(.text+0x0): multiple definition of `_start'
/usr/lib/crt1.o(.text+0x0): first defined here
/usr/lib/crt1.o(.text+0x36): undefined reference to `main'
GCC手册告诉我们,如果使用参数-nostartfiles,则链接过程中不用标准系统入口,其它正常。
那么编译如下:
$nasm -f elf tiny.asm
$gcc -s -nostartfiles tiny.o
$.a.out; echo $?
Segmentation fault
139
GCC没有报错,但是程序出错了。为什么呢?
原因在于我们把_start作为一个C程序,并希望从它返回。但是,它并不是一个程序,它只是一个符号用来告诉链接器程序的入口地址。如果我们查看的话,会发现栈顶的位置是1,当然不是一个地址。实际上,它是我们的argc的值。栈顶没有返回地址。
那么如何从_start返回呢?调用exit()函数!
准确地说,调用的是_exit()函数。因为,exit()另外还有其他一些功能,但因为我们希望绕开库函数启动代码,我们也得避免库的退出代码,直接使用OS的退出处理方式。
版本三:调用_exit()。_exit()接收一个整数值作为参数,我们需要将42压入栈,然后调用。
;tiny.asm
BITS 32
EXTERN _exit
GLOBAL _start
SECTION .text
_start:
push dword 42
call _exit
编译如下:
$nasm -f elf tiny.asm
$gcc -s -nostartfiles tiny.o
$./a.out;echo $?
成功;大小1340;缩小了一半。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
他的文章
看原图
赞赏
雪币:
留言: