C程序汇编运行模式简析

Olivia ·
更新时间:2024-11-14
· 898 次阅读

  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 8 10     .cfi_offset 5, -8 11     movl    %esp, %ebp 12     .cfi_def_cfa_register 5 13     movl    8(%ebp), %eax 14     addl    $3, %eax 15     popl    %ebp 16     .cfi_def_cfa 4, 4 17     .cfi_restore 5 18     ret 19     .cfi_endproc 20 .LFE0: 21     .size    g, .-g 22     .globl    f 23     .type    f, @function 24 f: 25 .LFB1: 26     .cfi_startproc 27     pushl    %ebp 28     .cfi_def_cfa_offset 8 29     .cfi_offset 5, -8 30     movl    %esp, %ebp 31     .cfi_def_cfa_register 5 32     subl    $4, %esp 33     movl    8(%ebp), %eax 34     movl    %eax, (%esp) 35     call    g 36     leave 37     .cfi_restore 5 38     .cfi_def_cfa 4, 4 39     ret 40     .cfi_endproc 41 .LFE1: 42     .size    f, .-f 43     .globl    main 44     .type    main, @function 45 main: 46 .LFB2: 47     .cfi_startproc 48     pushl    %ebp 49     .cfi_def_cfa_offset 8 50     .cfi_offset 5, -8 51     movl    %esp, %ebp 52     .cfi_def_cfa_register 5 53     subl    $4, %esp 54     movl    $8, (%esp) 55     call    f 56     addl    $1, %eax 57     leave 58     .cfi_restore 5 59     .cfi_def_cfa 4, 4 60     ret 61     .cfi_endproc 62 .LFE2: 63     .size    main, .-main 64     .ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" 65     .section    .note.GNU-stack,"",@progbits3.汇编代码分析   汇编出的代码,多了很多辅助信息,为了能够更好地看清主干,我们删减一下: 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               //调用g 15     leave                   //清楚局部变量空间 16     ret                     //返回 17 main: 18     pushl    %ebp 19     movl    %esp, %ebp 20     subl    $4, %esp        //空出局部变量空间 21     movl    $8, (%esp)      //为变量赋值 22     call    f               //调用f 23     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存入父栈栈顶,便于恢复到原节点处继续执行。   这样,可以有规律的一直嵌套下去。   如果使用递归函数,是一个码堆栈的过程,知道顶部的堆栈返回,函数像多米诺骨牌一样收回所有的堆栈。   这也是递归函数占用空间比较多的原因之一。如果没有很好地退出机制,有可能内存溢出。



c程序 运行

需要 登录 后方可回复, 如果你还没有账号请 注册新账号
相关文章