函数调用进程,汇编与C语言关系

  对于以下顺序:


二个函数调用另七个函数,需先将被调用函数参数实参)希图好。然后施行call指令,实现了八个职责:

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;
}

栈:

在函数调用时,第3个进栈的是主函数中等高校函授数调用后的下一条指令(函数调用语句的下一条可施行语句)的地点,然后是函数的顺序参数,在大好些个的C编写翻译器中,参数是由右往左入栈的,然后是函数中的局地变量。注意静态变量是不入栈的。
当此次函数调用甘休后,局地变量先出栈,然后是参数,最后栈顶指针指向最开端存的地方,也正是主函数中的下一条指令,程序由该点继续运维。

当产生函数调用的时候,栈空间中寄存的数目是这么的:

  • 1、调用者函数把被调函数所须要的参数依照与被调函数的形参顺序相反的一一压入栈中,即:从右向左依次把被调函数所需求的参数压入栈;
  • 2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地点正是重临地址压入栈中(这一个压栈操作隐含在call指令中);
  • 3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push
    ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov
    ebp,esp);
  • 4、在被调函数中,从ebp的岗位处初始寄放被调函数中的局地变量和一时半刻变量,并且那些变量的地方根据定义时的逐个依次减小,即:那一个变量的地址是依照栈的拉开趋势排列的,先定义的变量先入栈,后定义的变量后入栈;

产生函数调用时,入栈的相继为:

参数N<br /> 参数N-1 <br />参数N-1<br />参数N-2<br
/>…..<br />参数3<br />参数2<br />参数1<br
/>函数再次来到地址<br />上一层调用函数的EBP/BP<br
/>局地变量1<br />局地变量2<br />….<br
/>局地变量N<br />

函数调用栈如下图所示:

图片 1

解释:
//EBP
基址指针,是保存调用者函数的地址,总是指向函数栈栈底,ESP被调函数的指针,总是指向函数栈栈顶。

先,将调用者函数的EBP入栈(pushebp),然后将调用者函数的栈顶指针ESP赋值给被调函数的EBP(作为被调函数的栈底,movebp,esp),此时,EBP存放器处于贰个百般关键的职位,该贮存器中存放着三个地方(原EBP入栈后的栈顶),以该地址为基准,向上(栈底方向)能获得重回地址、参数值,向下(栈顶方向)能博得函数的局地变量值,而该地址处又贮存着上一层函数调用时的EBP值;
相似规律,SS:[ebp+4]处为被调函数的回来地址,SS:[EBP+8]处为传送给被调函数的第贰个参数(最终叁个入栈的参数,此处借使其占用4字节内部存款和储蓄器)的值,SS:[EBP-4]处为被调函数中的第一个部分变量,SS:[EBP]处为上一层EBP值;由于EBP中的地址处总是”上一层函数调用时的EBP值”,而在每一层函数调用中,都能经过当时的EBP值”向上(栈底方向)能获得重回地址、参数值,向下(栈顶方向)能博得被调函数的局地变量值”;
这样递归,就造成了函数调用栈;
Eg函数内有个别变量布局示例:

#include <stdio.h>
#include <string.h>
struct C
{
  int a;
  int b;
  int c;
};
int test2(int x, int y, int z)
{
  printf("hello,test2\n");
  return 0;
}
int test(int x, int y, int z)
{
  int a = 1;
  int b = 2;
  int c = 3;
  struct C st;
  printf("addr x = %u\n",(unsigned int)(&x));
  printf("addr y = %u\n",(unsigned int)(&y));
  printf("addr z = %u\n",(unsigned int)(&z));
  printf("addr a = %u\n",(unsigned int)(&a));
  printf("addr b = %u\n",(unsigned int)(&b));
  printf("addr c = %u\n",(unsigned int)(&c));
  printf("addr st = %u\n",(unsigned int)(&st));
  printf("addr st.a = %u\n",(unsigned int)(&st.a));
  printf("addr st.b = %u\n",(unsigned int)(&st.b));
  printf("addr st.c = %u\n",(unsigned int)(&st.c));
  return 0;
} int main(int argc, char** argv)
{
  int x = 1;
  int y = 2;
  int z = 3;
  test(x,y,z);
  printf("x = %d; y = %d; z = %d;\n", x,y,z);
  memset(&y, 0, 8);
  printf("x = %d; y = %d; z = %d;\n", x,y,z);
  return 0;
}

打印输出如下:

addr x = 3220024704
addr y = 3220024708
addr z = 3220024712
addr a = 3220024684
addr b = 3220024680
addr c = 3220024676
addr st = 3220024664
addr st.a = 3220024664
addr st.b = 3220024668
addr st.c = 3220024672
x = 1; y = 2; z = 3;
x = 0; y = 0; z = 3;

局地变量在栈中布局暗指图:

图片 2

该图中的局地变量都是在该示例中定义的:

图片 3

以此图形中显示的是三个顶级的函数调用栈的内存布局;
<div style=”color:blue”>访问函数的一些变量和做客函数参数的界别:

一对变量总是通过将ebp减去偏移量来拜候,函数参数总是通过将ebp加上偏移量来会见。对于三16个人变量来讲,第4个部分变量位于ebp-4,第一个位于ebp-8,依此类推,三16位局部变量在栈中变成四个逆序数组;第叁个函数参数位于ebp+8,第一个位于ebp+12,就那样推算,叁拾二人函数参数在栈中变成二个正序数组。</div>
Eg、钻探函数调用进度:

#include <stdio.h>

int bar(int c,int d)
{
        int e=c+d;
        return e;
}

int foo(int a,int b)
{
        return bar(a,b);
}

int main(int argc,int argv)
{
        foo(2,3);
        return 0;
}

地点是一个一点也不细略的函数调用进度,整个程序的执行进程是main调用foo,foo调用bar。
//查看反汇编文件(要翻开编写翻译后的汇编代码,其实还会有一种方式是gcc -S
text_stack.c,那样只生成汇编代码text_stack.s,而不生成二进制的目的文件。)

root@wangye:/home/wangye# gcc text_stack.c -g
root@wangye:/home/wangye# objdump -dS a.out 

反汇编结果很短,下边只列出我们关注的部分。

08048394 <bar>:
#include <stdio.h>

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 45 0c                mov    0xc(%ebp),%eax
 804839d:   8b 55 08                mov    0x8(%ebp),%edx
 80483a0:   8d 04 02                lea    (%edx,%eax,1),%eax
 80483a3:   89 45 fc                mov    %eax,-0x4(%ebp)
    return e;
 80483a6:   8b 45 fc                mov    -0x4(%ebp),%eax
}
 80483a9:   c9                      leave  
 80483aa:   c3                      ret    

080483ab <foo>:

int foo(int a,int b)
{
 80483ab:   55                      push   %ebp
 80483ac:   89 e5                   mov    %esp,%ebp
 80483ae:   83 ec 08                sub    $0x8,%esp
    return bar(a,b);
 80483b1:   8b 45 0c                mov    0xc(%ebp),%eax
 80483b4:   89 44 24 04             mov    %eax,0x4(%esp)
 80483b8:   8b 45 08                mov    0x8(%ebp),%eax
 80483bb:   89 04 24                mov    %eax,(%esp)
 80483be:   e8 d1 ff ff ff          call   8048394 <bar>
}
 80483c3:   c9                      leave  
 80483c4:   c3                      ret    

080483c5 <main>:

int main(int argc,int argv)
{
 80483c5:   55                      push   %ebp
 80483c6:   89 e5                   mov    %esp,%ebp
 80483c8:   83 ec 08                sub    $0x8,%esp
    foo(2,3);
 80483cb:   c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)
 80483d2:   00 
 80483d3:   c7 04 24 02 00 00 00    movl   $0x2,(%esp)
 80483da:   e8 cc ff ff ff          call   80483ab <foo>
    return 0;
 80483df:   b8 00 00 00 00          mov    $0x0,%eax
}

//我们用gdb追踪程序的实践,直到bar函数中的int e = c +
d;语句试行达成策动回来时,那时在gdb中打字与印刷函数栈帧。

wangye@wangye:~$ gdb text_stack 
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/wangye/text_stack...done.
(gdb) start
Temporary breakpoint 1 at 0x80483cb: file text_stack.c, line 16.
Starting program: /home/wangye/text_stack 

Temporary breakpoint 1, main (argc=1, argv=-1073744732) at text_stack.c:16
16      foo(2,3);
(gdb) s
foo (a=2, b=3) at text_stack.c:11
11      return bar(a,b);
(gdb) s
bar (c=2, d=3) at text_stack.c:5
5       int e=c+d;
(gdb) disassemble 
Dump of assembler code for function bar:
0x08048394 <bar+0>: push   %ebp
0x08048395 <bar+1>: mov    %esp,%ebp
0x08048397 <bar+3>: sub    $0x10,%esp
0x0804839a <bar+6>: mov    0xc(%ebp),%eax
0x0804839d <bar+9>: mov    0x8(%ebp),%edx
0x080483a0 <bar+12>:    lea    (%edx,%eax,1),%eax
0x080483a3 <bar+15>:    mov    %eax,-0x4(%ebp)
0x080483a6 <bar+18>:    mov    -0x4(%ebp),%eax
0x080483a9 <bar+21>:    leave  
0x080483aa <bar+22>:    ret    
End of assembler dump.
(gdb) si
0x0804839d  5       int e=c+d;
(gdb) si
0x080483a0  5       int e=c+d;
(gdb) si
0x080483a3  5       int e=c+d;
(gdb) si
6       return e;
(gdb) si
7   }
(gdb) bt
#0  bar (c=2, d=3) at text_stack.c:7
#1  0x080483c3 in foo (a=2, b=3) at text_stack.c:11
#2  0x080483df in main (argc=1, argv=-1073744732) at text_stack.c:16
(gdb) info re
record     registers  
(gdb) info regi
eax            0x5  5
ecx            0x4c2f5d43   1278172483
edx            0x2  2
ebx            0xb7fcaff4   -1208176652
esp            0xbffff3c8   0xbffff3c8
ebp            0xbffff3d8   0xbffff3d8
esi            0x0  0
edi            0x0  0
eip            0x80483a9    0x80483a9 <bar+21>
eflags         0x282    [ SF IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51
(gdb) info regi
eax            0x5  5
ecx            0x4c2f5d43   1278172483
edx            0x2  2
ebx            0xb7fcaff4   -1208176652
esp            0xbffff3c8   0xbffff3c8
ebp            0xbffff3d8   0xbffff3d8
esi            0x0  0
edi            0x0  0
eip            0x80483a9    0x80483a9 <bar+21>
eflags         0x282    [ SF IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51
(gdb) x/20 $esp
0xbffff3c8: -1073744904 134513689   -1208175868 5
0xbffff3d8: -1073744920 134513603   2   3
0xbffff3e8: -1073744904 134513631   2   3
0xbffff3f8: -1073744776 -1209406298 1   -1073744732
0xbffff408: -1073744724 -1208084392 -1073744800 -1

此地大家又用了多少个新的gdb命令,轻便解释一下:info
registers能够呈现全体寄放器的此时此刻值。在gdb中意味着贮存器名时前边要加个$,比如p
$esp能够打字与印刷esp寄放器的值,在上例中esp寄放器的值是0xbffff3c8,所以x/20
$esp命令查看内部存款和储蓄器中从0xbffff3c8
地址开端的二十个叁10位数。在推行顺序时,操作系统为经过分配一块栈空间来保存函数栈帧,esp寄放器总是指向栈顶,在x86平台上那一个栈是从高地址向低地址增加的,大家精通每一回调用三个函数都要分配三个栈帧来保存参数和局地变量,现在大家详细解析那些数据在栈空间的布局,依据gdb的出口结果图示如下:

图片 4

图中每个小方格代表4个字节的内部存款和储蓄器单元,举例b:
3这一个小方格占的内部存款和储蓄器地址是0xbffff3f4~0xbffff3f7,把地方写在每一种小方格的下边界线上,是为器重申该地址是内部存款和储蓄器单元的开场所址。咱们从main函数的此处起始看起:

foo(2,3);  
80483cb:    c7 44 24 04 03 00 00    movl   $0x3,0x4(%esp)  
80483d2:    00   
80483d3:    c7 04 24 02 00 00 00    movl   $0x2,(%esp)  
80483da:    e8 cc ff ff ff          call   80483ab <foo>  
return 0;  
80483df:    b8 00 00 00 00          mov    $0x0,%eax  

要调用函数foo先要把参数希图好,第二个参数保存在esp+4指向的内部存款和储蓄器地方,首个参数保存在esp指向的内部存款和储蓄器地方,可知参数是从右向左依次压栈的。然后实践call指令,那些命令有三个功能:

  • foo函数调用完今后要回去到call的下一条指令继续实施,所以把call的下一条指令的地方134513631压栈,同期把esp的值减4,esp的值未来是0xbffff3ec。
  • 修改程序计数器eip,跳转到foo函数的开首实践。

于今看foo函数的汇编代码:

080483ab <foo>:  

int foo(int a,int b)  
{  
 80483ab:   55                      push   %ebp  
 80483ac:   89 e5                   mov    %esp,%ebp  
 80483ae:   83 ec 08                sub    $0x8,%esp  

push
%ebp指令把ebp寄放器的值压栈,同期把esp的值减4。esp的值以后是0xbff1c414,下一条指令把这么些值传送给ebp寄放器。这两条指令合起来是把本来ebp的值保存在栈上,然后又给ebp赋了新值。在种种函数的栈帧中,ebp指向栈底,而esp指向栈顶,在函数实践进程中esp随着压栈和出栈操作随时变动,而ebp是不动的,函数的参数和部分变量都以通过ebp的值加上二个偏移量来拜访,比方foo函数的参数a和b分别通过ebp+8和ebp+12来拜谒。所以上面包车型客车下令把参数a和b再一次压栈,为调用bar函数做准备,然后把重临地址压栈,调用bar函数:

return bar(a,b);  
 80483b1:   8b 45 0c                mov    0xc(%ebp),%eax  
 80483b4:   89 44 24 04             mov    %eax,0x4(%esp)  
 80483b8:   8b 45 08                mov    0x8(%ebp),%eax  
 80483bb:   89 04 24                mov    %eax,(%esp)  
 80483be:   e8 d1 ff ff ff          call   8048394 <bar>  
}  
 80483c3:   c9                      leave    
 80483c4:   c3                      ret   

今昔看bar函数的吩咐:

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 45 0c                mov    0xc(%ebp),%eax  
 804839d:   8b 55 08                mov    0x8(%ebp),%edx  
 80483a0:   8d 04 02                lea    (%edx,%eax,1),%eax  
 80483a3:   89 45 fc                mov    %eax,-0x4(%ebp)  

此次又把foo函数的ebp压栈保存,然后给ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+12各自能够访谈参数c和d。bar函数还应该有多少个有个别变量e,能够通过ebp-4来拜谒。所以前边几条指令的意思是把参数c和d收取来存在寄放器中做加法,总结结果保存在eax寄放器中,再把eax存放器存回局地变量e的内部存款和储蓄器单元。
在gdb中得以用bt命令和frame命令查看每层栈帧上的参数和一部分变量,现在能够分解它的干活原理了:纵然本人当下在bar函数中,小编能够透过ebp找到bar函数的参数和有个别变量,也得以找到foo函数的ebp保存在栈上的值,有了foo函数的ebp,又有什么不可找到它的参数和一部分变量,也能够找到main函数的ebp保存在栈上的值,因而各层函数栈帧通过保留在栈上的ebp的值串起来了。
当今看bar函数的归来指令:

return e;  
 80483a6:   8b 45 fc                mov    -0x4(%ebp),%eax  
}  
 80483a9:   c9                      leave    
 80483aa:   c3                      ret   

bar函数有三个int型的再次来到值,这一个重返值是因此eax寄放器传递的,所以率先把e的值读到eax存放器中。然后实施leave指令,那个命令是函数开端的push
%ebp和mov %esp,%ebp的逆操作:

  • 把ebp的值赋给esp,未来esp的值是0xbffff3d8。
  • 明天esp所指向的栈顶保存着foo函数栈帧的ebp,把这几个值苏醒给ebp,同时esp扩张4,esp的值产生0xbffff3dc。

最终是ret指令,它是call指令的逆操作:

  • 前些天esp所指向的栈顶保存着赶回地址,把这几个值恢复生机给eip,相同的时间esp扩张4,esp的值产生0xbffff3e0。
  • 修改了先后计数器eip,因而跳转到重返地址0x80483c2继续试行。

地址0x80483c2处是foo函数的回来指令:

80483c3:    c9                      leave    
80483c4:    c3                      ret   

双重雷同的进度,又重回到了main函数。注意函数调用和重回经过中的这几个法规:

  • 参数压栈传递,何况是从右向左依次压栈。
  • ebp总是指向当前栈帧的栈底。
  • 重返值通过eax贮存器传递。

那些准则实际不是系统布局所强加的,ebp寄放器并非必须那样用,函数的参数和再次来到值亦不是必须这么传,只是操作系统和编写翻译器接纳了以那样的法子贯彻C代码中的函数调用,那名称为CallingConvention,Calling
Convention是操作系统二进制接口标准(ABI,Application
BinaryInterface)的一局地。

1.将调用函数下一条指令入栈,被调函数再次来到后将取那条指令继续实施.

  在编写翻译时加上-g选项,用objdump反汇编时能够把C代码和汇编代码穿插起来显示:

2.修改命令指针寄存器eip的值,使其针对性被调函数的进行地点.

图片 5

二个函数被调用,首先要马到功成以下动作创立新栈帧):

反汇编的结果相当长以下是截取要解析的一些:

将调用函数的栈帧栈底地址入栈,就要bp贮存器的值压入调用栈中,以后回去时用。

图片 6

设置被调函数的栈帧栈底地址ebp=esp。

图片 7


图片 8

函数调用和重返进度的条条框框:该法则与操作系统和编写翻译器相关)

  整个程序的实施进程是main调用foo, foo调用bar,
用gdb追踪程序的实行,直到bar函数中的int e = c +
d;语句实行完成盘算重临时,那时在gdb中打印函数栈帧。

  • 参数压栈传递,而且是从右向左;

  • ebp总是指向栈帧的栈底;

  • 重临值通过eax贮存器传递;

  • 被调用函数栈帧的栈底保存着调用函数栈帧的栈底;

  • 被调用函数栈帧的栈底上三个存款和储蓄单元保存着该函数再次回到后下一条指令的地点。

    图片 9

图片 10


图片 11

用上边包车型地铁代码来解释以上法则。

 

int bar(int c, int d)

disassemble能够反汇编当前函数恐怕钦点的函数,单独用disassemble是反汇编当前函数,如果disassemble后面跟函数名或地址则反汇编钦命的函数。

{

s(step)命令能够一行代码一行代码的单步调节和测验,而si命令能够一条指令一条指令的单步调节和测试。bt
列出调用栈

int e = c +d;

info
registers能够体现全部寄放器的当下值。在gdb中意味寄放器名时前面要加个$,譬喻p
$esp命令查看esp寄放器的值(上航海用体育场所未有显得该命令),在上例中esp贮存器的值是0xbff1c3f4,所以x/20
$esp命令查看内部存款和储蓄器中从0xbff1c3f4地址初始的21个三十一个人数。在执行顺序时,操作系统为经过分配一块栈空间来积存函数栈帧,esp贮存器总是指向栈顶,,在x86平台上这么些栈是从高地址向低地址增加的,每便调用叁个函数都要分配贰个栈帧来储存参数和一部分变量,今后大家深入分析这一个多少是怎么存款和储蓄的,依据gdb的输出结果图示如下:

return e;

图片 12

}

  途中种种小方格占4个字节,比如b:3以此方格的内部存储器地址是0xbf822d20~0xbf822d23。大家从main函数的此处开端看起:

int foo(int a, int b)

图片 13

{

  要调用函数foo先要把参数希图好,第叁个参数保存在esp+4所针对的内部存储器地点,第三个参数保存在esp所指向的内存地点,可见参数是从右往左叁次压栈的。然后实施call指令,那几个命令有四个功效:

return bar(a, b);

    1.
foo函数调用完事后要回到call的下一条指令继续施行,所以把call的下一条指令的地址0x80483e9压栈,同期把esp的值  减4,esp的值未来是0xbf822d18。

}

    2. 改造程序计数器eip, 跳转到foo函数的初叶实践。

int main(void)

  未来看foo函数的汇编代码:

{

图片 14

foo(2, 3);

  首先将ebp贮存器的值压栈,相同的时间把esp的值再减4,esp的值未来是0xbf822d14,然后把这几个值传送给ebp寄放器。换句话说就是把本来ebp的值保存在栈上,然后又给ebp赋了新值。在每一种函数的栈帧中,ebp指向栈底,esp指向栈顶,在函数施行进度中esp随着压栈和出栈操作随时变动,而ebp是不动的,函数的参数和有个别变量都以经过ebp的值加上一个偏移量来拜候的,举个例子foo函数的参数a和b分别通过ebp+8和ebp+12来做客,所以上边包车型大巴一声令下把参数a和b再一次压栈,为调用bar函数做筹算,然后把再次回到地址压栈,调用bar函数:

return 0;

图片 15

}

图片 16

在编写翻译时抬高-g选项,用objdump反汇编时能够把C代码和汇编代码穿插起来呈现,那样C代码和汇编代码的照料关系看得更驾驭,这里只列出我们关怀的局地:指令地址每一遍编译都不如只作参照。)

  今后看bar函数的指令:

080483b4 <bar>:

图片 17

#include <stdio.h>

  此番又把foo函数的ebp压栈保存,然后给ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+11个别能够访问参数c和d。bar函数还也许有贰个局地变量e,能够通过ebp-4来做客。所以前面几条指令的意思是把参数c和d抽取来存在存放器中做加法,add指令的持筹握算结果保存在eax贮存器中,再把eax存放器存回局地变量e的内部存款和储蓄器单元。

int bar(int c, int d)

  未来得以解释为何在gdb中可以用bt命令和frame命令查看各样栈帧上的参数和局地变量了:要是自己日前在bar函数中,小编能够透过ebp找到bar函数的参数和一些变量,也得以找到foo函数的ebp保存在栈上的值,有个foo函数的ebp,又有什么不可找到它的参数和有些变量,也可以找到main函数的ebp保存在栈上的值,因而各函数的栈帧通过保留在栈上的ebp的值串起来了。以后看bar函数的归来命令:

{

图片 18

80483b4:55 push %ebp

  bar函数有叁个int型的重返值,那些再次回到值是透过eax存放器传递的,所以率先把e的值读到eax贮存器中。然后实践leave指令,那几个命令是函数开端的push
%ebp和mov %esp, %ebp的逆操作:

80483b5:89 e5 mov %esp,%ebp

    1. 把ebp的值赋给esp,未来esp的值是0xbf822d04。

80483b7:83 ec 10 sub $0x10,%esp

    2.
现行反革命esp所指向的栈顶保存着foo函数栈帧的ebp,把这几个值恢复生机给ebp,同不时候esp扩张4,今后esp的值是0xbf822d08。

int e = c +d;

  最终是ret指令,它是call指令的逆操作:

80483ba:8b 45 0c mov 0xc(%ebp),%eax

    1.
现行反革命esp所指向的栈顶保存着回去地址,把那么些值恢复生机给eip,同偶然间esp扩展4,以后esp的值是0xbf822d0c。

80483bd:8b 55 08 mov 0x8(%ebp),%edx

    2. 修改了程序计数器eip,因而跳转到重返地址0x80483c2继续试行。

80483c0:8d 04 02 lea (%edx,%eax,1),%eax

  地址0x80483c2处是foo函数的回来指令:

80483c3:89 45 fc mov %eax,-0x4(%ebp)

图片 19

return e;

  重复雷同的进程,就又回来到了main函数。注意函数调用和重返进程中的那些法则:

80483c6:8b 45 fc mov -0x4(%ebp),%eax

    1. 参数压栈传递,而且是从右向左依次压栈。
    2.  ebp 总是指向栈帧的栈底。
    3. 再次回到值通过 eax 存放器传递。
  这个法规并非系统布局所强加的, ebp
寄放器实际不是必须这么用,函数的参数和重回值亦非必须那样传,只是操作系统和编写翻译器选拔了以如此的点子贯彻C代码中的函数调用,那叫做Calling
Convention,除了Calling
Convention之外,操作系统还亟需明确繁多C代码和二进制指令之间的接口标准,统称为ABI(Application
Binary Interface)。

}

80483c9:c9 leave

80483ca:c3 ret

080483cb <foo>:

int foo(int a, int b)

{

80483cb:55 push %ebp

80483cc:89 e5 mov %esp,%ebp

80483ce:83 ec 08 sub $0x8,%esp

return bar(a, b);

80483d1:8b 45 0c mov 0xc(%ebp),%eax

80483d4:89 44 24 04 mov %eax,0x4(%esp)

80483d8:8b 45 08 mov 0x8(%ebp),%eax

80483db:89 04 24 mov %eax,(%esp)

80483de:e8 d1 ff ff ff call 80483b4 <bar>

}

80483e3:c9 leave

80483e4:c3 ret

080483e5 <main>:

int main(void)

{

80483e5:55 push %ebp

80483e6:89 e5 mov %esp,%ebp

80483e8:83 ec 08 sub $0x8,%esp

foo(2, 3);

80483eb:c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)

80483f2:00

80483f3:c7 04 24 02 00 00 00 movl $0x2,(%esp)

80483fa:e8 cc ff ff ff call 80483cb <foo>

return 0;

80483ff:b8 00 00 00 00 mov $0x0,%eax

}


(gdb) info registers bar函数的栈帧)

esp 0xbffff718

ebp 0xbffff728

foo函数的栈帧

esp 0xbffff730

ebp 0xbffff738

main函数的栈帧

esp 0xbffff740

ebp 0xbffff748

图片 20


以上是函数调用进度中的栈帧框架,未来看怎么样回到:

bar函数的回来进程:

mov -0x4(%ebp),%eax;将e的值赋给eax。

leave;它是push %ebp和mov
%esp,%ebp的逆操作。把ebp的值赋给esp,今后esp的值是0xbffff728,ebp和esp都指向bar的栈底;栈底存放的难为foo函数的ebp。弹出ebp,今后ebp指向了foo函数的栈底,相同的时间esp加4.esp指向了foo函数的栈顶。foo函数的栈顶贮存的是foo
函数中call指令的下一条指令地址80483e3。到此截至,今后早就回到到foo函数的栈帧。

ret;该指令是call指令的逆操作;把esp的值复苏给eip,同期esp加4。修改了程序计数器eip,现在跳转到
地址80483e3,该处指令是leave ;ret;重复上述进度即重回到main函数。

main函数调用foo,foo调用bar,bar重回到foo,foo重回到main整个调用进程甘休。

本文出自 “note”
博客,请务必保留此出处http://gcfred.blog.51cto.com/7948335/1302721

http://www.bkjia.com/Cyy/616940.htmlwww.bkjia.comtruehttp://www.bkjia.com/Cyy/616940.htmlTechArticle一个函数调用另一个函数,需先将被调用函数参数实参)准备好。然后执行call指令,完成了两个任务:
1.将调用函数下一条指令入栈,被调…

相关文章