x86汇编指令分析
纯属个人调试过程中的个人理解
GDB调试文档
x86汇编指令分析
对于程序如何在栈中存放: https://www.askpure.com/course_DXMRI553-D81INAOS-06RBPK32-8QGUKX0E.html
结论:
8个通用寄存器:
rax rbx rcx rdx -> 数据寄存器, 都是64bit的, 其实 rax, eax, ax, al/ah 表示的都是同一个寄存器, 指向的位数不同而已
eax ebx ecx edx -> 数据寄存器, eax 32bit, 其中 ax 为 eax 低十六位, al(ah)为ax的低(高十六位)
edi esi (rdi, rsi) -> 变址寄存器
rbp rsp (ebp esp) -> 栈指针寄存器, rsp 一直指向栈的最顶部, rbp 一直指向栈最底部偏移八字节处, 而最底部的八字节存储被调用的 rbp指针
环境
x86, 64位机器, linux Centos8, GNU/Linux, gcc 8.5.0, make 4.2.1
栈示例图
C代码
#include <stdio.h>
#include <stdlib.h>
void test1(float a1, double a2, short a3, int a4,
unsigned int a5, unsigned char a6) {
int b1 = 0;
b1 = a1 + a2;
b1 = a3 + a4 + b1;
b1 = a5 + a6 + b1;
}
void test(int *ptr, int val, unsigned short val1) {
ptr[10] = val + val1;
}
void fn0() {
printf( "first register, last call\n" );
}
void fn1() {
printf( "next.\n" );
}
int main()
{
printf("start run \n");
atexit(fn0);
atexit(fn1);
float a1 = 10.1;
double a2 = 12.54;
short a3 = 0xDCAB;
int a4 = 0xABCDABCD;
unsigned int a5 = 0xADCBADCB;
unsigned char a6 = 0xDE;
test1(a1, a2,a3, a4, a5, a6);
int *null_ptr = NULL;
unsigned int val = 11;
unsigned short val1 = 9;
test(null_ptr, val, val1); //对空指针指向的内存区域写,会发生段错误
printf("this is good \n");
return 0;
}
生成部分汇编代码如下:
00000000004005d6 <test1>:
; rsp 0x7fffffffe238, rbp 0x7fffffffe270 在 rbp没有push之前, rsp和rbp的值是这样的
4005d6: 55 push %rbp
; rsp 0x7fffffffe230, rbp 0x7fffffffe230
4005d7: 48 89 e5 mov %rsp,%rbp
; rsp 0x7fffffffe230, rbp 0x7fffffffe230
4005da: f3 0f 11 45 ec movss %xmm0,-0x14(%rbp)
4005df: f2 0f 11 4d e0 movsd %xmm1,-0x20(%rbp)
4005e4: 89 f8 mov %edi,%eax
4005e6: 89 75 dc mov %esi,-0x24(%rbp)
4005e9: 89 55 d8 mov %edx,-0x28(%rbp)
4005ec: 89 ca mov %ecx,%edx
4005ee: 66 89 45 e8 mov %ax,-0x18(%rbp) ;ax 值为0xdcab, ax是eax的低十六位, al 是eax的低八位
4005f2: 89 d0 mov %edx,%eax
4005f4: 88 45 d4 mov %al,-0x2c(%rbp) ;al 值为0xab
4005f7: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
4005fe: f3 0f 5a 45 ec cvtss2sd -0x14(%rbp),%xmm0
400603: f2 0f 58 45 e0 addsd -0x20(%rbp),%xmm0
400608: f2 0f 2c c0 cvttsd2si %xmm0,%eax
40060c: 89 45 fc mov %eax,-0x4(%rbp)
40060f: 0f bf 55 e8 movswl -0x18(%rbp),%edx
400613: 8b 45 dc mov -0x24(%rbp),%eax
400616: 01 d0 add %edx,%eax
400618: 01 45 fc add %eax,-0x4(%rbp)
40061b: 0f b6 55 d4 movzbl -0x2c(%rbp),%edx
40061f: 8b 45 d8 mov -0x28(%rbp),%eax
400622: 01 c2 add %eax,%edx
400624: 8b 45 fc mov -0x4(%rbp),%eax
400627: 01 d0 add %edx,%eax
400629: 89 45 fc mov %eax,-0x4(%rbp)
40062c: 90 nop
; rsp 0x7fffffffe230, rbp 0x7fffffffe230
40062d: 5d pop %rbp
; rsp 0x7fffffffe238, rbp 0x7fffffffe270
40062e: c3 retq
000000000040062f <test>:
; rsp 0x7fffffffe238, rbp 0x7fffffffe270
40062f: 55 push %rbp
; rsp 0x7fffffffe230, rbp 0x7fffffffe270
400630: 48 89 e5 mov %rsp,%rbp
; rsp 0x7fffffffe230, rbp 0x7fffffffe230
400633: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400637: 89 75 f4 mov %esi,-0xc(%rbp)
40063a: 89 d0 mov %edx,%eax
40063c: 66 89 45 f0 mov %ax,-0x10(%rbp)
400640: 0f b7 4d f0 movzwl -0x10(%rbp),%ecx
400644: 48 8b 45 f8 mov -0x8(%rbp),%rax
400648: 48 83 c0 28 add $0x28,%rax
40064c: 8b 55 f4 mov -0xc(%rbp),%edx
40064f: 01 ca add %ecx,%edx
400651: 89 10 mov %edx,(%rax)
400653: 90 nop
; rsp 0x7fffffffe230, rbp 0x7fffffffe270
400654: 5d pop %rbp
; rsp 0x7fffffffe238, rbp 0x7fffffffe270
400655: c3 retq
0000000000400678 <main>:
; rsp 0x7fffffffe278, rbp 0x400730 <__libc_csu_init>, 进入main函数之前, 在 rbp没有push之前, rsp和rbp的值是这样的
; 此时 rsp指向帧帧的栈顶,, rbp 指向 __libc_csu_init 运行的地址, 也就是 __libc_csu_init 所在的栈帧, (个人理解的栈帧和栈顶不是一个概念)
400678: 55 push %rbp ;将rbp的寄存器的值推入栈中
; rsp 0x7fffffffe270, rbp 0x400730
; 将 rbp入栈, 已知指针为8字节, 所以此时rsp偏移8个字节
400679: 48 89 e5 mov %rsp,%rbp ; 将rsp
; rsp 0x7fffffffe270, rbp 0x7fffffffe270
; 然后将rsp的值保存在寄存器rbp中, 此时 rbp作为栈的指针,用于以下数据的入栈出栈处理
40067c: 48 83 ec 30 sub $0x30,%rsp
; rsp 0x7fffffffe240, rbp 0x7fffffffe270
; 将rsp累加0x30, 开辟空间用于存放变量, 此时rsp指向开辟空间后的位置, 由此可得出结论, rsp是一直指向栈顶的寄存器, rbp的值未变, 指向栈低
400680: bf 08 08 40 00 mov $0x400808,%edi
400685: e8 46 fe ff ff callq 4004d0 <puts@plt>
40068a: bf 56 06 40 00 mov $0x400656,%edi
40068f: e8 1c 01 00 00 callq 4007b0 <atexit>
; rip 0x400694
400694: bf 67 06 40 00 mov $0x400667,%edi
; rip 0x400699
400699: e8 12 01 00 00 callq 4007b0 <atexit>
;test1 参数初始化传递
; rip 0x40069e, 得出结论: rip 总是指向下一条将要执行的指令的汇编地址
40069e: f3 0f 10 05 7e 01 00 movss 0x17e(%rip),%xmm0 # 400824 <__dso_handle+0x44> ;
4006a5: 00
4006a6: f3 0f 11 45 fc movss %xmm0,-0x4(%rbp) ; float 占用四个字节 0x04
4006ab: f2 0f 10 05 75 01 00 movsd 0x175(%rip),%xmm0 # 400828 <__dso_handle+0x48>
4006b2: 00
4006b3: f2 0f 11 45 f0 movsd %xmm0,-0x10(%rbp) ; double占用八个字节,0x10-0x4=0xC=12,这里为什么会多出四个字节
; 如果按照正常来算 -0xC(%rbp), 0x7fffffffe264, 也能被4 除尽,应该也不是需要地址对齐, 暂时没搞明白
; GUN为mov添加了一个维度: movl 32位字长度, movw 16位字长度, movb 8位字长度
4006b8: 66 c7 45 ee ab dc movw $0xdcab,-0x12(%rbp) ;short 占用2字节, 0x12 - 0x10
4006be: c7 45 e8 cd ab cd ab movl $0xabcdabcd,-0x18(%rbp) ;int 占用四字节, 这里是占用了0x18-0x12=6, 又多了两个字节
4006c5: c7 45 e4 cb ad cb ad movl $0xadcbadcb,-0x1c(%rbp) ;无符号int战四字节, 0x1c-0x18=4,是四字节
4006cc: c6 45 e3 de movb $0xde,-0x1d(%rbp) ;无符号char, 占一字节
4006d0: 0f b6 4d e3 movzbl -0x1d(%rbp),%ecx ;ecx值为0xde
4006d4: 0f bf 45 ee movswl -0x12(%rbp),%eax ;eax值为0xffffdcab, 在寄存器中存储实则为4字节
4006d8: 8b 55 e4 mov -0x1c(%rbp),%edx ;edx 0xadcbadcb
4006db: 8b 75 e8 mov -0x18(%rbp),%esi ;0xabcdabcd
4006de: f2 0f 10 4d f0 movsd -0x10(%rbp),%xmm1;xmm1 和xmm0 存储的是浮点型
4006e3: f3 0f 10 45 fc movss -0x4(%rbp),%xmm0
4006e8: 89 c7 mov %eax,%edi
; rsp 0x7fffffffe240, rbp 0x7fffffffe270
4006ea: e8 e7 fe ff ff callq 4005d6 <test1>
; rsp 0x7fffffffe240, rbp 0x7fffffffe270
;test 参数初始化传递
4006ef: 48 c7 45 d8 00 00 00 movq $0x0,-0x28(%rbp)
4006f6: 00
4006f7: c7 45 d4 0b 00 00 00 movl $0xb,-0x2c(%rbp)
4006fe: 66 c7 45 d2 09 00 movw $0x9,-0x2e(%rbp)
400704: 0f b7 55 d2 movzwl -0x2e(%rbp),%edx
400708: 8b 4d d4 mov -0x2c(%rbp),%ecx
40070b: 48 8b 45 d8 mov -0x28(%rbp),%rax
40070f: 89 ce mov %ecx,%esi
400711: 48 89 c7 mov %rax,%rdi
400714: e8 16 ff ff ff callq 40062f <test>
400706: bf 03 08 40 00 mov $0x400803,%edi
40070b: e8 c0 fd ff ff callq 4004d0 <puts@plt>
400710: b8 00 00 00 00 mov $0x0,%eax
; rsp 0x7fffffffe240, rbp 0x7fffffffe270
400715: c9 leaveq
; rsp 0x7fffffffe238, rbp 0x400730 <__libc_csu_init>
400716: c3 retq
400717: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
40071e: 00 00
;8个通用寄存器:
; eax ebx ecx edx -> 数据寄存器, 存储运算过程中数据的存放, 其中 ax 为 eax 低十六位, al(ah)为ax的低(高十六位)
; edi esi -> 变址寄存器
; rbp rsp (ebp esp) -> 栈指针寄存器, rsp 一直指向栈的最顶部, rbp 一直指向
上述汇编代码中还有存在两个疑问:
- 0x4006be 行中, 一个double占八个字节, 实际rbp偏移多出四个字节
0x4006b3 行中, 一个int 占四个字节, 实际rbp偏移多出两个字节 - 0x4006ea 行中, 在main中方法跳转到test1的时候, rsp 直接从 0x7fffffffe240 跳转 0x7fffffffe238, 为什么会少了两个字节
- 0x40067c 行中, 在main中开辟了 0x30 的空间, 但是在 test1 中也定义了变量, 为什么没有开辟空间, 同样的, 在main函数中如果再加变量, 0x30 还会增加, 但是在test中加多少变量, main 中开辟的空间没有变化, test1 中也没有开辟空间
linux c 可执行文件如何被系统掉用
https://zhuanlan.zhihu.com/p/52054044
同样的, 源码如三, 汇编之后的代码如下, 点击查看:
a.out: 文件格式 elf64-x86-64
Disassembly of section .init:
0000000000400428 <_init>:
400428: f3 0f 1e fa endbr64
40042c: 48 83 ec 08 sub $0x8,%rsp
400430: 48 8b 05 b9 0b 20 00 mov 0x200bb9(%rip),%rax # 600ff0 <__gmon_start__>
400437: 48 85 c0 test %rax,%rax
40043a: 74 02 je 40043e <_init+0x16>
40043c: ff d0 callq *%rax
40043e: 48 83 c4 08 add $0x8,%rsp
400442: c3 retq
Disassembly of section .text:
0000000000400450 <_start>:
400450: f3 0f 1e fa endbr64
400454: 31 ed xor %ebp,%ebp
400456: 49 89 d1 mov %rdx,%r9
400459: 5e pop %rsi
40045a: 48 89 e2 mov %rsp,%rdx
40045d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
400461: 50 push %rax
400462: 54 push %rsp
400463: 49 c7 c0 f0 05 40 00 mov $0x4005f0,%r8
40046a: 48 c7 c1 80 05 40 00 mov $0x400580,%rcx
400471: 48 c7 c7 51 05 40 00 mov $0x400551,%rdi
400478: ff 15 6a 0b 20 00 callq *0x200b6a(%rip) # 600fe8 <__libc_start_main@GLIBC_2.2.5>
40047e: f4 hlt
000000000040047f <.annobin_init.c>:
40047f: 90 nop
0000000000400480 <_dl_relocate_static_pie>:
400480: f3 0f 1e fa endbr64
400484: c3 retq
0000000000400485 <.annobin__dl_relocate_static_pie.end>:
400485: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40048c: 00 00 00
40048f: 90 nop
0000000000400490 <deregister_tm_clones>:
400490: 48 8d 3d 89 0b 20 00 lea 0x200b89(%rip),%rdi # 601020 <__TMC_END__>
400497: 48 8d 05 82 0b 20 00 lea 0x200b82(%rip),%rax # 601020 <__TMC_END__>
40049e: 48 39 f8 cmp %rdi,%rax
4004a1: 74 15 je 4004b8 <deregister_tm_clones+0x28>
4004a3: 48 8b 05 36 0b 20 00 mov 0x200b36(%rip),%rax # 600fe0 <_ITM_deregisterTMCloneTable>
4004aa: 48 85 c0 test %rax,%rax
4004ad: 74 09 je 4004b8 <deregister_tm_clones+0x28>
4004af: ff e0 jmpq *%rax
4004b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
4004b8: c3 retq
4004b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000004004c0 <register_tm_clones>:
4004c0: 48 8d 3d 59 0b 20 00 lea 0x200b59(%rip),%rdi # 601020 <__TMC_END__>
4004c7: 48 8d 35 52 0b 20 00 lea 0x200b52(%rip),%rsi # 601020 <__TMC_END__>
4004ce: 48 29 fe sub %rdi,%rsi
4004d1: 48 c1 fe 03 sar $0x3,%rsi
4004d5: 48 89 f0 mov %rsi,%rax
4004d8: 48 c1 e8 3f shr $0x3f,%rax
4004dc: 48 01 c6 add %rax,%rsi
4004df: 48 d1 fe sar %rsi
4004e2: 74 14 je 4004f8 <register_tm_clones+0x38>
4004e4: 48 8b 05 0d 0b 20 00 mov 0x200b0d(%rip),%rax # 600ff8 <_ITM_registerTMCloneTable>
4004eb: 48 85 c0 test %rax,%rax
4004ee: 74 08 je 4004f8 <register_tm_clones+0x38>
4004f0: ff e0 jmpq *%rax
4004f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
4004f8: c3 retq
4004f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
0000000000400500 <__do_global_dtors_aux>:
400500: f3 0f 1e fa endbr64
400504: 80 3d 11 0b 20 00 00 cmpb $0x0,0x200b11(%rip) # 60101c <_edata>
40050b: 75 13 jne 400520 <__do_global_dtors_aux+0x20>
40050d: 55 push %rbp
40050e: 48 89 e5 mov %rsp,%rbp
400511: e8 7a ff ff ff callq 400490 <deregister_tm_clones>
400516: c6 05 ff 0a 20 00 01 movb $0x1,0x200aff(%rip) # 60101c <_edata>
40051d: 5d pop %rbp
40051e: c3 retq
40051f: 90 nop
400520: c3 retq
400521: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
400528: 00 00 00 00
40052c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400530 <frame_dummy>:
400530: f3 0f 1e fa endbr64
400534: eb 8a jmp 4004c0 <register_tm_clones>
0000000000400536 <test>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 48 89 7d f8 mov %rdi,-0x8(%rbp)
40053e: 89 75 f4 mov %esi,-0xc(%rbp)
400541: 48 8b 45 f8 mov -0x8(%rbp),%rax
400545: 48 8d 50 28 lea 0x28(%rax),%rdx
400549: 8b 45 f4 mov -0xc(%rbp),%eax
40054c: 89 02 mov %eax,(%rdx)
40054e: 90 nop
40054f: 5d pop %rbp
400550: c3 retq
0000000000400551 <main>:
400551: 55 push %rbp
400552: 48 89 e5 mov %rsp,%rbp
400555: 48 83 ec 10 sub $0x10,%rsp
400559: c7 45 fc 0b 00 00 00 movl $0xb,-0x4(%rbp)
400560: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
400567: 00
400568: 8b 55 fc mov -0x4(%rbp),%edx
40056b: 48 8b 45 f0 mov -0x10(%rbp),%rax
40056f: 89 d6 mov %edx,%esi
400571: 48 89 c7 mov %rax,%rdi
400574: e8 bd ff ff ff callq 400536 <test>
400579: b8 00 00 00 00 mov $0x0,%eax
40057e: c9 leaveq
40057f: c3 retq
0000000000400580 <__libc_csu_init>:
400580: f3 0f 1e fa endbr64
400584: 41 57 push %r15
400586: 49 89 d7 mov %rdx,%r15
400589: 41 56 push %r14
40058b: 49 89 f6 mov %rsi,%r14
40058e: 41 55 push %r13
400590: 41 89 fd mov %edi,%r13d
400593: 41 54 push %r12
400595: 4c 8d 25 a4 08 20 00 lea 0x2008a4(%rip),%r12 # 600e40 <__frame_dummy_init_array_entry>
40059c: 55 push %rbp
40059d: 48 8d 2d a4 08 20 00 lea 0x2008a4(%rip),%rbp # 600e48 <__init_array_end>
4005a4: 53 push %rbx
4005a5: 4c 29 e5 sub %r12,%rbp
4005a8: 48 83 ec 08 sub $0x8,%rsp
4005ac: e8 77 fe ff ff callq 400428 <_init>
4005b1: 48 c1 fd 03 sar $0x3,%rbp
4005b5: 74 1f je 4005d6 <__libc_csu_init+0x56>
4005b7: 31 db xor %ebx,%ebx
4005b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
4005c0: 4c 89 fa mov %r15,%rdx
4005c3: 4c 89 f6 mov %r14,%rsi
4005c6: 44 89 ef mov %r13d,%edi
4005c9: 41 ff 14 dc callq *(%r12,%rbx,8)
4005cd: 48 83 c3 01 add $0x1,%rbx
4005d1: 48 39 dd cmp %rbx,%rbp
4005d4: 75 ea jne 4005c0 <__libc_csu_init+0x40>
4005d6: 48 83 c4 08 add $0x8,%rsp
4005da: 5b pop %rbx
4005db: 5d pop %rbp
4005dc: 41 5c pop %r12
4005de: 41 5d pop %r13
4005e0: 41 5e pop %r14
4005e2: 41 5f pop %r15
4005e4: c3 retq
00000000004005e5 <.annobin___libc_csu_fini.start>:
4005e5: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
4005ec: 00 00 00 00
00000000004005f0 <__libc_csu_fini>:
4005f0: f3 0f 1e fa endbr64
4005f4: c3 retq
Disassembly of section .fini:
00000000004005f8 <_fini>:
4005f8: f3 0f 1e fa endbr64
4005fc: 48 83 ec 08 sub $0x8,%rsp
400600: 48 83 c4 08 add $0x8,%rsp
400604: c3 retq
使用 nm 和 ldd 查看 a.out 中链接的库和所有方法, 点击查看
[root@cen8 han]# nm a.out
0000000000400485 t .annobin__dl_relocate_static_pie.end
...
00000000004005f8 T _fini
0000000000400530 t frame_dummy
0000000000600e40 t __frame_dummy_init_array_entry
0000000000400734 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000400618 r __GNU_EH_FRAME_HDR
0000000000400428 T _init
0000000000600e48 t __init_array_end
0000000000600e40 t __init_array_start
0000000000400608 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
00000000004005f0 T __libc_csu_fini
0000000000400580 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
0000000000400551 T main
00000000004004c0 t register_tm_clones
0000000000400450 T _start
0000000000400536 T test
0000000000601020 D __TMC_END__
[root@cen8 han]# ldd ./a.out
linux-vdso.so.1 (0x00007ffc773f9000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb046233000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb0465f8000)
说明
- /lib64/ld-linux-x86-64.so.2: glibc 是Linux下的GUN C函数库,可单独运行, 负责动态加载, 通过读取可执行文件的头部信息来确定那些库文件是必须的, 以及哪些需要加载。加载完成之后, 它会通过修正执行文件里相关的地址指针来和加载的库文件完成动态链接, 此时程序就可以运行了
glibc是linux下面c标准库的实现,即GNU C Library。glibc本身是GNU旗下的C标准库,后来逐渐成为了Linux的标准c(新标准C99),而Linux下原来的标准c库Linux libc逐渐不再被维护。
glibc逐渐成linux标准C的原因: 归于GUN的发展壮大, GUN是一种操作系统 GUN/Linux: LinusTorvalds在1991年开发出与Unix兼容的内核Linux, GUN将Linux内核和GUN系统结合产生了一个非常完成的自由软件操作系统, 称之为 GUN/Linux, Linux开源内核代码,其实就是GUN/Linux操作系统 - libc.so.6 => /lib64/libc.so.6: glibc链接库, 提供了很多库函数. string, signal,io等基本功能
- linux-vdso.so.1: 内核自动映射到每个进程地址空间的文件。它的工作是找到并定位进程所需的其他共享库。
- 从上述汇编中可以看出,C代码编译成二进制文件,会分为多个区, 这里只显示: .init, .text, .fini
- 程序执行,
_start -> __libc_start_main -> main
, 这块可以用 gdbstarti
指令调试查看.
__libc_start_main
还调用了__libc_csu_init
函数,__libc_csu_init
会调用_init
函数
当程序开始执行的时候, shell 或 GUI会调用 execve, 使用
man execve
, 可以查看execve手册,
__libc_start_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));
作用:
处理关于setuid、setgid程序的安全问题
启动线程
注册用户程序的fini和rtld_fini参数,然后被at_exit调用,从而完成用户程序和加载器的负责清理工作的函数
调用其init参数
调用main函数,并把argc和argv参数、环境变量传递给它
调用exit函数,并将main函数的返回值传递给它
本文来自博客园踩坑狭,作者:韩若明瞳,转载请注明原文链接:https://www.cnblogs.com/han-guang-xue/p/16939626.html