C语言[汇编与C语言关系]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选项,用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)命令可以一行代码一行代码的单步调试,而si命令可以一样长指令一长指令的单步调试。bt
列出调用栈

info
registers可以显得所有寄存器的手上价值。在gdb中表示寄存器名时前面要加个$,例如p
$esp命令查看esp寄存器的值(上图无出示该令),在上例中esp寄存器的价是0xbff1c3f4,所以x/20
$esp命令查看内存中从0xbff1c3f4地址开始的20单32个数。在实施顺序时,操作系统也经过分配一片栈空间来囤积函数栈帧,esp寄存器总是指于栈顶,,在x86平台上这栈是从高地址为亚地址增长之,每次调用一个函数都设分配一个栈帧来存储参数与局部变量,现在我们分析这些数据是怎么存储的,根据gdb的输出结果图示如下:

C语言 7

  途中每个微方格占4单字节,例如b:3这方格的内存地址是0xbf822d20~0xbf822d23。我们于main函数的这里开看从:

C语言 8

  要调用函数foo先要拿参数准备好,第二单参数保存在esp+4所针对的内存位置,第一只参数保存在esp所指向的内存位置,可见参数是打右侧为左一差压栈的。然后实施call指令,这个令发出少独作用:

    1.
foo函数调用完之后要回到call的下同样长指令继续执行,所以将call的生一样漫长指令的地址0x80483e9杀栈,同时将esp的值  减4,esp的价值现在凡0xbf822d18。

    2. 窜程序计数器eip, 跳转到foo函数的发端执行。

  现在关押foo函数的汇编代码:

C语言 9

  首先以ebp寄存器的价压栈,同时把esp的值更减4,esp的值现在凡是0xbf822d14,然后把这个值传送给ebp寄存器。换句话说即是管原本ebp的值保存在栈上,然后又吃ebp赋了新值。在每个函数的栈帧中,ebp指向栈底,esp指向栈顶,在函数执行进程遭到esp随着压栈和出栈操作随时变动,而ebp是不动的,函数的参数和片变量都是由此ebp的价值长一个偏移量来做客的,例如foo函数的参数a和b分别通过ebp+8和ebp+12来访问,所以下面的命令把参数a和b再次压栈,为调用bar函数做准备,然后把返回地址压栈,调用bar函数:

C语言 10

C语言 11

  现在看bar函数的命:

C语言 12

  这次以将foo函数的ebp压栈保存,然后于ebp赋了新值,指向bar函数栈帧的栈底,通过ebp+8和ebp+12分级可拜参数c和d。bar函数还有一个局部变量e,可以通过ebp-4来拜访。所以后面几长条指令的意是拿参数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的价值是0xbf822d04。

    2.
现esp所指向的栈顶保存着foo函数栈帧的ebp,把这价值恢复被ebp,同时esp增加4,现在esp的值是0xbf822d08。

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

    1.
现esp所指向的栈顶保存着回去地址,把此价恢复被eip,同时esp增加4,现在esp的价是0xbf822d0c。

    2. 改了先后计数器eip,因此超过反到回地址0x80483c2继续执行。

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

C语言 14

  又同一的进程,就同时回到了main函数。注意函数调用和归经过遭到的这些规则:

  C语言  1. 参数压栈传递,并且是由右边为左依次压栈。
    2.  ebp 总是指为栈帧的栈底。
    3. 返回值通过 eax 寄存器传递。
  这些规则并无是网布局所强加的, ebp
寄存器并无是须这样用,函数的参数与返回值也非是得这样传,只是操作系统和编译器选择了以如此的计实现C代码中的函数调用,这名叫Calling
Convention,除了Calling
Convention之外,操作系统还欲规定多C代码和二进制指令中的接口规范,统称为ABI(Application
Binary Interface)。