1. ABI规定桢栈结构
(1)栈帧的内存布局(以Linux为例)
(2)栈帧的形成方式
(3)栈帧的销毁方式
2. ebp寄存器
(1)ebp为当前栈帧的基准(存储上一个栈帧的ebp值)
(2)通过ebp能够获取返回值地址、参数和局部变量等。
3. 函数调用发生时的细节
(1)调用者通过call指令调用函数,将返回地址压入栈中
(2)函数所需要的栈空间大小由编译器确定,表现为字面常量
(3)函数结束时,leave指令恢复上一个栈帧的esp和ebp(mov ebp, esp; pop ebp)
(4)函数返回时,ret指令将返回地址恢复到eip寄存器(pop eip)
【小贴士】GDB:info frame命令输出
【编程实验】函数栈帧结构初探
//frame.c
#include <stdio.h> #define PRINT_STACK_FRAME_INFO() do \ { char* ebp = NULL; char* esp = NULL; asm volatile ( "movl %%ebp, %0\n" "movl %%esp, %1\n" : "=r"(ebp), "=r"(esp) ); printf("ebp = %p\n", ebp); printf("previous ebp = 0x%x\n", *((int*)ebp)); printf("return address = 0x%x\n", *((int*)(ebp + 4))); printf("previous esp = %p\n", ebp + 8); printf("esp = %p\n", esp); printf("&ebp = %p\n", &ebp); printf("&esp = %p\n", &esp); } while(0) void test(int a, int b) { int c = 3; printf("test() : \n"); PRINT_STACK_FRAME_INFO(); printf("&a = %p\n", &a); printf("&b = %p\n", &b); printf("&c = %p\n", &c); } void func() { int a = 1; int b = 2; printf("func() : \n"); PRINT_STACK_FRAME_INFO(); printf("&a = %p\n", &a); printf("&b = %p\n", &b); test(a, b); } int main() { printf("main() : \n"); PRINT_STACK_FRAME_INFO(); func(); return 0; } /*调试结果 [email protected]:~$ gdb (gdb) shell gcc -g test.c -o test.out //-g加上调试信息 (gdb) file test.out //载入test.out文件 (gdb) start (gdb) break test.c : 35 //在第35行下断点 (gdb) info breakpoints //查看断点信息 (gdb) continue //继续执行 Continuing. main() : ebp = 0xbffff2d8 previous ebp = 0xbffff358 return address = 0x145ce7 previous esp = 0xbffff2e0 esp = 0xbffff2b0 &ebp = 0xbffff2cc &esp = 0xbffff2c8 func() : ebp = 0xbffff2a8 previous ebp = 0xbffff2d8 return address = 0x80486d6 previous esp = 0xbffff2b0 esp = 0xbffff280 &ebp = 0xbffff294 &esp = 0xbffff290 &a = 0xbffff29c &b = 0xbffff298 test() : ebp = 0xbffff278 previous ebp = 0xbffff2a8 return address = 0x8048601 previous esp = 0xbffff280 esp = 0xbffff250 &ebp = 0xbffff268 &esp = 0xbffff264 &a = 0xbffff280 &b = 0xbffff284 &c = 0xbffff26c (gdb) info frame //查看当前栈桢信息 Stack level 0, frame at 0xbffff280: eip = 0x80484f7 in test (test.c:35); saved eip 0x8048601 called by frame at 0xbffff2b0 source language c. Arglist at 0xbffff278, args: a=1, b=2 Locals at 0xbffff278, Previous frame‘s sp is 0xbffff280 Saved registers: ebp at 0xbffff278, eip at 0xbffff27c (gdb) x /lwx 0xbffff27c //查看0xbffff27c内存单元的内容 0xbffff27c: 0x08048601 //保留着调用者的eip,即函数返回地址 (gdb) x /1wx 0xbffff280 //查看第1个参数(a) 0xbffff280: 0x00000001 (gdb) x /1wx 0xbffff284 //查看第2个参数 0xbffff284: 0x00000002 (gdb) shell objdump -S test.out > test.s //反汇编输出到test.s文件 (gdb) */
//frame.s ——反汇编结果分析
test.out: file format elf32-i38 void test(int a, int b) { 80483f4: 55 push %ebp 80483f5: 89 e5 mov %esp,%ebp 80483f7: 83 ec 28 sub $0x28,%esp int c = 3; 80483fa: c7 45 f4 03 00 00 00 movl $0x3,-0xc(%ebp) printf("test() : \n"); 8048401: c7 04 24 a0 87 04 08 movl $0x80487a0,(%esp) 8048408: e8 17 ff ff ff call 8048324 <[email protected]> PRINT_STACK_FRAME_INFO(); 804840d: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp) 8048414: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%ebp) 804841b: 89 ea mov %ebp,%edx 804841d: 89 e0 mov %esp,%eax 804841f: 89 55 f0 mov %edx,-0x10(%ebp) 8048422: 89 45 ec mov %eax,-0x14(%ebp) 8048425: 8b 55 f0 mov -0x10(%ebp),%edx 8048428: b8 aa 87 04 08 mov $0x80487aa,%eax 804842d: 89 54 24 04 mov %edx,0x4(%esp) 8048431: 89 04 24 mov %eax,(%esp) 8048434: e8 db fe ff ff call 8048314 <[email protected]> 8048439: 8b 45 f0 mov -0x10(%ebp),%eax 804843c: 8b 10 mov (%eax),%edx 804843e: b8 b4 87 04 08 mov $0x80487b4,%eax 8048443: 89 54 24 04 mov %edx,0x4(%esp) 8048447: 89 04 24 mov %eax,(%esp) 804844a: e8 c5 fe ff ff call 8048314 <[email protected]> 804844f: 8b 45 f0 mov -0x10(%ebp),%eax 8048452: 83 c0 04 add $0x4,%eax 8048455: 8b 10 mov (%eax),%edx 8048457: b8 c9 87 04 08 mov $0x80487c9,%eax 804845c: 89 54 24 04 mov %edx,0x4(%esp) 8048460: 89 04 24 mov %eax,(%esp) 8048463: e8 ac fe ff ff call 8048314 <[email protected]> 8048468: 8b 45 f0 mov -0x10(%ebp),%eax 804846b: 8d 50 08 lea 0x8(%eax),%edx 804846e: b8 e0 87 04 08 mov $0x80487e0,%eax 8048473: 89 54 24 04 mov %edx,0x4(%esp) 8048477: 89 04 24 mov %eax,(%esp) 804847a: e8 95 fe ff ff call 8048314 <[email protected]> 804847f: 8b 55 ec mov -0x14(%ebp),%edx 8048482: b8 f3 87 04 08 mov $0x80487f3,%eax 8048487: 89 54 24 04 mov %edx,0x4(%esp) 804848b: 89 04 24 mov %eax,(%esp) 804848e: e8 81 fe ff ff call 8048314 <[email protected]> 8048493: b8 fd 87 04 08 mov $0x80487fd,%eax 8048498: 8d 55 f0 lea -0x10(%ebp),%edx 804849b: 89 54 24 04 mov %edx,0x4(%esp) 804849f: 89 04 24 mov %eax,(%esp) 80484a2: e8 6d fe ff ff call 8048314 <[email protected]> 80484a7: b8 08 88 04 08 mov $0x8048808,%eax 80484ac: 8d 55 ec lea -0x14(%ebp),%edx 80484af: 89 54 24 04 mov %edx,0x4(%esp) 80484b3: 89 04 24 mov %eax,(%esp) 80484b6: e8 59 fe ff ff call 8048314 <[email protected]> printf("&a = %p\n", &a); 80484bb: b8 13 88 04 08 mov $0x8048813,%eax 80484c0: 8d 55 08 lea 0x8(%ebp),%edx 80484c3: 89 54 24 04 mov %edx,0x4(%esp) 80484c7: 89 04 24 mov %eax,(%esp) 80484ca: e8 45 fe ff ff call 8048314 <[email protected]> printf("&b = %p\n", &b); 80484cf: b8 1c 88 04 08 mov $0x804881c,%eax 80484d4: 8d 55 0c lea 0xc(%ebp),%edx 80484d7: 89 54 24 04 mov %edx,0x4(%esp) 80484db: 89 04 24 mov %eax,(%esp) 80484de: e8 31 fe ff ff call 8048314 <[email protected]> printf("&c = %p\n", &c); 80484e3: b8 25 88 04 08 mov $0x8048825,%eax 80484e8: 8d 55 f4 lea -0xc(%ebp),%edx 80484eb: 89 54 24 04 mov %edx,0x4(%esp) 80484ef: 89 04 24 mov %eax,(%esp) 80484f2: e8 1d fe ff ff call 8048314 <[email protected]> } 80484f7: c9 leave 80484f8: c3 ret 080484f9 <func>: void func() { 80484f9: 55 push %ebp 80484fa: 89 e5 mov %esp,%ebp 80484fc: 83 ec 28 sub $0x28,%esp int a = 1; 80484ff: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) int b = 2; 8048506: c7 45 f0 02 00 00 00 movl $0x2,-0x10(%ebp) printf("func() : \n"); 804850d: c7 04 24 2e 88 04 08 movl $0x804882e,(%esp) 8048514: e8 0b fe ff ff call 8048324 <[email protected]> PRINT_STACK_FRAME_INFO(); 8048519: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%ebp) 8048520: c7 45 e8 00 00 00 00 movl $0x0,-0x18(%ebp) 8048527: 89 ea mov %ebp,%edx 8048529: 89 e0 mov %esp,%eax 804852b: 89 55 ec mov %edx,-0x14(%ebp) 804852e: 89 45 e8 mov %eax,-0x18(%ebp) 8048531: 8b 55 ec mov -0x14(%ebp),%edx 8048534: b8 aa 87 04 08 mov $0x80487aa,%eax 8048539: 89 54 24 04 mov %edx,0x4(%esp) 804853d: 89 04 24 mov %eax,(%esp) 8048540: e8 cf fd ff ff call 8048314 <[email protected]> 8048545: 8b 45 ec mov -0x14(%ebp),%eax 8048548: 8b 10 mov (%eax),%edx 804854a: b8 b4 87 04 08 mov $0x80487b4,%eax 804854f: 89 54 24 04 mov %edx,0x4(%esp) 8048553: 89 04 24 mov %eax,(%esp) 8048556: e8 b9 fd ff ff call 8048314 <[email protected]> 804855b: 8b 45 ec mov -0x14(%ebp),%eax 804855e: 83 c0 04 add $0x4,%eax 8048561: 8b 10 mov (%eax),%edx 8048563: b8 c9 87 04 08 mov $0x80487c9,%eax 8048568: 89 54 24 04 mov %edx,0x4(%esp) 804856c: 89 04 24 mov %eax,(%esp) 804856f: e8 a0 fd ff ff call 8048314 <[email protected]> 8048574: 8b 45 ec mov -0x14(%ebp),%eax 8048577: 8d 50 08 lea 0x8(%eax),%edx 804857a: b8 e0 87 04 08 mov $0x80487e0,%eax 804857f: 89 54 24 04 mov %edx,0x4(%esp) 8048583: 89 04 24 mov %eax,(%esp) 8048586: e8 89 fd ff ff call 8048314 <[email protected]> 804858b: 8b 55 e8 mov -0x18(%ebp),%edx 804858e: b8 f3 87 04 08 mov $0x80487f3,%eax 8048593: 89 54 24 04 mov %edx,0x4(%esp) 8048597: 89 04 24 mov %eax,(%esp) 804859a: e8 75 fd ff ff call 8048314 <[email protected]> 804859f: b8 fd 87 04 08 mov $0x80487fd,%eax 80485a4: 8d 55 ec lea -0x14(%ebp),%edx 80485a7: 89 54 24 04 mov %edx,0x4(%esp) 80485ab: 89 04 24 mov %eax,(%esp) 80485ae: e8 61 fd ff ff call 8048314 <[email protected]> 80485b3: b8 08 88 04 08 mov $0x8048808,%eax 80485b8: 8d 55 e8 lea -0x18(%ebp),%edx 80485bb: 89 54 24 04 mov %edx,0x4(%esp) 80485bf: 89 04 24 mov %eax,(%esp) 80485c2: e8 4d fd ff ff call 8048314 <[email protected]> printf("&a = %p\n", &a); 80485c7: b8 13 88 04 08 mov $0x8048813,%eax 80485cc: 8d 55 f4 lea -0xc(%ebp),%edx 80485cf: 89 54 24 04 mov %edx,0x4(%esp) 80485d3: 89 04 24 mov %eax,(%esp) 80485d6: e8 39 fd ff ff call 8048314 <[email protected]> printf("&b = %p\n", &b); 80485db: b8 1c 88 04 08 mov $0x804881c,%eax 80485e0: 8d 55 f0 lea -0x10(%ebp),%edx 80485e3: 89 54 24 04 mov %edx,0x4(%esp) 80485e7: 89 04 24 mov %eax,(%esp) 80485ea: e8 25 fd ff ff call 8048314 <[email protected]> test(a, b); 80485ef: 8b 55 f0 mov -0x10(%ebp),%edx 80485f2: 8b 45 f4 mov -0xc(%ebp),%eax 80485f5: 89 54 24 04 mov %edx,0x4(%esp) 80485f9: 89 04 24 mov %eax,(%esp) 80485fc: e8 f3 fd ff ff call 80483f4 <test> } 8048601: c9 leave 8048602: c3 ret 08048603 <main>: int main() { 8048603: 55 push %ebp 8048604: 89 e5 mov %esp,%ebp 8048606: 83 e4 f0 and $0xfffffff0,%esp 8048609: 83 ec 20 sub $0x20,%esp printf("main() : \n"); 804860c: c7 04 24 38 88 04 08 movl $0x8048838,(%esp) 8048613: e8 0c fd ff ff call 8048324 <[email protected]> PRINT_STACK_FRAME_INFO(); 8048618: c7 44 24 1c 00 00 00 movl $0x0,0x1c(%esp) 804861f: 00 8048620: c7 44 24 18 00 00 00 movl $0x0,0x18(%esp) 8048627: 00 8048628: 89 ea mov %ebp,%edx 804862a: 89 e0 mov %esp,%eax 804862c: 89 54 24 1c mov %edx,0x1c(%esp) 8048630: 89 44 24 18 mov %eax,0x18(%esp) 8048634: 8b 54 24 1c mov 0x1c(%esp),%edx 8048638: b8 aa 87 04 08 mov $0x80487aa,%eax 804863d: 89 54 24 04 mov %edx,0x4(%esp) 8048641: 89 04 24 mov %eax,(%esp) 8048644: e8 cb fc ff ff call 8048314 <[email protected]> 8048649: 8b 44 24 1c mov 0x1c(%esp),%eax 804864d: 8b 10 mov (%eax),%edx 804864f: b8 b4 87 04 08 mov $0x80487b4,%eax 8048654: 89 54 24 04 mov %edx,0x4(%esp) 8048658: 89 04 24 mov %eax,(%esp) 804865b: e8 b4 fc ff ff call 8048314 <[email protected]> 8048660: 8b 44 24 1c mov 0x1c(%esp),%eax 8048664: 83 c0 04 add $0x4,%eax 8048667: 8b 10 mov (%eax),%edx 8048669: b8 c9 87 04 08 mov $0x80487c9,%eax 804866e: 89 54 24 04 mov %edx,0x4(%esp) 8048672: 89 04 24 mov %eax,(%esp) 8048675: e8 9a fc ff ff call 8048314 <[email protected]> 804867a: 8b 44 24 1c mov 0x1c(%esp),%eax 804867e: 8d 50 08 lea 0x8(%eax),%edx 8048681: b8 e0 87 04 08 mov $0x80487e0,%eax 8048686: 89 54 24 04 mov %edx,0x4(%esp) 804868a: 89 04 24 mov %eax,(%esp) 804868d: e8 82 fc ff ff call 8048314 <[email protected]> 8048692: 8b 54 24 18 mov 0x18(%esp),%edx 8048696: b8 f3 87 04 08 mov $0x80487f3,%eax 804869b: 89 54 24 04 mov %edx,0x4(%esp) 804869f: 89 04 24 mov %eax,(%esp) 80486a2: e8 6d fc ff ff call 8048314 <[email protected]> 80486a7: b8 fd 87 04 08 mov $0x80487fd,%eax 80486ac: 8d 54 24 1c lea 0x1c(%esp),%edx 80486b0: 89 54 24 04 mov %edx,0x4(%esp) 80486b4: 89 04 24 mov %eax,(%esp) 80486b7: e8 58 fc ff ff call 8048314 <[email protected]> 80486bc: b8 08 88 04 08 mov $0x8048808,%eax 80486c1: 8d 54 24 18 lea 0x18(%esp),%edx 80486c5: 89 54 24 04 mov %edx,0x4(%esp) 80486c9: 89 04 24 mov %eax,(%esp) 80486cc: e8 43 fc ff ff call 8048314 <[email protected]> func(); 80486d1: e8 23 fe ff ff call 80484f9 <func> return 0; 80486d6: b8 00 00 00 00 mov $0x0,%eax } 80486db: c9 leave 80486dc: c3 ret
4. 常见的函数调用约定
调用约定 |
作用 |
备注 |
__cdecl__ |
①参数从右向左入栈 ②函数的调用者负责将参数弹出栈 ③返回值保存在eax寄存器 |
只有使用这种调用约定才支持可变参数定义 |
__stdcall__ |
①参数从右向左入栈 ②被调函数清理栈中的参数 ③返回值保存在eax寄存器 |
|
__fastcall__ |
①使用ecx和edx传递前两个参数,剩余的参数从右向左入栈 ②被调函数清理栈中的参数 ③返回值保存在eax寄存器中 |
|
__thiscall__ |
①c++成员函数的调用约定 ②参数从右至左的方式入栈 ③返回值保存在eax寄存器 |
①如果参数确定,this指针存放于ecx寄存器,函数自身清理栈中的参数 ②如果参数不确定,this指针在所有参数入栈后再入栈,同时由调用者清栈 ③当参数为可变参数时,自动变为__cdecl__ |
【编程实验】函数调用约定
//convention.c
#include <stdio.h> int test(int a, int b, int c) { return a + b + c; } //三方不同的调用约定,反汇编观察参数入栈、清栈者等信息。 void __attribute__((__cdecl__)) func_1(int a, int b) { } void __attribute__((__stdcall__)) func_2(int a, int b) { } void __attribute__((__fastcall__)) func_3(int a, int b) { } int main() { int a = 1; int b = 2; int r = test(1, 2, 3); //反汇编,观察函数的返回值(存放于eax中) func_1(a, b); func_2(a, b); func_3(a, b); printf("r = %d\n", r); return 0; }
//convention.s——反汇编结果分析
demo.out: file format elf32-i386 080483c4 <test>: #include <stdio.h> int test(int a, int b, int c) { 80483c4: 55 push %ebp 80483c5: 89 e5 mov %esp,%ebp return a + b + c; 80483c7: 8b 45 0c mov 0xc(%ebp),%eax 80483ca: 8b 55 08 mov 0x8(%ebp),%edx 80483cd: 8d 04 02 lea (%edx,%eax,1),%eax 80483d0: 03 45 10 add 0x10(%ebp),%eax } 80483d3: 5d pop %ebp 80483d4: c3 ret 080483d5 <func_1>: //三方不同的调用约定,反汇编观察参数入栈、清栈者等信息。 void __attribute__((__cdecl__)) func_1(int a, int b) { 80483d5: 55 push %ebp 80483d6: 89 e5 mov %esp,%ebp } 80483d8: 5d pop %ebp 80483d9: c3 ret 080483da <func_2>: void __attribute__((__stdcall__)) func_2(int a, int b) { 80483da: 55 push %ebp 80483db: 89 e5 mov %esp,%ebp } 80483dd: 5d pop %ebp 80483de: c2 08 00 ret $0x8 080483e1 <func_3>: void __attribute__((__fastcall__)) func_3(int a, int b) { 80483e1: 55 push %ebp 80483e2: 89 e5 mov %esp,%ebp 80483e4: 83 ec 08 sub $0x8,%esp 80483e7: 89 4d fc mov %ecx,-0x4(%ebp) 80483ea: 89 55 f8 mov %edx,-0x8(%ebp) } 80483ed: c9 leave 80483ee: c3 ret 080483ef <main>: int main() { 80483ef: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483f3: 83 e4 f0 and $0xfffffff0,%esp 80483f6: ff 71 fc pushl -0x4(%ecx) 80483f9: 55 push %ebp 80483fa: 89 e5 mov %esp,%ebp 80483fc: 51 push %ecx 80483fd: 83 ec 24 sub $0x24,%esp int a = 1; 8048400: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) int b = 2; 8048407: c7 45 f0 02 00 00 00 movl $0x2,-0x10(%ebp) int r = test(1, 2, 3); //反汇编,观察函数的返回值(存放于eax中) 804840e: c7 44 24 08 03 00 00 movl $0x3,0x8(%esp) 8048415: 00 8048416: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp) 804841d: 00 804841e: c7 04 24 01 00 00 00 movl $0x1,(%esp) 8048425: e8 9a ff ff ff call 80483c4 <test> 804842a: 89 45 ec mov %eax,-0x14(%ebp) func_1(a, b); 804842d: 8b 45 f0 mov -0x10(%ebp),%eax //参数b入栈 8048430: 89 44 24 04 mov %eax,0x4(%esp) 8048434: 8b 45 f4 mov -0xc(%ebp),%eax //参数a入栈 8048437: 89 04 24 mov %eax,(%esp) 804843a: e8 96 ff ff ff call 80483d5 <func_1> func_2(a, b); 804843f: 8b 45 f0 mov -0x10(%ebp),%eax //参数b入栈 8048442: 89 44 24 04 mov %eax,0x4(%esp) 8048446: 8b 45 f4 mov -0xc(%ebp),%eax //参数a入栈 8048449: 89 04 24 mov %eax,(%esp) 804844c: e8 89 ff ff ff call 80483da <func_2> 8048451: 83 ec 08 sub $0x8,%esp func_3(a, b); 8048454: 8b 55 f0 mov -0x10(%ebp),%edx //用edx传参(b) 8048457: 8b 45 f4 mov -0xc(%ebp),%eax 804845a: 89 c1 mov %eax,%ecx //用ecx传参(a) 804845c: e8 80 ff ff ff call 80483e1 <func_3> printf("r = %d\n", r); 8048461: b8 50 85 04 08 mov $0x8048550,%eax 8048466: 8b 55 ec mov -0x14(%ebp),%edx 8048469: 89 54 24 04 mov %edx,0x4(%esp) 804846d: 89 04 24 mov %eax,(%esp) 8048470: e8 7f fe ff ff call 80482f4 <[email protected]> return 0; 8048475: b8 00 00 00 00 mov $0x0,%eax } 804847a: 8b 4d fc mov -0x4(%ebp),%ecx 804847d: c9 leave 804847e: 8d 61 fc lea -0x4(%ecx),%esp 8048481: c3 ret
5. 结构体类型的返回值
(1)函数调用时,接收返回值的变量地址需要入栈
(2)被调函数直接通过变量地址拷贝返回值
(3)函数返回值用于初始化变量时,会把变量地址传入栈中
(4)函数返回值用于赋值时,会生成临时变量来接收返回值
【编程实验】结构体函数返回值
//return.c
#include <stdio.h> struct ST { int x; int y; int z; }; struct ST f(int x, int y, int z) { struct ST st = {0}; printf("f() : &st = %p\n", &st); st.x = x; st.y = y; st.z = z; return st; } //函数返回值用于赋值操作 void assign() { struct ST st = {0}; printf("assign() : &st = %p\n", &st); st = f(1, 2, 3); printf("assign() : &st = %p\n", &st); printf("assign() : st.x = %d\n", st.x); printf("assign() : st.y = %d\n", st.y); printf("assign() : st.z = %d\n", st.z); } //函数返回值用于初始化 void init() { struct ST st = f(4, 5, 6); printf("init() : &st = %p\n", &st); printf("init() : st.x = %d\n", st.x); printf("init() : st.y = %d\n", st.y); printf("init() : st.z = %d\n", st.z); } int main() { init(); assign(); return 0; } /*运行结果与分析 [email protected]:~$ gcc return.c [email protected]:~$ ./a.out f() : &st = 0xbfbe71a4 init() : &st = 0xbfbe71d4 init() : st.x = 4 init() : st.y = 5 init() : st.z = 6 assign() : &st = 0xbfbe71d4 f() : &st = 0xbfbe7194 assign() : &st = 0xbfbe71d4 assign() : st.x = 1 assign() : st.y = 2 assign() : st.z = 3 [email protected]:~$ gcc -g return.c -o return.out [email protected]:~$ gdb (gdb) file return.out (gdb) start (gdb) break return.c : 21 //在第21行下断点 (gdb) continue Continuing. f() : &st = 0xbffff284 Breakpoint 2, f (x=4, y=5, z=6) at return.c:21 21 } (gdb) backtrace //查看函数调用栈(main->init->f) #0 f (x=4, y=5, z=6) at return.c:21 #1 0x08048506 in init () at return.c:41 #2 0x08048566 in main () at return.c:51 (gdb) frame 1 //回到init函数的栈帧 #1 0x08048506 in init () at return.c:41 41 struct ST st = f(4, 5, 6); (gdb) info frame //查看init函数的栈帧信息 Stack level 1, frame at 0xbffff2d0: eip = 0x8048506 in init (return.c:41); saved eip 0x8048566 called by frame at 0xbffff2e0, caller of frame at 0xbffff2a0 source language c. Arglist at 0xbffff2c8, args: Locals at 0xbffff2c8, Previous frame‘s sp is 0xbffff2d0 Saved registers: ebp at 0xbffff2c8, eip at 0xbffff2cc (gdb) print /x &st //打印st变量的地址 $1 = 0xbffff2b4 (gdb) frame 0 //回到f函数栈帧 #0 f (x=4, y=5, z=6) at return.c:21 21 } (gdb) info frame //查看f函数栈帧 Stack level 0, frame at 0xbffff2a0: eip = 0x804841a in f (return.c:21); saved eip 0x8048506 called by frame at 0xbffff2d0 source language c. Arglist at 0xbffff298, args: x=4, y=5, z=6 Locals at 0xbffff298, Previous frame‘s sp is 0xbffff2a0 Saved registers: ebx at 0xbffff294, ebp at 0xbffff298, eip at 0xbffff29c (gdb) x /1wx 0xbffff298+8 //查看第4个参数,传参时st地址最后一个入栈 0xbffff2a0: 0xbffff2b4 //st的地址为0xbffff2b4(注意,可见st地址会入栈!) (gdb) continue Continuing. init() : &st = 0xbffff2b4 init() : st.x = 4 init() : st.y = 5 init() : st.z = 6 assign() : &st = 0xbffff2b4 f() : &st = 0xbffff274 Breakpoint 2, f (x=1, y=2, z=3) at return.c:21 21 } (gdb) backtrace //查看函数调用栈信息(main->assign->f) #0 f (x=1, y=2, z=3) at return.c:21 #1 0x08048476 in assign () at return.c:30 #2 0x0804856b in main () at return.c:53 (gdb) info frame //查看f函数的栈帧信息 Stack level 0, frame at 0xbffff290: eip = 0x804841a in f (return.c:21); saved eip 0x8048476 called by frame at 0xbffff2d0 source language c. Arglist at 0xbffff288, args: x=1, y=2, z=3 Locals at 0xbffff288, Previous frame‘s sp is 0xbffff290 Saved registers: ebx at 0xbffff284, ebp at 0xbffff288, eip at 0xbffff28c (gdb) x /1wx 0xbffff288+8 //查看第4个参数,是临时变量的地址 0xbffff290: 0xbffff2a0 //临时变量的地址,而不是st的地址(0xbffff274) (gdb) */
//return.s——反汇编结果分析
return.out: file format elf32-i386 struct ST f(int x, int y, int z) { 80483c4: 55 push %ebp 80483c5: 89 e5 mov %esp,%ebp 80483c7: 53 push %ebx 80483c8: 83 ec 24 sub $0x24,%esp 80483cb: 8b 5d 08 mov 0x8(%ebp),%ebx struct ST st = {0}; 80483ce: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%ebp) 80483d5: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp) 80483dc: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp) printf("f() : &st = %p\n", &st); 80483e3: b8 40 86 04 08 mov $0x8048640,%eax 80483e8: 8d 55 ec lea -0x14(%ebp),%edx 80483eb: 89 54 24 04 mov %edx,0x4(%esp) 80483ef: 89 04 24 mov %eax,(%esp) 80483f2: e8 fd fe ff ff call 80482f4 <[email protected]> st.x = x; 80483f7: 8b 45 0c mov 0xc(%ebp),%eax 80483fa: 89 45 ec mov %eax,-0x14(%ebp) st.y = y; 80483fd: 8b 45 10 mov 0x10(%ebp),%eax 8048400: 89 45 f0 mov %eax,-0x10(%ebp) st.z = z; 8048403: 8b 45 14 mov 0x14(%ebp),%eax 8048406: 89 45 f4 mov %eax,-0xc(%ebp) return st; 8048409: 8b 45 ec mov -0x14(%ebp),%eax 804840c: 89 03 mov %eax,(%ebx) 804840e: 8b 45 f0 mov -0x10(%ebp),%eax 8048411: 89 43 04 mov %eax,0x4(%ebx) 8048414: 8b 45 f4 mov -0xc(%ebp),%eax 8048417: 89 43 08 mov %eax,0x8(%ebx) } 804841a: 89 d8 mov %ebx,%eax 804841c: 83 c4 24 add $0x24,%esp 804841f: 5b pop %ebx 8048420: 5d pop %ebp 8048421: c2 04 00 ret $0x4 08048424 <assign>: //函数返回值用于赋值操作 void assign() { 8048424: 55 push %ebp 8048425: 89 e5 mov %esp,%ebp 8048427: 83 ec 38 sub $0x38,%esp struct ST st = {0}; 804842a: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%ebp) //-0x14(%ebp),也是st的地址 8048431: c7 45 f0 00 00 00 00 movl $0x0,-0x10(%ebp) 8048438: c7 45 f4 00 00 00 00 movl $0x0,-0xc(%ebp) printf("assign() : &st = %p\n", &st); 804843f: b8 50 86 04 08 mov $0x8048650,%eax 8048444: 8d 55 ec lea -0x14(%ebp),%edx //-0x14(%ebp),也是st的地址 8048447: 89 54 24 04 mov %edx,0x4(%esp) 804844b: 89 04 24 mov %eax,(%esp) 804844e: e8 a1 fe ff ff call 80482f4 <[email protected]> st = f(1, 2, 3); 8048453: 8d 45 d8 lea -0x28(%ebp),%eax //-0x28(%ebp),临时变量(栈上)的地址 8048456: c7 44 24 0c 03 00 00 movl $0x3,0xc(%esp) //参数3入栈 804845d: 00 804845e: c7 44 24 08 02 00 00 movl $0x2,0x8(%esp) //参数2入栈 8048465: 00 8048466: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp) //参数1入栈 804846d: 00 804846e: 89 04 24 mov %eax,(%esp) //临时变量入栈 8048471: e8 4e ff ff ff call 80483c4 <f> //调用f函数! 8048476: 83 ec 04 sub $0x4,%esp 8048479: 8b 45 d8 mov -0x28(%ebp),%eax //将临时变量地址保存在eax 804847c: 89 45 ec mov %eax,-0x14(%ebp) //拷贝x 804847f: 8b 45 dc mov -0x24(%ebp),%eax 8048482: 89 45 f0 mov %eax,-0x10(%ebp) //拷贝y 8048485: 8b 45 e0 mov -0x20(%ebp),%eax 8048488: 89 45 f4 mov %eax,-0xc(%ebp) //拷贝z printf("assign() : &st = %p\n", &st); 804848b: b8 50 86 04 08 mov $0x8048650,%eax 8048490: 8d 55 ec lea -0x14(%ebp),%edx 8048493: 89 54 24 04 mov %edx,0x4(%esp) 8048497: 89 04 24 mov %eax,(%esp) 804849a: e8 55 fe ff ff call 80482f4 <[email protected]> printf("assign() : st.x = %d\n", st.x); 804849f: 8b 55 ec mov -0x14(%ebp),%edx 80484a2: b8 65 86 04 08 mov $0x8048665,%eax 80484a7: 89 54 24 04 mov %edx,0x4(%esp) 80484ab: 89 04 24 mov %eax,(%esp) 80484ae: e8 41 fe ff ff call 80482f4 <[email protected]> printf("assign() : st.y = %d\n", st.y); 80484b3: 8b 55 f0 mov -0x10(%ebp),%edx 80484b6: b8 7b 86 04 08 mov $0x804867b,%eax 80484bb: 89 54 24 04 mov %edx,0x4(%esp) 80484bf: 89 04 24 mov %eax,(%esp) 80484c2: e8 2d fe ff ff call 80482f4 <[email protected]> printf("assign() : st.z = %d\n", st.z); 80484c7: 8b 55 f4 mov -0xc(%ebp),%edx 80484ca: b8 91 86 04 08 mov $0x8048691,%eax 80484cf: 89 54 24 04 mov %edx,0x4(%esp) 80484d3: 89 04 24 mov %eax,(%esp) 80484d6: e8 19 fe ff ff call 80482f4 <[email protected]> } 80484db: c9 leave 80484dc: c3 ret 080484dd <init>: //函数返回值用于初始化 void init() { 80484dd: 55 push %ebp 80484de: 89 e5 mov %esp,%ebp 80484e0: 83 ec 28 sub $0x28,%esp struct ST st = f(4, 5, 6); 80484e3: 8d 45 ec lea -0x14(%ebp),%eax //取st的地址 80484e6: c7 44 24 0c 06 00 00 movl $0x6,0xc(%esp) //参数6入栈 80484ed: 00 80484ee: c7 44 24 08 05 00 00 movl $0x5,0x8(%esp) //参数5入栈 80484f5: 00 80484f6: c7 44 24 04 04 00 00 movl $0x4,0x4(%esp) //参数4入栈 80484fd: 00 80484fe: 89 04 24 mov %eax,(%esp) //st地址入栈 8048501: e8 be fe ff ff call 80483c4 <f> //调用f函数! 8048506: 83 ec 04 sub $0x4,%esp printf("init() : &st = %p\n", &st); 8048509: b8 a7 86 04 08 mov $0x80486a7,%eax 804850e: 8d 55 ec lea -0x14(%ebp),%edx 8048511: 89 54 24 04 mov %edx,0x4(%esp) 8048515: 89 04 24 mov %eax,(%esp) 8048518: e8 d7 fd ff ff call 80482f4 <[email protected]> printf("init() : st.x = %d\n", st.x); 804851d: 8b 55 ec mov -0x14(%ebp),%edx 8048520: b8 ba 86 04 08 mov $0x80486ba,%eax 8048525: 89 54 24 04 mov %edx,0x4(%esp) 8048529: 89 04 24 mov %eax,(%esp) 804852c: e8 c3 fd ff ff call 80482f4 <[email protected]> printf("init() : st.y = %d\n", st.y); 8048531: 8b 55 f0 mov -0x10(%ebp),%edx 8048534: b8 ce 86 04 08 mov $0x80486ce,%eax 8048539: 89 54 24 04 mov %edx,0x4(%esp) 804853d: 89 04 24 mov %eax,(%esp) 8048540: e8 af fd ff ff call 80482f4 <[email protected]> printf("init() : st.z = %d\n", st.z); 8048545: 8b 55 f4 mov -0xc(%ebp),%edx 8048548: b8 e2 86 04 08 mov $0x80486e2,%eax 804854d: 89 54 24 04 mov %edx,0x4(%esp) 8048551: 89 04 24 mov %eax,(%esp) 8048554: e8 9b fd ff ff call 80482f4 <[email protected]> } 8048559: c9 leave 804855a: c3 ret 0804855b <main>: int main() { 804855b: 55 push %ebp 804855c: 89 e5 mov %esp,%ebp 804855e: 83 e4 f0 and $0xfffffff0,%esp init(); 8048561: e8 77 ff ff ff call 80484dd <init> assign(); 8048566: e8 b9 fe ff ff call 8048424 <assign> return 0; 804856b: b8 00 00 00 00 mov $0x0,%eax } 8048570: 89 ec mov %ebp,%esp 8048572: 5d pop %ebp 8048573: c3 ret
6. 小结
(1)栈帧是函数调用时形成的链式内存结构
(2)ebp是构成栈帧的核心基准寄存器
(3)调用约定决定了函数调用的细节行为,同时也定义了函数被编译后对应最终符号名
(4)基础数据类型的返回值通过eax传递
(5)结构体类型的返回值通过内存拷贝完成。