1. Linux下的二进制可执行文件。
如果世界很简单,那么二进制可执行文件也应该很简单,只包括CPU要执行的指令就可以了。可惜,世界并不简单……。Linux下的二进制可执行文件(以下简称可执行文件),也并不是只包括了指令,还包括了很多其他的信息,比如,执行需要的数据,重定位信息,调试信息,动态链接信息,等等。 所有这些信息都按照一个预定的格式组织在一个可执行文件里面。Linux下叫ELF可执行文件。
举一个最简单的例子,假设有下面这个程序:
int main()
{
return 0;
}
这个连“Hello World”都不能打印的程序,自然是什么都做不了。当然,如果只是把这个文件保存为文本文件,是无论如何也执行不了得。还需要两个重要的步骤:编译和链接,才能把它转换为可执行的ELF格式。
先来看看编译,也就是把C语言翻译成机器语言的过程。很简单,用下面的命令:
gcc -c test.c -o test.o <假设源文件名为test.c>
-c 参数告诉gcc,我们只需要编译这个文件,不需要连接。这样就会生成一个test.o文件。这个文件包含了上面源程序翻译后的机器指令和其他一些信息。这个test.o也属于ELF格式。如何看test.o里面的内容,可以用objdump命令:
objdump -x test.o
会有类似下面的输出:
[html] view plaincopy
- test.o: file format elf32-i386
- test.o
- architecture: i386, flags 0x00000010:
- HAS_SYMS
- start address 0x00000000
- Sections:
- Idx Name Size VMA LMA File off Algn
- 0 .text 0000000a 00000000 00000000 00000034 2**
- CONTENTS, ALLOC, LOAD, READONLY, CODE
- 1 .data 00000000 00000000 00000000 00000040 2**2
- CONTENTS, ALLOC, LOAD, DATA
- 2 .bss 00000000 00000000 00000000 00000040 2**2
- ALLOC
- 3 .comment 0000002b 00000000 00000000 00000040 2**0
- CONTENTS, READONLY
- 4 .note.GNU-stack 00000000 00000000 00000000 0000006b 2**0
- CONTENTS, READONLY
- SYMBOL TABLE:
- 00000000 l df *ABS* 00000000 test.c
- 00000000 l d .text 00000000 .text
- 00000000 l d .data 00000000 .data
- 00000000 l d .bss 00000000 .bss
- 00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
- 00000000 l d .comment 00000000 .comment
- 00000000 g F .text 0000000a main
test.o 主要包含了文件头和节。"节“是ELF文件的重要组成部分,一个节就是某一类型的数据。objdump的-x参数会打印出test.o中所有的节,也就是上面的"Sections". 其中.text节包含了可执行代码,.data节包含了已经初始化的数据,.bss节包含了未初始化数据。其他的节先忽略掉(其实是因为我也了解不多⋯⋯)
如果要看看test.o是不是包含源文件的编译结果, 可以将其反汇编查看。使用objdump -d 命令。 默认情况下,该命令只返回目标文件的可执行部分,在这里就是.text节。 objdump -d test.o 得到的结果如下:
[plain] view plaincopy
- test.o: file format elf32-i386
- Disassembly of section .text:
- 00000000 <main>:
- 0: 55 push %ebp
- 1: 89 e5 mov %esp,%ebp
- 3: b8 00 00 00 00 mov $0x0,%eax
- 8: 5d pop %ebp
- 9: c3 ret
可以看见这里就是一些栈的操作,没有做什么事情。当然,源码里面确实也没做什么事情。这个.o文件还不能执行,还需要经过链接。通常,我们可以用gcc一步完成编译链接过程,也就是我们最常用的: gcc test.c -o test
如果再次用objdump -d 反编译生成的test文件:
objdump -d test
额……会发现多了一堆东西。这是因为,c程序通常都是链接到c运行库的。在main函数执行前,c运行库需要初始化一些东西。这也说明,main()并不是程序的真正入口点。真正的入口点可以用objdump -f 查看test的文件头:
[plain] view plaincopy
- test: file format elf32-i386
- architecture: i386, flags 0x00000112:
- EXEC_P, HAS_SYMS, D_PAGED
- start address 0x080482e0
|