Chinaunix首页 | 论坛 | 博客
  • 博客访问: 36201
  • 博文数量: 8
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2009-08-17 23:30
文章分类
文章存档

2011年(1)

2009年(7)

我的朋友

分类:

2009-12-20 13:15:04

本文通过一个C语言例子程序,展示函数调用中参数的传递过程。

首先理解函数调用的过程。

 

1. 每个C函数经过GCC编译后,都会形成下面汇编:

.globl func

        .type   func, @function

func:

        pushl   %ebp

        movl    %esp, %ebp

        ...

        popl    %ebp

        ret

        .size   func, .-func

注意:

在函数开头,先把EBP保存到栈中,再把ESP的值复制到EBP中,函数的后续部分主要就用EBP来访问函数的参数。

在函数返回前,从栈中取回EBP的值。其实,如果ESP在上面过程中被修改过,在popl %ebp之前还要先把ESP修改回原来的值。即是说,在pushl %ebppopl %ebp时,ESP必须保持相同的值。

 

2. 当调用C函数时,GCC先把参数列表反序入栈,然后,在执行CALL func指令时,机器会自动把函数返回地址压入栈中。栈示意图如下:

 

 

由上面的示意图可以看出:函数参数Function parameter 3 , 2, 1是以相反的顺序压入栈中的,而参数入栈之后便是函数返回地址Return Address。之后程序进入函数,函数把EBP入栈,成为Old EBP Value,此时ESP指向Old EBP Value所在位置,再把ESP的复制到EBP中。示意图最右一列是函数在使用EBP访问参数时就使用的偏移。如,(%ebp) 指向旧的EBP值,4(%ebp)指向函数返回地址,而8(%ebp)指向第一个函数的地址。

 

 

现在我们写一个简单的C程序parstk.c

#include 

void func(int k)

{

    k++;

}

int main(int argc, char* argv[])

{

    int k = 0;

    func(k);

    return 0;

}

这个程序有两个函数,并且在main()中调用了func(). 我们把它编成汇编代码:

$gcc -S -o parstk.s parstk.c

使用cat查看parstk.s文件:

        .file   "parstk.c"

        .text

.globl func

        .type   func, @function

func:

        pushl   %ebp

        movl    %esp, %ebp

        addl    $1, 8(%ebp)

        popl    %ebp

        ret

        .size   func, .-func

.globl main

        .type   main, @function

main:

        leal    4(%esp), %ecx

        andl    $-16, %esp

        pushl   -4(%ecx)

        pushl   %ebp

        movl    %esp, %ebp

        pushl   %ecx

        subl    $20, %esp

        movl    $0, -8(%ebp)

        movl    -8(%ebp), %eax

        movl    %eax, (%esp)

        call    func

        movl    $0, %eax

        addl    $20, %esp

        popl    %ecx

        popl    %ebp

        leal    -4(%ecx), %esp

        ret

        .size   main, .-main

        .ident  "GCC: (GNU) 4.2.4 (Ubuntu 4.2.4-1ubuntu4)"

        .section        .note.GNU-stack,"",@progbits

 

可以看到,在func()main()的开头和结尾处都有EBP入栈,复制ESPEBP出栈的规范操作。

现在我们来分析一下main()函数调用func()的过程。

 

main()中,

1)movl $0, -8(%ebp)

相当于执行了int k = 0. -8(%ebp)是局部变量k的地址,此时它被初始化为0。

2) 接下去两行是把变量k压入栈中。这里解释一下为什么没有使用pushl。首先,main()movl %esp, %ebp之后,又用subl $20, %espESP的值减了20,相当于往栈中偷偷地压入20个空的字,后面直接使用movl直接把k的值放到ESP指向的内存中,相当于覆盖栈顶,而此时栈顶并没有重要的数据,所以用movl而没用pushl是安全的,且节省了栈空间。

3) call func

注意,此句执行完毕之后,函数返回地址自动入栈,且程序转到func()函数执行。

 

func()中,

1) EBP入栈,复制ESP

2) addl $1, 8(%ebp)

对第一个参数加1. 正如前面所说,使用了8(%ebp)访问第一个参数。

 

 

此外,还可以看出:

O main()函数中对ESP减了20之后,在返回前还是把它加了回来。指令Subl $20, %espaddl $20, %esp

O func()函数中没有修改ESP,所以也不需要在返回前对ESP做修复工作。

 

 

阅读(1606) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~