分享
 
 
 

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

王朝system·作者佚名  2006-11-24
窄屏简体版  字體: |||超大  

整理:alert7(alert7)

来源:http://www.xfocus.org

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

作者:breadbox

原文

整理翻译:alert7

主页: 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 : push %ebp

0x80483a1 : mov %esp,%ebp

0x80483a3 : mov $0x2a,%eax

0x80483a8 : jmp 0x80483b0

0x80483aa : lea 0x0(%esi),%esi

0x80483b0 : leave

0x80483b1 : 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环境变量。

所以,那个根本就不是返回地址。

因此,_start要退出,就要调用exit()函数。

事实上,我们实际调用的_exit()函数,因为exit()函数所要做的额外事情太多了,因为我们跳过了

lib库的启动代码,所以我们也可以跳过LIB库的shutdown代码。

好了,再让我们试试。调用_exit()函数,它唯一的参数就是一个整形。所以我们需要push一个数到

堆栈里,然后调用_exit().

(应该这样定义:EXTERN _exit)

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF

; tiny.asm

BITS 32

EXTERN _exit

GLOBAL _start

SECTION .text

_start:

push dword 42

call _exit

EOF

[alert7@redhat]# nasm -f elf tiny.asm

[alert7@redhat]# gcc -Wall -s -nostartfiles tiny.o

[alert7@redhat]# ./a.out ; echo $?

42

yeah~~,成功了,来看看多大

[alert7@redhat]# wc -c a.out

1312 a.out

不错不错,又减少了将近一半,:),有没有其他所我们感兴趣的gcc选项呢?

在-nostartfiles就有一个很另人感兴趣的选项:

-nostdlib

在linking的时候,不使用标准的LIB和启动文件。那些东西都需要自己指定传给

linker.

这个值得研究一下:

[alert7@redhat]# gcc -Wall -s -nostdlib tiny.o

tiny.o: In function `_start':

tiny.o(.text+0x6): undefined reference to `_exit'

collect2: ld returned 1 exit status

_exit()是一个库函数,但是加了-nostdlib 就不能使用了,所以我们必须自己处理,

首先,必须知道在linux下如何制造一个系统调用。

--------------------------------------------------------------------------------

象其他操作系统一样,linux通过系统调用来向程序提供基本的服务。

这包括打开文件,读写文件句柄,等等......

LINUX系统调用接口只有一个指令:int 0x80.所有的系统调用都是通过该接口。

为了制造一个系统调用,eax应该包含一个数字(该数字表明了哪个系统调用),其他寄存器

保存着参数。

假如系统调用使用一个参数,那么参数在ebx中;

假如使用两个参数,那么在ebx,ecx中

假如使用三个,四个,五个参数,那么使用ebx,ecx,esi

从系统调用返回时, eax 将包含了一个返回值。

假如错误发生,eax将是一个负值,它的绝对值表示错误的类型。

在/usr/include/asm/unistd.h中列出了不同的系统调用。

快速看一下将看到exit的系统调用号为1。它只有一个参数,该值会返回给父进程,该值会

被放到ebx中。

好了,现在又可以开工了:)

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF

; tiny.asm

BITS 32

GLOBAL _start

SECTION .text

_start:

mov eax, 1

mov ebx, 42

int 0x80

EOF

[alert7@redhat]# nasm -f elf tiny.asm

[alert7@redhat]# gcc -Wall -s -nostdlib tiny.o

[alert7@redhat]# ./a.out ; echo $?

42

看看大小

[alert7@redhat]# wc -c a.out

416 a.out

现在可真是tiny,呵呵,那么还能不能更小呢?

如何使用更短的指令呢?

看看下面两段汇编代码:

00000000 B801000000 mov eax, 1

00000005 BB2A000000 mov ebx, 42

0000000A CD80 int 0x80

00000000 31C0 xor eax, eax

00000002 40 inc eax

00000003 B32A mov bl, 42

00000005 CD80 int 0x80

很明显从功能上讲是等价的,但是下面一个比上面一个节约了5个字节。

使用gcc大概已经不能减少大小了,下面我们就使用linker--ld

[alert7@redhat]# set -o noclobber && cat > tiny.asm << EOF

; tiny.asm

BITS 32

GLOBAL _start

SECTION .text

_start:

xor eax,eax

inc eax

mov bl,42

int 0x80

EOF

[alert7@redhat]# nasm -f elf tiny.asm

[alert7@redhat]# ld -s tiny.o

[alert7@redhat]# wc -c a.out

412 a.out

小了4个字节,应该是5个字节的,但是另外的一个字节被用来考虑对齐去了。

是否到达了极限了呢,能否更小?

hm.我们的程序代码现在只有7个字节长。是否ELF文件还有405字节的额外的负载呢 ?他们都是

些什么?

使用objdump来看看文件的内容:

[alert7@redhat]# objdump -x a.out | less

a.out: no symbols

a.out: file format elf32-i386

a.out

architecture: i386, flags 0x00000102:

EXEC_P, D_PAGED

start address 0x08048080

Program Header:

LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12

filesz 0x00000087 memsz 0x00000087 flags r-x

Sections:

Idx Name Size VMA LMA File off Algn

0 .text 00000007 08048080 08048080 00000080 2**4

CONTENTS, ALLOC, LOAD, READONLY, CODE

1 .bss 00000001 08049087 08049087 00000087 2**0

CONTENTS

2 .comment 0000001c 00000000 00000000 00000088 2**0

CONTENTS, READONLY

[译者注:在我的机子上多了个.bss节,我想可能是跟ld版本有关。所以在我系统上

演示的一直比原作者上面的大:(

看来要想更小的话,还是可以考虑找个低版本的编译:)

]

如上,完整的.text节为7个字节大,刚好如我们刚才所说。

但是还有其他的节,例如".comment",谁安排它的呢?".comment"节大小为28个字节。

我们现在不知道.comment节到底是什么东西,但是可以大胆的说,它是不必须的。

.comment节在文件偏移量为00000087 (16进制)

我们来看看是什么东西

[alert7@redhat]# objdump -s a.out

a.out: file format elf32-i386

Contents of section .text:

8048080 31c040b3 2acd80 1.@.*..

Contents of section .bss:

8049087 00 .

Contents of section .comment:

0000 00546865 204e6574 77696465 20417373 .The Netwide Ass

0010 656d626c 65722030 2e393800 embler 0.98.

哦,是nasm自己的一段信息,或许我们应该使用gas.......

假如我们:

[alert7@redhat]# set -o noclobber && cat > tiny.s << EOF

.globl _start

.text

_start:

xorl %eax, %eax

incl %eax

movb $42, %bl

int $0x80

EOF

[alert7@redhat]# gcc -s -nostdlib tiny.S

[alert7@redhat]# ./a.out ; echo $?

42

[alert7@redhat]# wc -c a.out

368 a.out

[译者注:在作者机子上这里大小没有变化,但在我的系统上,这里变成了368

(跟作者的机子上一样了),比前面的所以的都要小

]

再用一下objdump,会有些不同:

Sections:

Idx Name Size VMA LMA File off Algn

0 .text 00000007 08048074 08048074 00000074 2**2

CONTENTS, ALLOC, LOAD, READONLY, CODE

1 .data 00000000 0804907c 0804907c 0000007c 2**2

CONTENTS, ALLOC, LOAD, DATA

2 .bss 00000000 0804907c 0804907c 0000007c 2**2

ALLOC

没有了commnet节,但是多了两个无用的节,用来存储不存在的数据。而且那些节居然还是0长度。

他们使文件大小变大。

所以它们都是没有用的,我们如何来去掉它们呢?

我们需要准备一些elf文件格式的知识。虽然我也已经翻译过《ELF文件格式》 ,

在http://www.xfocus.org/上可以找到,但是翻译的很垃圾,早已招人唾骂过了,

所以还是推荐大家看英文原版文档,而且是强烈推荐。

--------------------------------------------------------------------------------

elf文件格式英文文档下载地址:

ftp://tsx.mit.edu/pub/linux/packages/GCC/ELF.doc.tar.gz.

或者 http://www.muppetlabs.com/~breadbox/software/ELF.txt.

基本的,我们需要知道如下知识:

每一个elf文件都是以一个ELF header的结构开始的。该结构为52个字节长,并且包含了一个

信息部分,这些信息部分描述

[1] [2] [3] 下一页

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有