最近面试,技术经理问我汇编到main函数步骤是什么,当时我是一脸疑惑,what?main函数就不是C语言调用第一个函数吗?后面我自己查了相关资料,才发现自己too young too navie.
很多人只知道main函数是c语言的第一个调用函数,其实不是,下面用个例子说明
在开始讲解之前,我先科普一下知识,平时我们编译.c文件是,一般分为三个步骤:
第一步生成汇编代码
第二部生成目标文件
第三步生成可执行文件
[test@localhost Assembler]$ gcc -S main.c
[test@localhost Assembler]$ gcc -c main.s
[test@localhost Assembler]$ gcc main.o
-S生成汇编代码,-c生成目标文件,gcc生成可执行文件。
好了,下面我们来写个程序!
创建一个test.c文件,里面写以下代码
#include
#include
嘻嘻,没错,我故意没写main函数,看看会编译后会发生什么结果!
这个编译结果有两个意思,第一个是在Scrt1.o文件里调用了_start函数。第二个意思是在_start函数中没调用main函数。这说明main函数并不是我们所说的C语言第一个调用的函数,下面用更具体的证据证明。
下面我们查看Scrt1.o里面调用了什么:
D是初始化data段,U是undefined的含义,说明main函数在该文件中未定义,但是T _start代表存在代码区 _start,并且在_start里面调用了main函数,进一步证实了我们的猜测。
如果小伙伴想深究下去main函数怎么调用,请继续往下看!
我们先修改main.c文件代码,加上main函数
#include
#include
int main(){
}
然后我们编译是没有报错的,然后我们反编译该代码
yates@yates-HLY-WX9XX:~/software/c$ objdump -d test
test: file format elf64-x86-64
Disassembly of section .init:
0000000000001000 <_init>:
1000:f3 0f 1e fa endbr64
1004:48 83 ec 08 sub $0x8,%rsp
1008:48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8
100f:48 85 c0 test %rax,%rax
1012:74 02 je 1016 <_init>
1014:ff d0 callq *%rax
1016:48 83 c4 08 add $0x8,%rsp
101a:c3 retq
Disassembly of section .plt:
0000000000001020 <.plt>:
1020:ff 35 a2 2f 00 00 pushq 0x2fa2(%rip) # 3fc8
1026:f2 ff 25 a3 2f 00 00 bnd jmpq *0x2fa3(%rip) # 3fd0
102d:0f 1f 00 nopl (%rax)
Disassembly of section .plt.got:
0000000000001030 <__cxa_finalize>:
1030:f3 0f 1e fa endbr64
1034:f2 ff 25 bd 2f 00 00 bnd jmpq *0x2fbd(%rip) # 3ff8
103b:0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
Disassembly of section .text:
0000000000001040 <_start>:
1040:f3 0f 1e fa endbr64
1044:31 ed xor %ebp,%ebp
1046:49 89 d1 mov %rdx,%r9
1049:5e pop %rsi
104a:48 89 e2 mov %rsp,%rdx
104d:48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1051:50 push %rax
1052:54 push %rsp
1053:4c 8d 05 56 01 00 00 lea 0x156(%rip),%r8 # 11b0
105a:48 8d 0d df 00 00 00 lea 0xdf(%rip),%rcx # 1140
1061:48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 1129
1068:ff 15 72 2f 00 00 callq *0x2f72(%rip) # 3fe0
106e:f4 hlt
106f:90 nop
0000000000001070 :
1070:48 8d 3d 99 2f 00 00 lea 0x2f99(%rip),%rdi # 4010
1077:48 8d 05 92 2f 00 00 lea 0x2f92(%rip),%rax # 4010
107e:48 39 f8 cmp %rdi,%rax
1081:74 15 je 1098
1083:48 8b 05 4e 2f 00 00 mov 0x2f4e(%rip),%rax # 3fd8
108a:48 85 c0 test %rax,%rax
108d:74 09 je 1098
108f:ff e0 jmpq *%rax
1091:0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1098:c3 retq
1099:0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010a0 :
10a0:48 8d 3d 69 2f 00 00 lea 0x2f69(%rip),%rdi # 4010
10a7:48 8d 35 62 2f 00 00 lea 0x2f62(%rip),%rsi # 4010
10ae:48 29 fe sub %rdi,%rsi
10b1:48 89 f0 mov %rsi,%rax
10b4:48 c1 ee 3f shr $0x3f,%rsi
10b8:48 c1 f8 03 sar $0x3,%rax
10bc:48 01 c6 add %rax,%rsi
10bf:48 d1 fe sar %rsi
10c2:74 14 je 10d8
10c4:48 8b 05 25 2f 00 00 mov 0x2f25(%rip),%rax # 3ff0
10cb:48 85 c0 test %rax,%rax
10ce:74 08 je 10d8
10d0:ff e0 jmpq *%rax
10d2:66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
10d8:c3 retq
10d9:0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000010e0 <__do_global_dtors_aux>:
10e0:f3 0f 1e fa endbr64
10e4:80 3d 25 2f 00 00 00 cmpb $0x0,0x2f25(%rip) # 4010
10eb:75 2b jne 1118 <__do_global_dtors_aux>
10ed:55 push %rbp
10ee:48 83 3d 02 2f 00 00 cmpq $0x0,0x2f02(%rip) # 3ff8
10f5:00
10f6:48 89 e5 mov %rsp,%rbp
10f9:74 0c je 1107 <__do_global_dtors_aux>
10fb:48 8b 3d 06 2f 00 00 mov 0x2f06(%rip),%rdi # 4008
1102:e8 29 ff ff ff callq 1030 <__cxa_finalize>
1107:e8 64 ff ff ff callq 1070
110c:c6 05 fd 2e 00 00 01 movb $0x1,0x2efd(%rip) # 4010
1113:5d pop %rbp
1114:c3 retq
1115:0f 1f 00 nopl (%rax)
1118:c3 retq
1119:0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000001120 :
1120:f3 0f 1e fa endbr64
1124:e9 77 ff ff ff jmpq 10a0
0000000000001129 :
1129:f3 0f 1e fa endbr64
112d:55 push %rbp
112e:48 89 e5 mov %rsp,%rbp
1131:b8 00 00 00 00 mov $0x0,%eax
1136:5d pop %rbp
1137:c3 retq
1138:0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
113f:00
0000000000001140 <__libc_csu_init>:
1140:f3 0f 1e fa endbr64
1144:41 57 push %r15
1146:4c 8d 3d a3 2c 00 00 lea 0x2ca3(%rip),%r15 # 3df0
114d:41 56 push %r14
114f:49 89 d6 mov %rdx,%r14
1152:41 55 push %r13
1154:49 89 f5 mov %rsi,%r13
1157:41 54 push %r12
1159:41 89 fc mov %edi,%r12d
115c:55 push %rbp
115d:48 8d 2d 94 2c 00 00 lea 0x2c94(%rip),%rbp # 3df8
1164:53 push %rbx
1165:4c 29 fd sub %r15,%rbp
1168:48 83 ec 08 sub $0x8,%rsp
116c:e8 8f fe ff ff callq 1000 <_init>
1171:48 c1 fd 03 sar $0x3,%rbp
1175:74 1f je 1196 <__libc_csu_init>
1177:31 db xor %ebx,%ebx
1179:0f 1f 80 00 00 00 00 nopl 0x0(%rax)
1180:4c 89 f2 mov %r14,%rdx
1183:4c 89 ee mov %r13,%rsi
1186:44 89 e7 mov %r12d,%edi
1189:41 ff 14 df callq *(%r15,%rbx,8)
118d:48 83 c3 01 add $0x1,%rbx
1191:48 39 dd cmp %rbx,%rbp
1194:75 ea jne 1180 <__libc_csu_init>
1196:48 83 c4 08 add $0x8,%rsp
119a:5b pop %rbx
119b:5d pop %rbp
119c:41 5c pop %r12
119e:41 5d pop %r13
11a0:41 5e pop %r14
11a2:41 5f pop %r15
11a4:c3 retq
11a5:66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
11ac:00 00 00 00
00000000000011b0 <__libc_csu_fini>:
11b0:f3 0f 1e fa endbr64
11b4:c3 retq
Disassembly of section .fini:
00000000000011b8 <_fini>:
11b8:f3 0f 1e fa endbr64
11bc:48 83 ec 08 sub $0x8,%rsp
11c0:48 83 c4 08 add $0x8,%rsp
11c4:c3 retq
我们发现这里面调用了__libc_start_main函数,但是该函数并没有在Scrt1.o里面调用,其实我们根据名字可以猜测到,该函数调用在libc库中调用。
首先将一系列参数压栈,然后调用 libc 的库函数 __libc_start_main 做初始化工作,其中最后一个压栈的参数 push $129是 main 函数的地址,__libc_start_main 在完成初始化工作之后会调用 main 函数。由于 __libc_start_main 需要动态链接,所以这个库函数的指令在可执行文件 main 的反汇编中肯定是找不到的,然而我们找到了这个:
0000000000001040 <_start>:
1040:f3 0f 1e fa endbr64
1044:31 ed xor %ebp,%ebp
1046:49 89 d1 mov %rdx,%r9
1049:5e pop %rsi
104a:48 89 e2 mov %rsp,%rdx
104d:48 83 e4 f0 and $0xfffffffffffffff0,%rsp
1051:50 push %rax
1052:54 push %rsp
1053:4c 8d 05 56 01 00 00 lea 0x156(%rip),%r8 # 11b0
105a:48 8d 0d df 00 00 00 lea 0xdf(%rip),%rcx # 1140
1061:48 8d 3d c1 00 00 00 lea 0xc1(%rip),%rdi # 1129
1068:ff 15 72 2f 00 00 callq *0x2f72(%rip) # 3fe0
106e:f4 hlt
106f:90 nop
从上面可以说明,编译一个.c文件,除了要链接Scrt1.o文件和libc库。
以上是我在Ubuntu 20系统上debug 出来的结果,但是实际每个系统出现现象会有不一样,但是具体原理基本不便,所以小伙伴要多动手,多动脑。