首先来看一段Unix/Linux下的汇编代码:
#PURPOSE: This program finds the maximum number of a
# set of data items.
##VARIABLES: The registers have the following uses:
## %edi - Holds the index of the data item being examined
# %ebx - Largest data item found
# %eax - Current data item
## The following memory locations are used:
## data_items - contains the item data. A 0 is used
# to terminate the data
#
.section .data
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
.section .text
.globl _start
_start:
movl $0, %edi
movl data_items(,%edi,4), %eax
movl %eax, %ebx
start_loop:
cmpl $0, %eax
je loop_exit
incl %edi
movl data_items(,%edi,4), %eax
cmpl %ebx, %eax
jle start_loop
movl %eax, %ebx
jmp start_loop
loop_exit:
movl $1, %eax
int $0x80
被红色注释的部分就是汇编代码,先大概的解释一下这段代码的意思。.section .data表示的是数据段。
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
表示的是定义的一个long型的数组。
.section .text 表示的是代码区段。下面的部分就是真正的代码部分。
.globl _start 表示的定义了一个全局的标识符_start,相当C语言中的标签作用,_start要被链接器使用,相当C语言中的main函数一样,是整个程序的入口。
用%edi寄存器中的值表示数组下标
movl data_items(,%edi,4), %eax
movl %eax, %ebx
表示把取数组data_items中的第%edi的值,存放至寄存器%eax,同时%ebx表示的是数组中的最大值,初始化为数组的第一个元素。
start_loop,loop_exit都属于标签性质。
cmpl $0, %eax
je loop_exit
因为我们定义数组的时候,最后一个元素为0,即0为哨兵,比较%eax和0,如果相等的话,表示程序已经完成,跳转至loop_exit处执行。
incl %edi
movl data_items(,%edi,4), %eax
cmpl %ebx, %eax
否则移动数组下标,取数组的下一个元素值,与%ebx进行比较,若%eax中的值比%ebx值大,则将%eax值给%ebx,保持%ebx维持是数组的最大值。
movl $1, %eax
int $0x80
表示exit退出,并且退出码为1.
上面所讲的就是该段汇编代码的涵义。.
把上述的汇编代码编辑在test.s文档中,然后执行下面的命令:
as test.s -o test.o
ld test.o -o test
Unix/Linux中的可执行文件都是采用的ELF文件格式标准,但是有3中不同的类型:
<1>.可重定位的目标文件(Relocatable Object file)
<2>.可执行文件(Executable file)
<3>.共享库(Shared library)
我们通过分析ELF文件的组成,来了解编译和链接的过程。
我们用Linux下带的工具来查看elf文件,用下述命令即可:
$ readelf -a test.o
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 200 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 40 (bytes)
Number of section headers: 8
Section header string table index: 5
...
上面的就是被解读出来的elf文件信息。
ELF Header中描述了操作系统是UNIX,体系结构是80386。Section Header Table中有8个Section Header,在文件中的位置(或者叫文件地址)从200(0xc8)开始,每个40字节,共320字节,到文件地址0x207结束。这个目标文件没有Program Header。
如果想要从目标文件反汇编的话,可以执行下述命令即可。
$ objdump -d test.o
test.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: bf 00 00 00 00mov $0x0,%edi
5: 8b 04 bd 00 00 00 00mov 0x0(,%edi,4),%eax
c: 89 c3mov %eax,%ebx
0000000e :
e: 83 f8 00cmp $0x0,%eax
11: 74 10je 23
13: 47inc %edi
14: 8b 04 bd 00 00 00 00mov 0x0(,%edi,4),%eax
1b: 39 d8cmp %ebx,%eax
1d: 7e efjle e
1f: 89 c3mov %eax,%ebx
21: eb ebjmp e
00000023 :
23: b8 01 00 00 00mov $0x1,%eax
28: cd 80int $0x80
上面右边的代码就是反汇编后生成的代码,对比一下本身的代码和反汇编后的有何区别,会发现很多该出现地址的地方都是0,这是因为程序只有在链接的时候才会真正的把函数以及变量的地址加载进代码中来。
我们对可执行程序反汇编一下,就知道结果了:
$ objdump -d test
test: file format elf32-i386
Disassembly of section .text:
08048074 <_start>:
8048074: bf 00 00 00 00mov $0x0,%edi
8048079: 8b 04 bd a0 90 04 08mov 0x80490a0(,%edi,4),%eax
8048080: 89 c3mov %eax,%ebx
08048082 :
8048082: 83 f8 00cmp $0x0,%eax
8048085: 74 10je 8048097
8048087: 47inc %edi
8048088: 8b 04 bd a0 90 04 08mov 0x80490a0(,%edi,4),%eax
804808f: 39 d8cmp %ebx,%eax
8048091: 7e efjle 8048082
8048093: 89 c3mov %eax,%ebx
8048095: eb ebjmp 8048082
08048097 :
8048097: b8 01 00 00 00mov $0x1,%eax
804809c: cd 80int $0x80现在反汇编的代码中已经把绝对地址都加在进程序中了。从这个角度来理解编译和链接应该更深入一些,编译只是检查语法错误,对应的地址都是虚拟地址(即被MMU单元截获了地址),链接时,把虚拟地址转化为绝对地址,加载进可执行文件中。