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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理