在linux平台上创建超小的ELF可执行文件

2008-04-09 03:59:37来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

作者:breadbox <breadbox@muppetlabs.com>
原文<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程序设计(十二)

下一篇:用Delphi设计自己的代理服务器