函数调用过程分析

上传人:子 文档编号:47158980 上传时间:2018-06-30 格式:PDF 页数:11 大小:325.38KB
返回 下载 相关 举报
函数调用过程分析_第1页
第1页 / 共11页
函数调用过程分析_第2页
第2页 / 共11页
函数调用过程分析_第3页
第3页 / 共11页
函数调用过程分析_第4页
第4页 / 共11页
函数调用过程分析_第5页
第5页 / 共11页
点击查看更多>>
资源描述

《函数调用过程分析》由会员分享,可在线阅读,更多相关《函数调用过程分析(11页珍藏版)》请在金锄头文库上搜索。

1、1. 函数调用函数调用过程分析过程分析 1. 函数调用函数调用 我们用下面的代码来研究函数调用的过程。 例例 19.1. 研究函数的调用过程研究函数的调用过程 int bar(int c, int d) int e = c + d; return e; int foo(int a, int b) return bar(a, b); int main(void) foo(2, 3); return 0; 如果在编译时加上-g 选项(在第 10 章 gdb 讲过-g 选项),那么用 objdump 反汇编时可以把 C 代 码和汇编代码穿插起来显示,这样 C 代码和汇编代码的对应关系看得更清楚。反汇

2、编的结果很长, 以下只列出我们关心的部分。 $ gcc main.c -g $ objdump -dS a.out . 08048394 : int bar(int c, int d) 8048394: 55 push %ebp 8048395: 89 e5 mov %esp,%ebp 8048397: 83 ec 10 sub $0x10,%esp int e = c + d; 804839a: 8b 55 0c mov 0xc(%ebp),%edx 804839d: 8b 45 08 mov 0x8(%ebp),%eax 80483a0: 01 d0 add %edx,%eax 80483

3、a2: 89 45 fc mov %eax,-0x4(%ebp) return e; 80483a5: 8b 45 fc mov -0x4(%ebp),%eax 80483a8: c9 leave 80483a9: c3 ret 080483aa : int foo(int a, int b) 80483aa: 55 push %ebp 80483ab: 89 e5 mov %esp,%ebp 80483ad: 83 ec 08 sub $0x8,%esp return bar(a, b); 80483b0: 8b 45 0c mov 0xc(%ebp),%eax 80483b3: 89 44

4、 24 04 mov %eax,0x4(%esp) 80483b7: 8b 45 08 mov 0x8(%ebp),%eax 80483ba: 89 04 24 mov %eax,(%esp) 80483bd: e8 d2 ff ff ff call 8048394 80483c2: c9 leave 80483c3: c3 ret 080483c4 : int main(void) 80483c4: 8d 4c 24 04 lea 0x4(%esp),%ecx 80483c8: 83 e4 f0 and $0xfffffff0,%esp 80483cb: ff 71 fc pushl -0x

5、4(%ecx) 80483ce: 55 push %ebp 80483cf: 89 e5 mov %esp,%ebp 80483d1: 51 push %ecx 80483d2: 83 ec 08 sub $0x8,%esp foo(2, 3); 80483d5: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 80483dc: 00 80483dd: c7 04 24 02 00 00 00 movl $0x2,(%esp) 80483e4: e8 c1 ff ff ff call 80483aa return 0; 80483e9: b8 00 00 00

6、 00 mov $0x0,%eax 80483ee: 83 c4 08 add $0x8,%esp 80483f1: 59 pop %ecx 80483f2: 5d pop %ebp 80483f3: 8d 61 fc lea -0x4(%ecx),%esp 80483f6: c3 ret . 要查看编译后的汇编代码,其实还有一种办法是 gcc -S main.c,这样只生成汇编代码 main.s,而不 生成二进制的目标文件。 整个程序的执行过程是 main 调用 foo,foo 调用 bar,我们用 gdb 跟踪程序的执行,直到 bar 函数中 的 int e = c + d;语句执行完毕准

7、备返回时,这时在 gdb 中打印函数栈帧。 (gdb) start . main () at main.c:14 14 foo(2, 3); (gdb) s foo (a=2, b=3) at main.c:9 9 return bar(a, b); (gdb) s bar (c=2, d=3) at main.c:3 3 int e = c + d; (gdb) disassemble Dump of assembler code for function bar: 0x08048394 : push %ebp 0x08048395 : mov %esp,%ebp 0x08048397 :

8、sub $0x10,%esp 0x0804839a : mov 0xc(%ebp),%edx 0x0804839d : mov 0x8(%ebp),%eax 0x080483a0 : add %edx,%eax 0x080483a2 : mov %eax,-0x4(%ebp) 0x080483a5 : mov -0x4(%ebp),%eax 0x080483a8 : leave 0x080483a9 : ret End of assembler dump. (gdb) si 0x0804839d 3 int e = c + d; (gdb) si 0x080483a0 3 int e = c

9、+ d; (gdb) si 0x080483a2 3 int e = c + d; (gdb) si 4 return e; (gdb) si 5 (gdb) bt #0 bar (c=2, d=3) at main.c:5 #1 0x080483c2 in foo (a=2, b=3) at main.c:9 #2 0x080483e9 in main () at main.c:14 (gdb) info registers eax 0x5 5 ecx 0xbff1c440 -1074674624 edx 0x3 3 ebx 0xb7fe6ff4 -1208061964 esp 0xbff1

10、c3f4 0xbff1c3f4 ebp 0xbff1c404 0xbff1c404 esi 0x8048410 134513680 edi 0x80482e0 134513376 eip 0x80483a8 0x80483a8 eflags 0x200206 PF IF ID cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) x/20 $esp 0xbff1c3f4: 0x00000000 0xbff1c6f7 0xb7efbdae 0x00000005 0xbff1c404: 0xbff1c41

11、4 0x080483c2 0x00000002 0x00000003 0xbff1c414: 0xbff1c428 0x080483e9 0x00000002 0x00000003 0xbff1c424: 0xbff1c440 0xbff1c498 0xb7ea3685 0x08048410 0xbff1c434: 0x080482e0 0xbff1c498 0xb7ea3685 0x00000001 (gdb) 这里又用到几个新的 gdb 命令。disassemble 可以反汇编当前函数或者指定的函数,单独用 disassemble 命令是反汇编当前函数,如果 disassemble 命令后

12、面跟函数名或地址则反汇编指定的函 数。以前我们讲过 step 命令可以一行代码一行代码地单步调试,而这里用到的 si 命令可以一条指令 一条指令地单步调试。info registers 可以显示所有寄存器的当前值。在 gdb 中表示寄存器名时前 面要加个$, 例如 p $esp 可以打印 esp 寄存器的值, 在上例中 esp 寄存器的值是 0xbff1c3f4, 所以 x/20 $esp 命令查看内存中从 0xbff1c3f4 地址开始的 20 个 32 位数。在执行程序时,操作系统为进程分配 一块栈空间来保存函数栈帧,esp 寄存器总是指向栈顶,在 x86 平台上这个栈是从高地址向低地址增

13、 长的,我们知道每次调用一个函数都要分配一个栈帧来保存参数和局部变量,现在我们详细分析这些 数据在栈空间的布局,根据 gdb 的输出结果图示如下29: 图图 19.1. 函数栈帧函数栈帧 图中每个小方格表示 4 个字节的内存单元,例如 b: 3 这个小方格占的内存地址是 0xbff1c4200xbff1c423,我把地址写在每个小方格的下边界线上,是为了强调该地址是内存单元的 起始地址。我们从 main 函数的这里开始看起: foo(2, 3); 80483d5: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp) 80483dc: 00 80483dd: c7 04 24 02 00 00 00 movl $0x2,(%esp) 80483e4: e8 c1 ff ff ff call 80483aa return 0; 80483e9: b8 00 00 00 00 mov $0x0,%eax 要调用函数 foo 先要把参数准备好,第二个参数保存在 esp+4 指向的内存位置,第一个参数保存在 esp 指向的内存位置,可见参数是从右向左依次压栈的。然后执行 call 指令,

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 科普知识

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号