在linux平台上创建超小的ELF可执行文件
2008-04-09 03:59:37来源:互联网 阅读 ()
原文<A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux >
整理翻译:alert7 <alert7@21cn.com>
来源: http://www.xfocus.org/
时间:2001-9-4
------------------------------------------------------------------
前言:
有些时候,文件的大小是很重要的,从这片文章中,也探讨了ELF文件格式内部的工作
情况与LINUX的操作系统。该片文章向我们展示了如何构造一个超小的ELF可执行文件。
文章中给出的这些example都是运行在intel 386体系的LINUX上。其他系统体系上或许也有同样的
效果,但我不感肯定。
我们的汇编代码使用的是Nasm写的,它的风格类似于X86汇编风格。
NASM软件是免费的,可以从下面得到
http://www.web-sites.co.uk/nasm/
--------------------------------------------------------------------------------
看看下面一个很小的程序例子,它唯一做的事情就是返回一个数值到操作系统中。
UNIX系统通常返回0和1,这里我们使用42作为返回值。
[alert7@redhat]# set -o noclobber && cat > tiny.c << EOF
/* tiny.c */
int main(void) { return 42; }
EOF
[alert7@redhat]# gcc -Wall tiny.c
[alert7@redhat]# ./a.out ;echo $?
42
再用gdb看看,这个程序实在很简单吧
[alert7@redhat]# gdb a.out -q
(gdb) disass main
Dump of assembler code for function main:
0x80483a0 <main>: push 雙
0x80483a1 <main 1>: mov %esp,雙
0x80483a3 <main 3>: mov $0x2a,陎
0x80483a8 <main 8>: jmp 0x80483b0 <main 16>
0x80483aa <main 10>: lea 0x0(%esi),%esi
0x80483b0 <main 16>: leave
0x80483b1 <main 17>: ret
看看有多大
[alert7@redhat]# wc -c a.out
11648 a.out
在原作者的机子上3998,在我的rh 2.2.14-5.0上就变成11648,好大啊,我们需要
使它变的更小。
[alert7@redhat]# gcc -Wall -s tiny.c
[alert7@redhat]# ./a.out ;echo $?
42
[alert7@redhat]# wc -c a.out
2960 a.out
现在变成2960,小多了.
gcc -Wall -s tiny.c实际上等价于
gcc -Wall tiny.c
strip a.out 抛弃所有的标号
[alert7@redhat]# wc -c a.out
11648 a.out
[alert7@redhat]# strip a.out
[alert7@redhat]# wc -c a.out
2960 a.out
下一步,我们来进行优化。
[alert7@redhat]# gcc -Wall -s -O3 tiny.c
[alert7@redhat]# wc -c a.out
2944 a.out
我们看到,只比上面的小16个字节,所以以优化指令来减小大小是比较困难的。
很不幸,C程序在编译的时候编译器会增加一些额外的代码,所以接下来我们使用汇编来写程序。
如上一个程序,我们需要返回代码为42,我们只需要把eax设置为42就可以了。程序的
返回状态就是存放在eax中的,从上面一段disass main出来的汇编代码我们也应该知道。
[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL main
SECTION .text
main:
mov eax, 42
ret
EOF
编译并测试
[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s tiny.o
[alert7@redhat]# ./a.out ; echo $?
42
现在看看汇编代码有什么不同,看看它的大小
[alert7@redhat]# wc -c a.out
2892 a.out
这样又减小了(2944-2892)52个字节. 但是,只要我们使用main()接口,就还会有许多额外的代码。
linker还会为我们加一个到OS的接口。事实上就是调用main().所以我们如何来去掉我们不需要的
代码呢。
linker默认使用的实际入口是标号_start.gcc联接时,它会自动包括一个_start的例程,设置argc和argv,
....,最后调用main().
所以让我们来看看,是否可以跳过这个,自己定义_start例程。
[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF
; tiny.asm
BITS 32
GLOBAL _start
SECTION .text
_start:
mov eax, 42
ret
EOF
[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s tiny.o
tiny.o: In function `_start':
tiny.o(.text 0x0): multiple definition of `_start'
/usr/lib/crt1.o(.text 0x0): first defined here
/usr/lib/crt1.o: In function `_start':
/usr/lib/crt1.o(.text 0x18): undefined reference to `main'
collect2: ld returned 1 exit status
如何做才可以编译过去呢?
GCC有一个编译选项--nostartfiles
-nostartfiles
当linking时,不使用标准的启动文件。但是通常是使用的。
我们要的就是这个,再来:
[alert7@redhat]# nasm -f elf tiny.asm
[alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o
[alert7@redhat]# ./a.out ; echo $?
Segmentation fault (core dumped)
139
gcc没有报错,但是程序core dump了,到底发生了什么?
错就错在我们把_start看成了一个C的函数,然后试着从它返回。事实上它根本不是一个函数。
它仅仅是一个标号,它是被linker使用的一个程序入口点。当程序运行,它也就直接被调用。
假如我们来看,将看到在堆栈顶部的变量值为1,它的确非常的不象一个地址。事实上,在
堆栈那位置是我们程序的argc变量,之后是argv数组,包含NULL元素,接下来是envp环境变量。
所以,那个根本就不是返回地址。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
上一篇:perl程序设计(十二)
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash