1.编译、链接相关的文件
测试代码如下:
/*main.c*/
int add(int a_, int b_);
extern int global_extern_int;
int global_int = 3;
int main()
{
static int a = 19;
global_int = 5;
int rtv = 0;
rtv = add(global_int, global_extern_int);
return rtv;
}
/*add.c*/
int global_extern_int = 2;
int add(int a_, int b_)
{
return a_+ b_;
}
编译、链接(32位),
gcc -g -fno-pie -no-pie -m32 -c add.c
gcc -g -fno-pie -no-pie -m32 -c main.c
gcc -g -fno-pie -no-pie -m32 -o main main.o add.o
生成的应用程序main的大小为16K。
lyf@lyf:~/test$ ls -hl
total 32K
-rw-r--r-- 1 lyf lyf 74 Dec 2 10:22 add.c
-rw-r--r-- 1 lyf lyf 2.0K Dec 2 10:42 add.o
-rwxr-xr-x 1 lyf lyf 16K Dec 2 13:54 main
-rw-r--r-- 1 lyf lyf 212 Dec 2 11:33 main.c
-rw-r--r-- 1 lyf lyf 2.3K Dec 2 11:34 main.o
大部分的现代操作系统都使用ASCII标准来表示文本字符,使用单字节的整数值来表示每个字符,如下所示,程序的源文件为ASCII文件,这种只由ASCII字符构成的文件称为文本文件,除了文本文件,其他的都是二进制文件。
lyf@lyf:~/test$ file main.c
main.c: C source, ASCII text
lyf@lyf:~/test$ hexdump -C ./main.c
00000000 69 6e 74 20 61 64 64 28 69 6e 74 20 61 5f 2c 20 |int add(int a_, |
00000010 69 6e 74 20 62 5f 29 3b 0a 65 78 74 65 72 6e 20 |int b_);.extern |
00000020 69 6e 74 20 67 6c 6f 62 61 6c 5f 65 78 74 65 72 |int global_exter|
00000030 6e 5f 69 6e 74 3b 0a 69 6e 74 20 67 6c 6f 62 61 |n_int;.int globa|
00000040 6c 5f 69 6e 74 20 3d 20 33 3b 0a 0a 69 6e 74 20 |l_int = 3;..int |
00000050 6d 61 69 6e 28 29 0a 7b 0a 20 20 20 20 73 74 61 |main().{. sta|
00000060 74 69 63 20 69 6e 74 20 61 20 3d 20 31 39 3b 0a |tic int a = 19;.|
00000070 20 20 20 20 67 6c 6f 62 61 6c 5f 69 6e 74 20 3d | global_int =|
00000080 20 35 3b 0a 20 20 20 20 69 6e 74 20 72 74 76 20 | 5;. int rtv |
00000090 3d 20 30 3b 0a 20 20 20 20 72 74 76 20 3d 20 61 |= 0;. rtv = a|
000000a0 64 64 28 67 6c 6f 62 61 6c 5f 69 6e 74 2c 20 67 |dd(global_int, g|
000000b0 6c 6f 62 61 6c 5f 65 78 74 65 72 6e 5f 69 6e 74 |lobal_extern_int|
000000c0 29 3b 0a 20 20 20 20 72 65 74 75 72 6e 20 72 74 |);. return rt|
000000d0 76 3b 0a 7d |v;.}|
000000d4
lyf@lyf:~/test$ file main.o
main.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), with debug_info, not stripped
lyf@lyf:~/test$ file add.o
add.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), with debug_info, not stripped
lyf@lyf:~/test$ file main
main: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=5b8fb49e162dbf05605114331a1fdfb524aefbbf, for GNU/Linux 3.2.0, with debug_info, not stripped
- .o的文件格式为ELF relocatable,可重定位的;
- 应用程序main的文件格式为ELF executable,可执行的;
- 编译生成的.o文件以及链接后最终生成的main应用程序,从本质上来讲依然是文件(Linux上遵循ELF这种文件格式);
- 应用程序main默认是动态链接的,大小为16K;
默认是动态链接的,那么链接了什么动态库?发现是libc。
lyf@lyf:~/test$ readelf -d ./main
Dynamic section at offset 0x2f14 contains 24 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libc.so.6]
......
执行应用程序,
lyf@lyf:~/test$ ./main
lyf@lyf:~/test$ echo $?
7
- 在shell中执行./main,shell也是一个应用程序,当执行
./main
时,会在当前目录下寻找名为main的可执行程序,然后解析命令行参数,本例中没有,除此之外,还会将shell中的一些环境变量引入到main程序的执行过程中;接着读取main文件(二进制文件),将ELF可执行文件加载到内存中(虚拟内存,一个程序运行时对应一个进程,现代操作系统进程所操作的内存地址为虚拟地址),如果是链接了动态库,还会将动态库也加载到内存中(对于PIC,Position Independent Code,只加载一份,后续介绍); - 执行完成后,返回值为7,符合程序的预期;
改main为静态链接,
lyf@lyf:~/test$ gcc -g -fno-pie -no-pie -m32 -static -o main main.o add.o
静态链接后,main的大小为731K,大了很多。
lyf@lyf:~/test$ ls -hl
total 748K
-rw-r--r-- 1 lyf lyf 74 Dec 2 10:22 add.c
-rw-r--r-- 1 lyf lyf 2.0K Dec 2 10:42 add.o
-rwxr-xr-x 1 lyf lyf 731K Dec 2 13:51 main
-rw-r--r-- 1 lyf lyf 212 Dec 2 11:33 main.c
-rw-r--r-- 1 lyf lyf 2.3K Dec 2 11:34 main.o
2.内存中的程序
如下图所示,
- 通过编译,生成的文件为ELF Relocatable object(.o)文件;
- 通过重定位、链接等,生成ELF Executable文件;
- 链接器对编译生成.o文件采用相似段合并的方法最终生成executable(其中很重要的一步就是后面要讲的符号重定位)。
如前面所介绍的,编译链接后的硬盘中的main应用程序是ELF格式的文件,通过shell执行main后,会加载到内存中。
- 进程的内存空间中的一部分空间是使用ELF中的部分段来填充的,例如对于main的ELF中的.init,.text,.rodata等段会放在只读区,而.data,.bss等段会放在可读可写的区域。代码段及只读数据等肯定要设计成是只读的,否则程序指令能被随意修改会产生安全问题。数据区显而易见是需要可读写的。
- 对于.bss段,看过别人有个精妙的解释,可以把它叫做Better Save Space段,主要是为了减少elf文件不必要的文件大小的增加,例如代码中定义了一个全局4096字节长度的char数组,均初始化为0,这个变量就没必要放在数据段,然后占用4096字节都填充为0。我们只需要知道这个数据结构的组成这些信息即可,当后续加载到内存中时,可以再为其分配空间。
3.什么是符号(Symbol)
符号对应的就是地址。变量的符号是变量的地址,函数的符号是函数的地址。链接过程的本质就是把多个不同的目标(.o)文件之间相互“粘”在一起,是解决目标文件之间对函数和变量地址的引用。
关于ELF文件格式的细节,这里不做介绍,网上很多资料。应用程序main通过编译先有add.o和main.o,然后再链接生成main。因此接下来先分析.o中相关的符号(Symbol),然后再分析应用程序main中对相关符号是如何处理的。
对于add.o,其中定义了一个全局变量global_extern_int
和add
函数,
lyf@lyf:~/test$ readelf -s add.o
Symbol table '.symtab' contains 10 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS add.c
2: 00000000 0 SECTION LOCAL DEFAULT 1 .text
3: 00000000 0 SECTION LOCAL DEFAULT 4 .debug_info
4: 00000000 0 SECTION LOCAL DEFAULT 6 .debug_abbrev
5: 00000000 0 SECTION LOCAL DEFAULT 9 .debug_line
6: 00000000 0 SECTION LOCAL DEFAULT 11 .debug_str
7: 00000000 0 SECTION LOCAL DEFAULT 12 .debug_line_str
8: 00000000 4 OBJECT GLOBAL DEFAULT 2 global_extern_int
9: 00000000 13 FUNC GLOBAL DEFAULT 1 add
从上述输出我们可以看出来,全局变量global_extern_int
和add
函数都是符号(Symbol),且都是全局的(GLOBAL)。其中全局变量global_extern_int的Ndx为2(.data),add函数的Ndx为1(.text),对应代码段和数据段。函数的TYPE为FUNC,变量的TYPE为OBJECT。
下面是add.o中包含的所有的段(Section)的信息,符号表中的Ndx对应段表中的Nr,表示该符号属于哪个段(Section),
lyf@lyf:~/test$ readelf -S add.o
There are 20 section headers, starting at offset 0x4a4:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .text PROGBITS 00000000 000034 00000d 00 AX 0 0 1
[ 2] .data PROGBITS 00000000 000044 000004 00 WA 0 0 4
[ 3] .bss NOBITS 00000000 000048 000000 00 WA 0 0 1
[ 4] .debug_info PROGBITS 00000000 000048 00006f 00 0 0 1
[ 5] .rel.debug_info REL 00000000 000374 000048 08 I 17 4 4
[ 6] .debug_abbrev PROGBITS 00000000 0000b7 000060 00 0 0 1
[ 7] .debug_aranges PROGBITS 00000000 000117 000020 00 0 0 1
[ 8] .rel.debug_a[...] REL 00000000 0003bc 000010 08 I 17 7 4
[ 9] .debug_line PROGBITS 00000000 000137 00004b 00 0 0 1
[10] .rel.debug_line REL 00000000 0003cc 000020 08 I 17 9 4
[11] .debug_str PROGBITS 00000000 000182 00009c 01 MS 0 0 1
[12] .debug_line_str PROGBITS 00000000 00021e 000030 01 MS 0 0 1
[13] .comment PROGBITS 00000000 00024e 00002c 01 MS 0 0 1
[14] .note.GNU-stack PROGBITS 00000000 00027a 000000 00 0 0 1
[15] .eh_frame PROGBITS 00000000 00027c 000038 00 A 0 0 4
[16] .rel.eh_frame REL 00000000 0003ec 000008 08 I 17 15 4
[17] .symtab SYMTAB 00000000 0002b4 0000a0 10 18 8 4
[18] .strtab STRTAB 00000000 000354 00001d 00 0 0 1
[19] .shstrtab STRTAB 00000000 0003f4 0000af 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), p (processor specific)
对于main.o而言,引用了add.o中的函数和全局变量,同时自己定义了全局变量global_int
及局部变量rtv
,那么main.o的符号表,
lyf@lyf:~/test$ readelf -s main.o
Symbol table '.symtab' contains 13 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS main.c
2: 00000000 0 SECTION LOCAL DEFAULT 1 .text
3: 00000004 4 OBJECT LOCAL DEFAULT 3 a.0
4: 00000000 0 SECTION LOCAL DEFAULT 5 .debug_info
5: 00000000 0 SECTION LOCAL DEFAULT 7 .debug_abbrev
6: 00000000 0 SECTION LOCAL DEFAULT 10 .debug_line
7: 00000000 0 SECTION LOCAL DEFAULT 12 .debug_str
8: 00000000 0 SECTION LOCAL DEFAULT 13 .debug_line_str
9: 00000000 4 OBJECT GLOBAL DEFAULT 3 global_int
10: 00000000 72 FUNC GLOBAL DEFAULT 1 main
11: 00000000 0 NOTYPE GLOBAL DEFAULT UND global_extern_int
12: 00000000 0 NOTYPE GLOBAL DEFAULT UND add
如上所示,
- 局部变量(rtv)不是符号,并不在符号表中;
- 静态变量,全局变量,函数都是符号,静态变量的符号的作用域是本地LOCAL;
- global_extern_int和add引用了add.o中的符号,因此其类型为NOTYPE,因为在链接前还不知道具体的类型。
4.符号的重定位
main.o中对符号的引用是如何进行的?也就是如何获取到所需要的符号的真实地址呢?
首先,因为main.o要使用外部的符号(add.o中的),因此在main.o中有一个重定位段(.rel.text),告诉链接器该如何去做符号重定位。下面重定位段中的Offset指的是相对于.text段起始位置的偏移量。
lyf@lyf:~/test$ readelf -r ./main.o
Relocation section '.rel.text' at offset 0x484 contains 4 entries:
Offset Info Type Sym.Value Sym. Name
00000013 00000901 R_386_32 00000000 global_int
00000024 00000b01 R_386_32 00000000 global_extern_int
00000029 00000901 R_386_32 00000000 global_int
00000033 00000c02 R_386_PC32 00000000 add
......
- 对于global_int,需要在.text段中0x13和0x29的位置去做重定位,重定位的方式是R_386_32;
- 对于global_extern_int,需要在.text段中0x24的位置去做重定位,重定位的方式是R_386_32;
- 对于add函数,需要在.text段中0x33的位置去做重定位,重定位的方式是R_386_PC32;
Name | Value | Field | Calculation |
---|---|---|---|
R_386_NONE | 0 | None | None |
R_386_32 | 2 | dword | S + A |
R_386_PC32 | 1 | dword | S + A – P |
R_386_GOT32 | 3 | dword | G + A |
R_386_PLT32 | 4 | dword | L + A – P |
R_386_COPY | 5 | None | Value is copied directly from shared object |
R_386_GLOB_DAT | 6 | dword | S |
R_386_JMP_SLOT | 7 | dword | S |
R_386_RELATIVE | 8 | dword | B + A |
R_386_GOTOFF | 9 | dword | S + A – GOT |
R_386_GOTPC | 10 | dword | GOT + A – P |
R_386_32PLT | 11 | dword | L + A |
R_386_16 | 20 | word | S + A |
R_386_PC16 | 21 | word | S + A – P |
R_386_8 | 22 | byte | S + A |
R_386_PC8 | 23 | byte | S + A – P |
R_386_SIZE32 | 38 | dword | z + A |
其中,R_386_32和R_386_PC32的重定位计算方式中,
- A为保存在修正位置的值;
- S为符号的实际地址,这个地址在链接前是没有的,只有通过链接,将所有的.o的相似段进行合并,生成可执行文件,此时所有符号的地址都已经确定好了(此时所有符号,指令的地址都是真实的,也就是对应运行时进程的虚拟地址)。但是这个时候合并的很多.o的.text代码段中的部分符号地址依然不是最终的确定地址,需要去用上面的公式去做重定位。是个反过来的过程,先把所有符号的地址都定下来后,然后再去修改.text代码段中部分的符号地址,然后才生成可以正确执行的可执行程序。
- P为被修正的位置,后面通过例子解释会很容易理解。
下面是main.o中对应代码段的反汇编,
lyf@lyf:~/test$ objdump -drS -Mintel ./main.o
./main.o: file format elf32-i386
Disassembly of section .text:
00000000 :
int add(int a_, int b_);
extern int global_extern_int;
int global_int = 3;
int main()
{
0: 8d 4c 24 04 lea ecx,[esp+0x4]
4: 83 e4 f0 and esp,0xfffffff0
7: ff 71 fc push DWORD PTR [ecx-0x4]
a: 55 push ebp
b: 89 e5 mov ebp,esp
d: 51 push ecx
e: 83 ec 14 sub esp,0x14
static int a = 19;
global_int = 5;
11: c7 05 00 00 00 00 05 mov DWORD PTR ds:0x0,0x5
18: 00 00 00
13: R_386_32 global_int
int rtv = 0;
1b: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
rtv = add(global_int, global_extern_int);
22: 8b 15 00 00 00 00 mov edx,DWORD PTR ds:0x0
24: R_386_32 global_extern_int
28: a1 00 00 00 00 mov eax,ds:0x0
29: R_386_32 global_int
2d: 83 ec 08 sub esp,0x8
30: 52 push edx
31: 50 push eax
32: e8 fc ff ff ff call 33
33: R_386_PC32 add
37: 83 c4 10 add esp,0x10
3a: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
return rtv;
3d: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
40: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
43: c9 leave
44: 8d 61 fc lea esp,[ecx-0x4]
47: c3 ret
- 结合main.o的重定向表.rel.text,在.text偏移0x13的位置上需要去做个R_386_32类型的重定向。也就是
c7 05 00 00 00 00 05
,对应的代码为global_int=5
,0x13对应的是4个字节的global_int对应的地址,但是此时是00 00 00 00
,所以此时对应的A为0; - 在.text偏移0x33的位置上需要去做个R_386_PC32类型的重定向,也就是
e8 fc ff ff ff
,其中0xe8是操作码,查找INTEL IA-32指令手册,这条指令是一个近址相对位移调用指令,在e8后面跟着的4个字节是被调用函数相对于调用指令下一条指令的偏移量。小端,fc ff ff ff
对应ff ff ff fc
,对应-4,调用指令下一条指令的地址为0x37,0x37-4=0x33
,被调用函数的地址是0x33
,又回到了原地?此时A的值为-4;
接下来,看看粘和后的main应用程序中对应符号的地址,对应公式中对应的S,
lyf@lyf:~/test$ readelf -s ./main
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (2)
2: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
3: 0804a004 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
Symbol table '.symtab' contains 40 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS crt1.o
2: 080481cc 32 OBJECT LOCAL DEFAULT 3 __abi_tag
3: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
4: 080490b0 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
5: 080490f0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
6: 08049130 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
7: 0804c024 1 OBJECT LOCAL DEFAULT 24 completed.0
8: 0804bf10 0 OBJECT LOCAL DEFAULT 19 __do_global_dtor[...]
9: 08049160 0 FUNC LOCAL DEFAULT 13 frame_dummy
10: 0804bf0c 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_in[...]
11: 00000000 0 FILE LOCAL DEFAULT ABS main.c
12: 0804c01c 4 OBJECT LOCAL DEFAULT 23 a.0
13: 00000000 0 FILE LOCAL DEFAULT ABS add.c
14: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
15: 0804a0ec 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
16: 00000000 0 FILE LOCAL DEFAULT ABS
17: 0804bf14 0 OBJECT LOCAL DEFAULT 20 _DYNAMIC
18: 0804a008 0 NOTYPE LOCAL DEFAULT 16 __GNU_EH_FRAME_HDR
19: 0804c000 0 OBJECT LOCAL DEFAULT 22 _GLOBAL_OFFSET_TABLE_
20: 0804c020 4 OBJECT GLOBAL DEFAULT 23 global_extern_int
21: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_mai[...]
22: 080490a0 4 FUNC GLOBAL HIDDEN 13 __x86.get_pc_thunk.bx
23: 0804c010 0 NOTYPE WEAK DEFAULT 23 data_start
24: 080491ae 13 FUNC GLOBAL DEFAULT 13 add
25: 0804c018 4 OBJECT GLOBAL DEFAULT 23 global_int
26: 0804c024 0 NOTYPE GLOBAL DEFAULT 23 _edata
27: 080491bc 0 FUNC GLOBAL HIDDEN 14 _fini
28: 0804c010 0 NOTYPE GLOBAL DEFAULT 23 __data_start
29: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
30: 0804c014 0 OBJECT GLOBAL HIDDEN 23 __dso_handle
31: 0804a004 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
32: 0804c028 0 NOTYPE GLOBAL DEFAULT 24 _end
33: 08049090 5 FUNC GLOBAL HIDDEN 13 _dl_relocate_sta[...]
34: 08049050 49 FUNC GLOBAL DEFAULT 13 _start
35: 0804a000 4 OBJECT GLOBAL DEFAULT 15 _fp_hw
36: 0804c024 0 NOTYPE GLOBAL DEFAULT 24 __bss_start
37: 08049166 72 FUNC GLOBAL DEFAULT 13 main
38: 0804c024 0 OBJECT GLOBAL HIDDEN 23 __TMC_END__
39: 08049000 0 FUNC GLOBAL HIDDEN 11 _init
- main的地址为0x08049166,add函数的地址为0x080491ae,global_extern_int的地址为0x0804c020,global_int的地址为0x0804c018;
- main.o的在.text偏移0x13的位置上的R_386_32类型的重定向,global_int对应的真实地址为0x0804c018,S=0x0804c018,A为0,则main.o中.text的0x13的位置上的重定位的结果为S+A=0x0804c018;
- main.o在.text偏移0x33的位置上的R_386_PC32类型的重定向,add的实际地址为0x080491ae,则S=0x080491ae,A为-4,P为修正处的位置即main+0x33,即
0x08049166+0x33=0x8049199
,则S+A-P=0x080491ae+(-4)-0x8049199=0x11
,则重定位后的结果为0x11
; - 在main中还有一些其他的函数符号,例如,
__libc_start_main@GLIBC_2.34
,_init
,_fini
,可以把他们理解成图一中的System code,其实虽然main函数是C/C++的入口函数,但是它也是被其他的system code去调用,下面从main的反汇编代码中也能看出来。
下面来确认下,最终重定位的结果是否正确,查看应用程序main的反汇编代码,
lyf@lyf:~/test$ objdump -d -Mintel ./main
./main: file format elf32-i386
Disassembly of section .init:
08049000 :
8049000: f3 0f 1e fb endbr32
8049004: 53 push ebx
8049005: 83 ec 08 sub esp,0x8
8049008: e8 93 00 00 00 call 80490a0
804900d: 81 c3 f3 2f 00 00 add ebx,0x2ff3
8049013: 8b 83 fc ff ff ff mov eax,DWORD PTR [ebx-0x4]
8049019: 85 c0 test eax,eax
804901b: 74 02 je 804901f
804901d: ff d0 call eax
804901f: 83 c4 08 add esp,0x8
8049022: 5b pop ebx
8049023: c3 ret
Disassembly of section .plt:
08049030 :
8049030: ff 35 04 c0 04 08 push DWORD PTR ds:0x804c004
8049036: ff 25 08 c0 04 08 jmp DWORD PTR ds:0x804c008
804903c: 00 00 add BYTE PTR [eax],al
...
08049040 :
8049040: ff 25 0c c0 04 08 jmp DWORD PTR ds:0x804c00c
8049046: 68 00 00 00 00 push 0x0
804904b: e9 e0 ff ff ff jmp 8049030
Disassembly of section .text:
08049050 :
8049050: f3 0f 1e fb endbr32
8049054: 31 ed xor ebp,ebp
8049056: 5e pop esi
8049057: 89 e1 mov ecx,esp
8049059: 83 e4 f0 and esp,0xfffffff0
804905c: 50 push eax
804905d: 54 push esp
804905e: 52 push edx
804905f: e8 19 00 00 00 call 804907d
8049064: 81 c3 9c 2f 00 00 add ebx,0x2f9c
804906a: 6a 00 push 0x0
804906c: 6a 00 push 0x0
804906e: 51 push ecx
804906f: 56 push esi
8049070: c7 c0 66 91 04 08 mov eax,0x8049166
8049076: 50 push eax
8049077: e8 c4 ff ff ff call 8049040
804907c: f4 hlt
804907d: 8b 1c 24 mov ebx,DWORD PTR [esp]
8049080: c3 ret
8049081: 66 90 xchg ax,ax
8049083: 66 90 xchg ax,ax
8049085: 66 90 xchg ax,ax
8049087: 66 90 xchg ax,ax
8049089: 66 90 xchg ax,ax
804908b: 66 90 xchg ax,ax
804908d: 66 90 xchg ax,ax
804908f: 90 nop
08049166 :
8049166: 8d 4c 24 04 lea ecx,[esp+0x4]
804916a: 83 e4 f0 and esp,0xfffffff0
804916d: ff 71 fc push DWORD PTR [ecx-0x4]
8049170: 55 push ebp
8049171: 89 e5 mov ebp,esp
8049173: 51 push ecx
8049174: 83 ec 14 sub esp,0x14
8049177: c7 05 18 c0 04 08 05 mov DWORD PTR ds:0x804c018,0x5
804917e: 00 00 00
8049181: c7 45 f4 00 00 00 00 mov DWORD PTR [ebp-0xc],0x0
8049188: 8b 15 20 c0 04 08 mov edx,DWORD PTR ds:0x804c020
804918e: a1 18 c0 04 08 mov eax,ds:0x804c018
8049193: 83 ec 08 sub esp,0x8
8049196: 52 push edx
8049197: 50 push eax
8049198: e8 11 00 00 00 call 80491ae
804919d: 83 c4 10 add esp,0x10
80491a0: 89 45 f4 mov DWORD PTR [ebp-0xc],eax
80491a3: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc]
80491a6: 8b 4d fc mov ecx,DWORD PTR [ebp-0x4]
80491a9: c9 leave
80491aa: 8d 61 fc lea esp,[ecx-0x4]
80491ad: c3 ret
080491ae :
80491ae: 55 push ebp
80491af: 89 e5 mov ebp,esp
80491b1: 8b 55 08 mov edx,DWORD PTR [ebp+0x8]
80491b4: 8b 45 0c mov eax,DWORD PTR [ebp+0xc]
80491b7: 01 d0 add eax,edx
80491b9: 5d pop ebp
80491ba: c3 ret
Disassembly of section .fini:
080491bc :
80491bc: f3 0f 1e fb endbr32
80491c0: 53 push ebx
80491c1: 83 ec 08 sub esp,0x8
80491c4: e8 d7 fe ff ff call 80490a0
80491c9: 81 c3 37 2e 00 00 add ebx,0x2e37
80491cf: 83 c4 08 add esp,0x8
80491d2: 5b pop ebx
80491d3: c3 ret
- 8049177行,修正后的地址为
0x804c018
,和上述计算结果相同; - 8049198行,修正后的结果为
e8 11 00 00 00
,小端,即0x11
,和上述计算结果相同。
结合下图和main的反汇编代码,其中__libc_start_main的函数签名如下所示,main函数是被其调用的,
int __libc_start_main(int *(main) (int, char * *, char * *), int argc, char * * ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (* stack_end));
_start
是真正的函数入口,会去调用__libc_start_main
;__libc_start_main
会去调用__init
,main
,__fini
等函数;8049070: c7 c0 66 91 04 08 mov eax,0x8049166
将main函数的指针(0x8049166)放入寄存器,入栈,8049077: e8 c4 ff ff ff call 8049040
调用__libc_start_main
,接着就开始执行用户层代码。- 关于
__libc_start_main@plt
后面在介绍动态库和PIC(地址无关代码)时会介绍(本文main默认动态链接了libc库),这里可以理解成调用__libc_start_main
。