C函数调用过程

原文地址:http://blog.csdn.net/songuooo/article/details/7855221


       这几天在看GCC Inline Assembly,在C代码中通过asm或__asm__嵌入一些汇编代码,如进行系统调用,使用寄存器以提高性能能,需要对函数调用过程中的堆栈帧(Stack Frame)、CPU寄存器、GCC inlie assembly等了如指掌。现在看看函数调用过程吧。


1. Linux 进程虚拟地址空间

以32位操作系统为例,下面是Linux进程地址空间布局:



32位虚拟地址空间的高1GB的空间是留给操作系统内核的,栈由高地址到低地址向下增长,堆由低地址到高地址向上增长。

C中如 malloc 等分配的内存在堆中分配。初始化了的静态变量和全局变量放在Data段中。未初始化的全局变量和局部静态变量放在Bss段中,更准确的说是在Bss段为它们预留了空间。非静态局部变量是在函数调用过程中暂存在栈上的。


2. 函数的堆栈帧

栈在程序运行中具有举足轻重的地位。最重要的,栈保存了一个函数调用所需要的维护信息,被称为堆栈帧(Stack Frame),一个函数(被调函数)的堆栈帧一般包括下面几个方面的内容:

(1) 函数参数,默认调用惯例情况下从右向左的顺序依次把参数压入栈中。由函数调用方执行。

(2) 函数的返回地址,即调用方调用此函数(如call func1)的下一条指令的地址。函数调用方(call指令)执行。

(3) 保存调用方函数的EBP寄存器,即将调用方函数的EBP压入堆栈,并令EBP指向此栈中的地址:pushl %ebp; movl %esp, %ebp。由被调函数执行。

(4) 上下文:保存在函数调用过程中需要保持不变的寄存器(函数调用方的),如ebx,esi,edi等。由被调函数执行。

(5) 临时变量,如非静态局部变量。

下面是一个函数的堆栈帧结构图:


压入函数参数和返回地址的过程是由函数调用方在调用函数之前将其压入栈中,每个函数执行后首先要执行的就是把函数调用方的EBP寄存器压入栈中,之后是在栈上开辟一些空间存放局部变量,最后把要保存的寄存器压入栈中。i386标准函数进入和退出的指令序列的基本形式如下:(GCC 内联汇编描述)

  1. pushl %ebp  
  2. movl %esp, %ebp  
  3. subl $x, %esp  
  4. [pushl reg1]  
  5. ...  
  6. [pushl regn]  
  7. 函数实际内容  
  8. [popl regn]  
  9. ...  
  10. [popl reg1]  
  11. movl %ebp, %esp  
  12. opol %ebp  
  13. ret  
pushl %ebp
movl %esp, %ebp
subl $x, %esp
[pushl reg1]
...
[pushl regn]
函数实际内容
[popl regn]
...
[popl reg1]
movl %ebp, %esp
opol %ebp
ret

3. 例子

下面通过代码分析一下吧:

  1. /* stackFrame.c*/                                                                                                              
  2. #include <stdio.h>  
  3.   
  4. void func1(int a, int b);  
  5. void func2(int a, int b, int c);  
  6.   
  7. int main(void )  
  8. {  
  9.     int a = 1;  
  10.     func1(a, 2);  
  11.     return 0;  
  12. }  
  13.   
  14. void func1(int a, int b)  
  15. {  
  16.     int  a1 = 3;  
  17.     char b1 = '4' - '0'//4  
  18.     func2(a1, b1, 5);  
  19. }  
  20.   
  21. void func2(int a, int b, int c)  
  22. {  
  23.     char a1 = 'a';  
  24. }  
/* stackFrame.c*/                                                                                                            
#include <stdio.h>

void func1(int a, int b);
void func2(int a, int b, int c);

int main(void )
{
    int a = 1;
    func1(a, 2);
    return 0;
}

void func1(int a, int b)
{
    int  a1 = 3;
    char b1 = '4' - '0'; //4
    func2(a1, b1, 5);
}

void func2(int a, int b, int c)
{
    char a1 = 'a';
}

汇编后的汇编代码如下,gcc -o stackFrame.s -S stackFrame.c

  1.     .file   "funcstack.c"                                                                                                      
  2.     .text  
  3. .globl main  
  4.     .type   main, @function  
  5. main:  
  6.     pushl   %ebp  
  7.     movl    %esp, %ebp  
  8.     andl    $-16, %esp  
  9.     subl    $32, %esp  
  10.     movl    $1, 28(%esp)  
  11.     movl    $2, 4(%esp)  
  12.     movl    28(%esp), %eax  
  13.     movl    %eax, (%esp)  
  14.     call    func1  
  15.     movl    $0, %eax                //函数返回值送往eax  
  16.     leave  
  17.     ret  
  18.     .size   main, .-main  
  19. .globl func1  
  20.     .type   func1, @function  
  21. func1:  
  22.     pushl   %ebp                //压入函数调用方的EBP,Old EBP  
  23.     movl    %esp, %ebp          //令当前EBP指向栈顶,此时的栈顶指向Old EBP  
  24.     subl    $40, %esp           //在栈上开辟一些空间,存放局部变量等  
  25.     movl    $3, -16(%ebp)       //func1中整型变量a1的存储位置, ebp-16  
  26.     movb    $4, -9(%ebp)        //func1中字符型变量b1的存储位置,ebp-9  
  27.     movsbl  -9(%ebp), %eax      //将b1送入eax  
  28.     movl    $5, 8(%esp)         //将调用func2的函数参数5压入栈,esp+8  
  29.     movl    %eax, 4(%esp)       //将参数b1压入栈,            esp+4  
  30.     movl    -16(%ebp), %eax     //将a1送入eax  
  31.     movl    %eax, (%esp)        //将参数a1压入栈,            esp+0  
  32.     call    func2               //调用func2,下一调指令的返回地址入栈(EIP入栈),内部完成,无汇编代码  
  33.     leave  
  34.     ret                         //将返回地址送往EIP  
  35.     .size   func1, .-func1  
  36. .globl func2  
  37.     .type   func2, @function  
  38. func2:  
  39.     pushl   %ebp  
  40.     movl    %esp, %ebp  
  41.     subl    $16, %esp  
  42.     movb    $97, -1(%ebp)  
  43.     leave  
  44.     ret  
  45.     .size   func2, .-func2  
  46.     .ident  "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"  
  47.     .section    .note.GNU-stack,"",@progbits   
    .file   "funcstack.c"                                                                                                    
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $1, 28(%esp)
    movl    $2, 4(%esp)
    movl    28(%esp), %eax
    movl    %eax, (%esp)
    call    func1
    movl    $0, %eax                //函数返回值送往eax
    leave
    ret
    .size   main, .-main
.globl func1
    .type   func1, @function
func1:
    pushl   %ebp                //压入函数调用方的EBP,Old EBP
    movl    %esp, %ebp          //令当前EBP指向栈顶,此时的栈顶指向Old EBP
    subl    $40, %esp           //在栈上开辟一些空间,存放局部变量等
    movl    $3, -16(%ebp)       //func1中整型变量a1的存储位置, ebp-16
    movb    $4, -9(%ebp)        //func1中字符型变量b1的存储位置,ebp-9
    movsbl  -9(%ebp), %eax      //将b1送入eax
    movl    $5, 8(%esp)         //将调用func2的函数参数5压入栈,esp+8
    movl    %eax, 4(%esp)       //将参数b1压入栈,            esp+4
    movl    -16(%ebp), %eax     //将a1送入eax
    movl    %eax, (%esp)        //将参数a1压入栈,            esp+0
    call    func2               //调用func2,下一调指令的返回地址入栈(EIP入栈),内部完成,无汇编代码
    leave
    ret                         //将返回地址送往EIP
    .size   func1, .-func1
.globl func2
    .type   func2, @function
func2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movb    $97, -1(%ebp)
    leave
    ret
    .size   func2, .-func2
    .ident  "GCC: (GNU) 4.4.4 20100726 (Red Hat 4.4.4-13)"
    .section    .note.GNU-stack,"",@progbits 

4. 例子

代码如下:

  1. #include <stdio.h>   
  2.   
  3. void test1(int a, int b);  
  4. void test2(int a, int b, int c);  
  5.   
  6. int main(void)  
  7. {  
  8.     test1(1, 2);  
  9.     return 0;  
  10. }  
  11.   
  12. void test1(int a, int b)  
  13. {  
  14.     char *ebp = NULL;  
  15.   
  16.     asm("movl %%ebp, %0\n\t"  
  17.         :"=r"(ebp));  
  18.   
  19.     printf("test1: ebp = %p\n", ebp); //test1函数的ebp  
  20.     test2(3, 4, 5);  
  21. }  
  22.   
  23. void test2(int a, int b, int c)  
  24. {  
  25.     int *ebp = NULL;  
  26.   
  27.     asm("movl %%ebp, %0\n\t"  
  28.         :"=r"(ebp));  
  29.   
  30.     printf("test2: *ebp = %p\n", (char *)(*ebp)); //test2的ebp指向的内容(old EBP),是test1的EBP  
  31.     printf("*(ebp + 4)  = %d\n", *((char *)ebp + 4));  //返回地址  
  32.     printf("*(ebp + 8)  = %d\n", *((char *)ebp + 8));  //第一个参数 3  
  33.     printf("*(ebp + 12) = %d\n", *((char *)ebp + 12)); //第二个参数 4  
  34.     printf("*(ebp + 16) = %d\n", *((char *)ebp + 16)); //第三个参数 5  
  35. }       
#include <stdio.h> 

void test1(int a, int b);
void test2(int a, int b, int c);

int main(void)
{
    test1(1, 2);
    return 0;
}

void test1(int a, int b)
{
    char *ebp = NULL;

    asm("movl %%ebp, %0\n\t"
        :"=r"(ebp));

    printf("test1: ebp = %p\n", ebp); //test1函数的ebp
    test2(3, 4, 5);
}

void test2(int a, int b, int c)
{
    int *ebp = NULL;

    asm("movl %%ebp, %0\n\t"
        :"=r"(ebp));

    printf("test2: *ebp = %p\n", (char *)(*ebp)); //test2的ebp指向的内容(old EBP),是test1的EBP
    printf("*(ebp + 4)  = %d\n", *((char *)ebp + 4));  //返回地址
    printf("*(ebp + 8)  = %d\n", *((char *)ebp + 8));  //第一个参数 3
    printf("*(ebp + 12) = %d\n", *((char *)ebp + 12)); //第二个参数 4
    printf("*(ebp + 16) = %d\n", *((char *)ebp + 16)); //第三个参数 5
}     

结果如下:
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值