您的位置:首页 > 编程语言 > C语言/C++

从汇编简要分析c语言函数调用栈

2011-09-13 20:19 330 查看
先写个简单的小demo, 分析一下c的函数调用过程。

void test(int a, int b) {
a++;
b+=2;
}

void haha() {
test(3, 5);
}

int main() {
haha();
return 0;
}


下面是haha()的汇编代码:

Dump of assembler code for function haha:

0x080483a1 <+0>: push %ebp

0x080483a2 <+1>: mov %esp,%ebp

0x080483a4 <+3>: sub $0x8,%esp

=> 0x080483a7 <+6>: movl $0x5,0x4(%esp)

0x080483af <+14>: movl $0x3,(%esp)

0x080483b6 <+21>: call 0x8048394 <test>

0x080483bb <+26>: leave

0x080483bc <+27>: ret

End of assembler dump.

此时程序暂停在test(3, 5) 这一行, 调用test时, 首先会把函数参数压栈。

0x080483a4 <+3>: sub $0x8,%esp 表示栈顶加8个字节, 可以保留两个int型参数。

0x080483a7 <+6>: movl $0x5,0x4(%esp) 第二个参数先入栈

0x080483af <+14>: movl $0x3,(%esp) 第一个参数后如栈

0x080483b6 <+21>: call 0x8048394 <test> call会调用test函数, 这里隐含的操作是eip寄存器内容入栈,用来保存函数调用结束后应该执行的命令地址。

下面是test的汇编代码:

Dump of assembler code for function test:

0x08048394 <+0>: push %ebp

0x08048395 <+1>: mov %esp,%ebp

=> 0x08048397 <+3>: addl $0x1,0x8(%ebp)

0x0804839b <+7>: addl $0x2,0xc(%ebp)

0x0804839f <+11>: pop %ebp

0x080483a0 <+12>: ret

End of assembler dump.

0x08048394 <+0>: push %ebp 首先保存调用函数(haha)的栈底地址

0x08048395 <+1>: mov %esp,%ebp 然后把esp 内容赋给ebp寄存器, ebp保存新栈帧 (test)的栈底地址。 此时esp ebp指向栈的同一个位置。

此时ebp的内容保存着调用函数的栈底地址, 以此为基准, %ebp + 4 地址保存返回地址, %ebp + 8 保存 第一个参数, &ebp + 12 保存第二个参数, 而比%ebp - x (x 为正数) 保存test函数的局部变量(这个例子没有用到局部变量)。

0x08048397 <+3>: addl $0x1,0x8(%ebp) 第一个参数+1

0x0804839b <+7>: addl $0x2,0xc(%ebp) 第二个参数+2

0x080483a0 <+12>: ret 返回。

ebp 具有十分的意义, 它总是保存上层调用时的ebp值, 而且在没层调用中都可一通过ebp (向栈底方向)找到返回地址, 参数, (向栈顶方向) 找到局部变量。 这是一个递归的过程。

如上所示, 当程序执行到 0x08048397 <+3>: addl $0x1,0x8(%ebp) 的时候, 打印ebp 内容:

(gdb) p $ebp

$1 = (void *) 0xbffff310

表示test的栈底地址为0xbffff310

0xbffff310 + 8 为第一参数3

(gdb) x /1uw 0xbffff318

0xbffff318: 3

0xbffff310 + 12 为第一参数5

(gdb) x /1uw 0xbffff31c

0xbffff31c: 5

0xbffff310 + 4 为 返回地址

(gdb) x /1aw 0xbffff314

0xbffff314: 0x80483bb <haha+26>

而 0x80483bb <haha+26> 对应函数haha中的 0x080483bb <+26>: leave
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: