[汇编与C语言关系]一.函数调用

  对于以下顺序:

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选项,用objdump反汇编时能够把C代码和汇编代码穿插起来突显:

C语言 1

反汇编的结果很短以下是截取要分析的某个:

C语言 2

C语言 3

C语言 4

  整个程序的推行进度是main调用foo, foo调用bar,
用gdb跟踪程序的履行,直到bar函数中的int e = c +
d;语句执行实现准备赶回时,这时在gdb中打字与印刷函数栈帧。

C语言 5

C语言 6

 

disassemble能够反汇编当前函数或许钦定的函数,单独用disassemble是反汇编当前函数,假若disassemble后面跟函数名或地方则反汇编内定的函数。

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

C语言,info
registers可以显示全数寄存器的此时此刻值。在gdb中意味寄存器名时前边要加个$,例如p
$esp命令查看esp寄存器的值(上海体育场面没有出示该命令),在上例中esp寄存器的值是0xbff1c3f4,所以x/20
$esp命令查看内部存款和储蓄器中从0xbff一c三f四地址早先的十多个30位数。在实施顺序时,操作系统为经过分配一块栈空间来储存函数栈帧,esp寄存器总是指向栈顶,,在x捌六平台上那些栈是从高地址向低地址增加的,每一回调用二个函数都要分配一个栈帧来储存参数和一些变量,今后我们分析这么些多少是怎么存款和储蓄的,依据gdb的出口结果图示如下:

C语言 7

  途中每一种小方格占多少个字节,例如b:3以此方格的内部存款和储蓄器地址是0xbf82二d20~0xbf82贰d2三。大家从main函数的此处开端看起:

C语言 8

  要调用函数foo先要把参数准备好,第3个参数保存在esp+四所针对的内存地方,第三个参数保存在esp所指向的内部存款和储蓄器地方,可知参数是从右往左一回压栈的。然后实施call指令,那一个命令有三个职能:

    1.
foo函数调用完之后要回来call的下一条指令继续执行,所以把call的下一条指令的地址0x8048叁e玖压栈,同时把esp的值  减4,esp的值以往是0xbf822d1捌。

    二. 改动程序计数器eip, 跳转到foo函数的始发执行。

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

C语言 9

  首先将ebp寄存器的值压栈,同时把esp的值再减四,esp的值将来是0xbf82二d1四,然后把这些值传送给ebp寄存器。换句话说便是把本来ebp的值保存在栈上,然后又给ebp赋了新值。在种种函数的栈帧中,ebp指向栈底,esp指向栈顶,在函数执行进度中esp随着压栈和出栈操作随时变动,而ebp是不动的,函数的参数和局地变量都以透过ebp的值加上贰个偏移量来走访的,例如foo函数的参数a和b分别通过ebp+八和ebp+12来访问,所以上边包车型客车命令把参数a和b再一次压栈,为调用bar函数做准备,然后把再次来到地址压栈,调用bar函数:

C语言 10

C语言 11

  今后看bar函数的命令:

C语言 12

  本次又把foo函数的ebp压栈保存,然后给ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+13分头能够访问参数c和d。bar函数还有三个片段变量e,能够经过ebp-四来访问。所在此在此之前边几条指令的意趣是把参数c和d取出来存在寄存器中做加法,add指令的测算结果保存在eax寄存器中,再把eax寄存器存回局地变量e的内部存款和储蓄器单元。

  未来得以解释为啥在gdb中能够用bt命令和frame命令查看各样栈帧上的参数和部分变量了:假使本身日前在bar函数中,笔者能够透过ebp找到bar函数的参数和局地变量,也足以找到foo函数的ebp保存在栈上的值,有个foo函数的ebp,又能够找到它的参数和部分变量,也得以找到main函数的ebp保存在栈上的值,由此各函数的栈帧通过保留在栈上的ebp的值串起来了。今后看bar函数的回来命令:

C语言 13

  bar函数有一个int型的重临值,那个重回值是通过eax寄存器传递的,所以率先把e的值读到eax寄存器中。然后实施leave指令,这么些命令是函数发轫的push
%ebp和mov %esp, %ebp的逆操作:

    1. 把ebp的值赋给esp,今后esp的值是0xbf82二d0四。

    二.
现行反革命esp所指向的栈顶保存着foo函数栈帧的ebp,把这一个值苏醒给ebp,同时esp扩张四,未来esp的值是0xbf82二d0八。

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

    一.
现行反革命esp所指向的栈顶保存着回去地址,把那几个值苏醒给eip,同时esp增添四,今后esp的值是0xbf82二d0c。

    二. 改动了先后计数器eip,因而跳转到重返地址0x804捌叁c2继续执行。

  地址0x804八3c2处是foo函数的归来指令:

C语言 14

  重复雷同的历程,就又赶回到了main函数。注意函数调用和再次来到经过中的这个规则:

    壹. 参数压栈传递,并且是从右向左依次压栈。
    二.  ebp 总是指向栈帧的栈底。
    叁. 重回值通过 eax 寄存器传递。
  那么些规则并不是系统布局所强加的, ebp
寄存器并不是必须这样用,函数的参数和重回值也不是必须那样传,只是操作系统和编写翻译器采用了以如此的主意贯彻C代码中的函数调用,那叫做Calling
Convention,除了Calling
Convention之外,操作系统还供给分明许多C代码和2进制指令之间的接口规范,统称为ABI(Application
Binary Interface)。