首页 > 学院 > 操作系统 > 正文

C程序汇编运行模式简析

2024-06-28 13:21:41
字体:
来源:转载
供稿:网友
C程序汇编运行模式简析

SJTUBEAR 原创作品转载请注明出处 /《linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

1. 汇编

在修习LINUX内核这门课的初始阶段,首先需要掌握的就是汇编以及汇编程序对于堆栈的操作。

下面我们就来分析一下一个简单地C程序是如何被汇编程序所表达的!

2. 得到汇编代码

首先,我们写一个简单地C程序,命名为exp1.c:

 1 #include <stdio.h> 2  3 int g(int x) 4 { 5     return x+3; 6 } 7  8 int f(x) 9 {10     return g(x);11 }12 13 int main()14 {15     return f(8)+1;    16 }

程序非常的简单,我们此时再通过编译指令将其编译为汇编程序:

1 gcc –S –o main.s main.c -m32

这样我们就得到了这个简单C程序的汇编代码:

 1     .file    "exp1.c" 2     .text 3     .globl    g 4     .type    g, @function 5 g: 6 .LFB0: 7     .cfi_startPRoc 8     pushl    %ebp 9     .cfi_def_cfa_offset 810     .cfi_offset 5, -811     movl    %esp, %ebp12     .cfi_def_cfa_register 513     movl    8(%ebp), %eax14     addl    $3, %eax15     popl    %ebp16     .cfi_def_cfa 4, 417     .cfi_restore 518     ret19     .cfi_endproc20 .LFE0:21     .size    g, .-g22     .globl    f23     .type    f, @function24 f:25 .LFB1:26     .cfi_startproc27     pushl    %ebp28     .cfi_def_cfa_offset 829     .cfi_offset 5, -830     movl    %esp, %ebp31     .cfi_def_cfa_register 532     subl    $4, %esp33     movl    8(%ebp), %eax34     movl    %eax, (%esp)35     call    g36     leave37     .cfi_restore 538     .cfi_def_cfa 4, 439     ret40     .cfi_endproc41 .LFE1:42     .size    f, .-f43     .globl    main44     .type    main, @function45 main:46 .LFB2:47     .cfi_startproc48     pushl    %ebp49     .cfi_def_cfa_offset 850     .cfi_offset 5, -851     movl    %esp, %ebp52     .cfi_def_cfa_register 553     subl    $4, %esp54     movl    $8, (%esp)55     call    f56     addl    $1, %eax57     leave58     .cfi_restore 559     .cfi_def_cfa 4, 460     ret61     .cfi_endproc62 .LFE2:63     .size    main, .-main64     .ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"65     .section    .note.GNU-stack,"",@progbits
3.汇编代码分析

汇编出的代码,多了很多辅助信息,为了能够更好地看清主干,我们删减一下:

 1 g: 2     pushl    %ebp            //保存现场,将父函数的栈底寄存器存入当前程序栈中 3     movl    %esp, %ebp      //构建当前函数堆栈 4     movl    8(%ebp), %eax   //从父函数堆栈中取得参数,存入ax寄存器 5     addl    $3, %eax        //完成+3操作 6     popl    %ebp            //恢复原父函数堆栈 7     ret                     //pop出原Eip地址,恢复执行 8 f: 9     pushl    %ebp            //保存现场,将父函数的栈底寄存器存入当前程序栈中10     movl    %esp, %ebp      //构建当前函数堆栈11     subl    $4, %esp        //栈顶加一,用以储存变量传递给g函数12     movl    8(%ebp), %eax   //取得参数13     movl    %eax, (%esp)    //将参数传入变量位置14     call    g               //调用g15     leave                   //清楚局部变量空间16     ret                     //返回17 main:18     pushl    %ebp19     movl    %esp, %ebp20     subl    $4, %esp        //空出局部变量空间21     movl    $8, (%esp)      //为变量赋值22     call    f               //调用f23     addl    $1, %eax        //完成+1操作24     leave                   //清理局部变量25     ret                     //返回

我们对f函数进行详细的分析:

1. 首先进行enter指令:

此时,ebp当前所指向的位置存入栈顶,并且将ebp重定向指向esp:

2.栈顶加一并存入变量值:

3.调用g

4.从g返回后,返回值储存在AX寄存器中,不用操作,调用leave,清理变量

5.最后ret,同时EIP被读出恢复到原位置继续执行,返回值在AX中传递给调用函数

3.个人的一点感悟:

程序的调用就是这样嵌套的执行下去,每个函数都有自己的堆栈用以储存当前变量以及环境值,并通过将父函数的EBP放入栈底用以恢复环境。

同时EIP存入父栈栈顶,便于恢复到原节点处继续执行。

这样,就可以有规律的一直嵌套下去。

如果使用递归函数,就是一个码堆栈的过程,知道最顶部的堆栈返回,函数就像多米诺骨牌一样收回所有的堆栈。

这也是递归函数占用空间比较多的原因之一。如果没有很好地退出机制,有可能内存溢出。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表