zoukankan      html  css  js  c++  java
  • [Linux内核分析第一周课程] 由C语言程序的汇编表示观察CPU寄存器与内存的互动

    孟宁《Linux内核分析》第一周实验

    作者:Zou Le

    原创作品转载请注明出处。

    课程信息:

    《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

    ---------------------------实验正文---------------------------

    本实验在实验楼64位LIinux虚拟机下进行。

    C代码如下:

    int increment5(int x) {
    
      return x + 5;
    
    }
    
    int solve(int x) {
    
      return increment5(x) - 2;
    
    }
    
    int main(void) {
    
      return solve(2017);
    
    }

    C程序的执行逻辑为:以main函数为入口,调用solve函数,字面值2017作为solve函数的参数,在solve函数中执行调用increment5函数,increment5函数接受2017,并加上5,返回solve函数再减去2,最后回到main函数返回该值。

    通过同目录下-S和-m32参数得到的汇编代码main.s,将汇编代码中以’.’开头的行删除后得到的代码如下:

    increment5:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        addl    $5, %eax
        popl    %ebp
        ret
    solve:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   8(%ebp)
        call    increment5
        addl    $4, %esp
        subl    $2, %eax
        leave
        ret
    main:
        pushl   %ebp
        movl    %esp, %ebp
        pushl   $2017
        call    solve
        addl    $4, %esp
        leave
        ret

    实验截图如下:

     

     

    由教材CSAPP第三章可知,内存为单个过程(函数)分配的那部分栈称为栈帧。最顶端的栈帧由两个指针界定,分别为%ebp指向栈底,%esp指向栈顶,每次push命令都会让%esp减去4(内存中栈向下增长),然后在新的地址写入值。pop则先读出当前%esp位置的值,再将栈减去4。

    ----------------程序运行时内存栈的变化描述---------------

    首先给出带行号的代码作为基准。

    本汇编代码执行的过程为:

    可以观察到每次调用函数,首先执行的命令为

        pushl   %ebp

        movl    %esp, %ebp

    若内存中main函数入口时esp和ebp都指向第0格。则这两行代码所做的事情就是将第0格的信息保存到第1格,然后将栈的原点ebp挪到第1格,以第1格作为本过程的栈底。

    第20行,所做的事情为将字面值2017写入第2格,此时ebp指向第1格,esp指向第2格。

    第21行,call命令将eip=22的命令写入第3格,然后令eip=9。

    第9行和第10行,进入solve函数,先创建栈帧:将main函数的栈底信息(栈底=1)保存到第4格,然后以第4格作为本过程的栈底。注意到,此时8(%ebp)指向的是第2格,即solve函数的参数。对于单参数的函数,往往进入新函数后,其参数所在的位置就是8(%ebp),%ebp本身保存了上一个(要返回的函数)的栈底信息,再往前一格保存了eip指针要返回的位置。

    第11行,esp再次增加四,在第5格写入字面值2017。

    第12行,call命令将eip=13保存到第6格,然后令eip=2。

    第2行和第3行,创建栈帧,将slove函数的栈底信息(栈底=4)保存到第7格,然后以第7格作为本函数的栈底。8(%ebp)随即指向第5格的值,即2017。

    第4行,将第5格的值2017写入eax寄存器中。

    第5行,将eax中的值增加5。

    第6行,将第7格(栈底=4)的信息赋值给ebp,即又让ebp指向第4格,同时esp回退1,指向第6格。

    第7行,ret的语意为 popl %eip,即此时esp指向第6格,内容为eip指向13。该命令修改eip指向第13行,并使得esp回退到第5格。

    第13行,将esp再回退1格,指向第4格。(此时栈顶和栈低都是第4格)。注意到,每次call命令后都会跟一个addl $4, %esp。在本例子中,均为回退掉为call函数所准备的单个函数参数。

    第14行,%eax中减去2。

    第15行,leave指令。leave指令的语意为先movl %ebp, %esp,然后popl %ebp。本行中%ebp保存的信息为main函数的栈底(第1格),合起来的作用为让%ebp重新指向main函数的栈底1,并且esp指向第3格(eip=22)。

    第16行,ret。此行令eip重新回到main函数,程序的第22行,同时esp回退到第2格。

    第22行,将esp回退一格,回到第1格。

    第23行,令栈底等于0,栈顶也等于0。

    第24行程序结束,%eax中保存最后的结果。

    具体内存信息和ebp、esp变化如下所示:

     

    由上述过程可知,相比于高级语言如C语言,汇编语言有很大一部分繁琐的底层的函数调用,传值的信息。如我们所说的函数调用栈,是通过保存上级函数继续执行的地址和上级函数的原来的栈地址后,创建新的栈底来完成的。

    通过本次实验,我对32位汇编中常出现的8(%ebp)和leave+ret有了“感觉”,感受到了C语言相对于汇编所做的抽象工作的重要性,也对程序的机器级表示有了初步的认识。

  • 相关阅读:
    VC++2012编程演练数据结构《25》线索二叉树
    VC++2012编程演练数据结构《26》最大堆二叉树
    VC++2012编程演练数据结构《19》散列文件
    VC++2012编程演练数据结构《21》二叉排序树
    VC++2012编程演练数据结构《23》二叉树排序
    VC++2012编程演练数据结构《22》常规排序算法
    VC++2012编程演练数据结构《27》最小堆二叉树
    VC++2012编程演练数据结构《20》索引文件
    自从来到了上海,开始工作以来就没怎么到博客园
    Graphics 单元中的类
  • 原文地址:https://www.cnblogs.com/aweffr/p/6417102.html
Copyright © 2011-2022 走看看