/***
***by hjs.hust
***hjs.hust@gmail.com
***2012-12-17
***/
参考:http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
相关软件工具:
readelf:readelf显示一个或多个elf格式的目标文件信息,还可以反汇编(具体看手册),可通过各种参数选项来控制显示目标文件的特定信息
nm:主要功能是列出目标文件中的符号,这样可以帮助程序员定位和分析程序和目标文件中的符号信息
objdump:显示文件的特定信息
ar:用于创建、修改、提取归档文件
objcopy:转换文件格式(只能是BFD库中所描述的文件格式)
elf定义:/usr/include/elf.h中有elf的更详细的结构定义,或者man elf
程序:HelloWorld.c
开始:
$ readelf -a hw
只看头部信息:
$ readelf -h hw
开始:
//tiny.c
int main(void){
return 42;
}
执行一下:
$ gcc -Wall tiny.c
$ ./a.out ; echo $? #print return result
看下文件多大:
$ wc -c a.out
8655 a.out
strip可执行文件,编译到汇编语言,不进行汇编和链接
$ gcc -Wall -S tiny.c
$ wc -c a.out
6296 a.out
进行优化
$ gcc -Wall -S -O3 tiny.c
$ wc -c a.out
6296 a.out //没有变化,程序中几乎没有什么可以优化的
下面用汇编了:
; tiny.asm
BITS 64
GLOBAL main
SECTION .text
main:
mov eax, 42
ret
编译:
$ nasm -f elf64 tiny.asm
$ gcc -Wall -s tiny.o
$ wc -c a.out
6296 a.out //天哪,有没变,难道C较之汇编得没有额外开销吗?
问题是,通过使用main()接口我们仍然引进了很大开销。链接器仍然为我们添加一个到OS的接口,真正调用main()的是那个接口。那如果我们不需要它的话该怎么做?链接器真正使用的入口点缺省是名字为_start的符号。当我们使用gcc链接时,它自动包含一个_start例程,该例程将设置argc和argv,以及其他事情,然后调用main()。那么,看看我们能否绕过这一点。定义我们自己的_start例程:
; tiny.asm
BITS 64
GLOBAL _start
SECTION .text
_start:
mov eax, 42
ret
编译:
$ nasm -f elf64 tiny.asm
$ gcc -Wall -s -nostartfiles tiny.o //注意添加这个参数
但是:
$ ./a.out ; echo $?
段错误 (核心已转储)
139
错在哪里?错误在于我们把_start当作它好像是一个C函数,并且试图从它返回。实际上,它根本不是一个函数。它只是目标文件中链接器用来定位程序入口点的一个符号。当我们的程序被激活时,它被直接激活。如果我们去查看一下,将会发现栈顶上是数1,这显然不像是一个地址。事实上,栈顶上是我们程序的argc值。在这之后是argv数组的元素,包括结束时的NULL元素,接着是envp的元素。这就是全部。在栈顶上没有返回地址。那么,_start是如何退出的?它调用了exit()函数!毕竟,这就是它出现的作用。实际上,我说谎了。它真正做的是调用_exit()函数。[标准启动中_start还是调用exit()。]exit()要为进程进行某些任务的结束处理,但是这些任务将不会被启动,因为我们绕过了库的启动代码。所以我们也需要绕过库的结束代码,直接到达操作系统的结束处理。好,让我们再试一下。我们将要调用_exit(),这是一个需要一个整数参数的函数。所以我们需要做的就是把那个数压到栈上并调用该函数。(我们还需要声明_exit()为外
部)下面是我们的汇编:
; tiny.asm
BITS 32
EXTERN _exit
GLOBAL _start
SECTION .text
_start:
push dword 42
call _exit
好吧,这在32位机里面是正确的,但到了64咋就不行了呢(返回232),不过不要紧,下面还有。
gcc还有什么别的有意思的选项吗?在文档中紧接着-nostartfiles的,很是显眼:-nostdlib
Don't use the standard system libraries and startup files whenlinking. Only the files you specify will be passed to the linker.
$ gcc -Wall -s -nostdlib tiny.o
tiny.o:在函数‘_start’中:
tiny.asm:(.text+0x3):对‘_exit’未定义的引用
collect2: 错误: ld 返回 1
是的..._exit()毕竟是一个库函数。它必须要被填充。好吧。但是肯定,我们并不需要libc的帮助来结束一个程序,不是吗?是的,我们不需要。如果我们愿意抛弃所有可移植性要求,我们可以退出程序而不需要和任何其他东西链接。然而,首先我们需要了解如何在Linux下进行系统调用。Linux,像大多数操作系统一样,通过系统调用对它支持的程序提供基本的必需功能。这包括打开文件,读写文件句柄——当然,也包括结束一个进程。Linux系统调用接口是一条指令:int 0x80。所有的系统调用都通过这个中断进行。要进行一个系统调用,eax应当包含一个数来指明那个系统调用被调用,并且其他寄存器用于传递参数,如果有的话。如果系统调用需要一个参数,它将在ebx里;两个参数的系统调将使用ebx和ecx。类似的,edx,esi,和edi将分别