浅析函数调用过程
来源:互联网 发布:利为汇seo视频教程 编辑:程序博客网 时间:2024/06/02 00:52
一个函数到底是怎么调用的,返回值是如何返回的,这里面学问真的很多,让我们来小小的分析一下。。。。
我们看一个很简单的C代码,通过gdb调试来分析函数调用过程。
平台和工具:ubuntu12.04+gcc 4.6.3+GNU gdb 7.4-2012.04
cs.c:
int add(int a,int b){return a+b;}int main(){int a=1;int b=2;int c=add(a,b);}
编译: gcc -g cs.c
调试:gdb a.out
我们看一下:main函数的汇编代码:
(gdb) disas mainDump of assembler code for function main:01 0x080483c1 <+0>:push %ebp02 0x080483c2 <+1>:mov %esp,%ebp03 0x080483c4 <+3>:sub $0x18,%esp04 0x080483c7 <+6>:movl $0x1,-0xc(%ebp)05 0x080483ce <+13>:movl $0x2,-0x8(%ebp)06 0x080483d5 <+20>:mov -0x8(%ebp),%eax07 0x080483d8 <+23>:mov %eax,0x4(%esp)08 0x080483dc <+27>:mov -0xc(%ebp),%eax09 0x080483df <+30>:mov %eax,(%esp)10 0x080483e2 <+33>:call 0x80483b4 <add>11 0x080483e7 <+38>:mov %eax,-0x4(%ebp)12 0x080483ea <+41>:leave 13 0x080483eb <+42>:ret End of assembler dump.
为了方便我在每行前面加了序号(本来是没有的)
堆栈情况如下:
图1
下面的数字代表main函数反编译的行号
1.帧指针ebp入栈
2.帧指针ebp指向当前栈指针esp
3.分配24(ox18)字节空间,所以esp下移24字节,如图1
4,5.局部变量a,b入栈,这里可以看到变量分配是按地址增大方向,所以虽然a先定义,但a的地址小,如图1
这里,你可能会问那C在哪呢?按地址增大方向,c应该在b的上面,说的很对,图1中
ebp-4是不是什么都没有,那就是c的所在地址,我们将在11行得到证明
6,7.将add的参数b入栈
8,9.将add的参数a入栈,如图1
10.调用add子程序,我们反编译add函数,
(gdb) disas addDump of assembler code for function add: 0x080483b4 <+0>:push %ebp 0x080483b5 <+1>:mov %esp,%ebp 0x080483b7 <+3>:mov 0xc(%ebp),%eax 0x080483ba <+6>:mov 0x8(%ebp),%edx 0x080483bd <+9>:add %edx,%eax 0x080483bf <+11>:pop %ebp 0x080483c0 <+12>:ret End of assembler dump.我们看到调用的是add函数的地址: 0x080483b4
call做了两个操作,一,将返回地址入栈,那返回地址是什么呢?
返回地址,顾名思义就是调用子程序后返回的地址,那就应该是call下一条指令
的地址,即 0x080483e7 二,跳转到子程序的起始处
调用add后,堆栈如下图所以。
图2
我们将验证图2的返回地址就是0x080483e7
给两函数添加断点
gdb) break mainBreakpoint 1 at 0x80483c7: file cs.c, line 8.(gdb) break addBreakpoint 2 at 0x80483b7: file cs.c, line 3.(gdb) rStarting program: /home/yihaibo/c/a.out Breakpoint 1, main () at cs.c:88int a=1;(gdb) n9int b=2;(gdb) n10int c=add(a,b);(gdb) disas mainDump of assembler code for function main: 0x080483c1 <+0>:push %ebp 0x080483c2 <+1>:mov %esp,%ebp 0x080483c4 <+3>:sub $0x18,%esp 0x080483c7 <+6>:movl $0x1,-0xc(%ebp) 0x080483ce <+13>:movl $0x2,-0x8(%ebp)=> 0x080483d5 <+20>:mov -0x8(%ebp),%eax 0x080483d8 <+23>:mov %eax,0x4(%esp) 0x080483dc <+27>:mov -0xc(%ebp),%eax 0x080483df <+30>:mov %eax,(%esp) 0x080483e2 <+33>:call 0x80483b4 <add> 0x080483e7 <+38>:mov %eax,-0x4(%ebp) 0x080483ea <+41>:leave 0x080483eb <+42>:ret End of assembler dump.调试过程中,可通过disas main,参看执行到main函数哪条指令(箭头所指地址)
(gdb) stepi0x080483d810int c=add(a,b);(gdb) disas mainDump of assembler code for function main: 0x080483c1 <+0>:push %ebp 0x080483c2 <+1>:mov %esp,%ebp 0x080483c4 <+3>:sub $0x18,%esp 0x080483c7 <+6>:movl $0x1,-0xc(%ebp) 0x080483ce <+13>:movl $0x2,-0x8(%ebp) 0x080483d5 <+20>:mov -0x8(%ebp),%eax=> 0x080483d8 <+23>:mov %eax,0x4(%esp) 0x080483dc <+27>:mov -0xc(%ebp),%eax 0x080483df <+30>:mov %eax,(%esp) 0x080483e2 <+33>:call 0x80483b4 <add> 0x080483e7 <+38>:mov %eax,-0x4(%ebp) 0x080483ea <+41>:leave 0x080483eb <+42>:ret End of assembler dump.可通过命令:stepi,执行汇编代码一条指令,和上面对比,发现的确执行一条指令。
(gdb) disas mainDump of assembler code for function main: 0x080483c1 <+0>:push %ebp 0x080483c2 <+1>:mov %esp,%ebp 0x080483c4 <+3>:sub $0x18,%esp 0x080483c7 <+6>:movl $0x1,-0xc(%ebp) 0x080483ce <+13>:movl $0x2,-0x8(%ebp) 0x080483d5 <+20>:mov -0x8(%ebp),%eax 0x080483d8 <+23>:mov %eax,0x4(%esp) 0x080483dc <+27>:mov -0xc(%ebp),%eax 0x080483df <+30>:mov %eax,(%esp)=> 0x080483e2 <+33>:call 0x80483b4 <add> 0x080483e7 <+38>:mov %eax,-0x4(%ebp) 0x080483ea <+41>:leave 0x080483eb <+42>:ret End of assembler dump.(gdb) stepiadd (a=1, b=2) at cs.c:22{(gdb) print /x *(int*)($ebp-28)$2 = 0x80483e7通过多次stepi,当执行call指令时,我们可以打印出返回地址的值。
很容易计算出,返回地址所在栈地址是($ebp-28),所以通过print /x *(int*)($ebp-28)命令打印出($ebp-28)中存储的值(x 16进制),我们发现的确是0x080483e7,的确是call的下一条指令所在的地址。
这时程序将跳转到add子程序,
(gdb) disas addDump of assembler code for function add:=> 0x080483b4 <+0>:push %ebp 0x080483b5 <+1>:mov %esp,%ebp 0x080483b7 <+3>:mov 0xc(%ebp),%eax 0x080483ba <+6>:mov 0x8(%ebp),%edx 0x080483bd <+9>:add %edx,%eax 0x080483bf <+11>:pop %ebp 0x080483c0 <+12>:ret End of assembler dump.跳转之后的堆栈如图2所示。
我们看到:
mov 0xc(%ebp),%eax 取得形参b的值2
mov 0x8(%ebp),%edx 取得形参a的值1
然后将两值相加,此时%eax中的值就是add的返回值。
pop %ebp 弹出%ebp
ret 弹出返回地址,并跳转到返回地址,此时堆栈有恢复到图1。
指令执行如下:
(gdb) disas mainDump of assembler code for function main: 0x080483c1 <+0>:push %ebp 0x080483c2 <+1>:mov %esp,%ebp 0x080483c4 <+3>:sub $0x18,%esp 0x080483c7 <+6>:movl $0x1,-0xc(%ebp) 0x080483ce <+13>:movl $0x2,-0x8(%ebp) 0x080483d5 <+20>:mov -0x8(%ebp),%eax 0x080483d8 <+23>:mov %eax,0x4(%esp) 0x080483dc <+27>:mov -0xc(%ebp),%eax 0x080483df <+30>:mov %eax,(%esp) 0x080483e2 <+33>:call 0x80483b4 <add>=> 0x080483e7 <+38>:mov %eax,-0x4(%ebp) 0x080483ea <+41>:leave 0x080483eb <+42>:ret End of assembler dump.此时将add返回值eax赋给-0x4(%ebp),即上面所说c的地址
(gdb) print &c$6 = (int *) 0xbffff0e4(gdb) print $ebp-4$7 = (void *) 0xbffff0e4(gdb) print /x *(int*)($ebp-4)$4 = 0x3(gdb) print c$5 = 3通过上面我们可以看出c的地址的确是$ebp-4,在本次实验中eax是函数的返回值
这个过程分析就over了.....
- 浅析函数调用过程
- 深入理解C语言----函数调用过程浅析
- 浅析函数调用栈
- 浅析函数调用栈
- 浅析远程过程调用 RPC
- 浅析远程过程调用 RPC
- 浅析远程过程调用 RPC
- 浅析远程过程调用 RPC
- 浅析远程过程调用 RPC
- 浅析c++的函数调用
- 进程 过程调用 函数调用
- 【C++】【CPR】浅析远程过程调用 RPC
- 【C++】【CPR】浅析远程过程调用 RPC
- Wayland中的跨进程过程调用浅析
- Wayland中的跨进程过程调用浅析
- 浅析RPC远程过程调用基本原理
- Wayland中的跨进程过程调用浅析
- 函数调用过程
- [有向环] hdu 4324 Triangle LOVE
- openstack nova 基础知识——rpc回调机制(callback)
- 利用windows sockets实现TCP通讯示例程序
- WF运行时服务介绍
- 2012.8.20
- 浅析函数调用过程
- Servlet程序
- hdu 3584 (三维树状数组模板 )
- openstack nova 基础知识——从源码看一个服务是如何启动的
- POJ 1011: Sticks
- rman配置参数详解
- openstack nova 基础知识——scheduler的filter和weight
- 我的第一篇文章
- 亲测:编译php可配合apache运行cgi脚本