2020-2021-1 20209323 《linux内核原理与分析》第二周作业

 本周的实验内容是:汇编一个C语言程序代码,反汇编并分析其汇编指令执行过程。

 在实验楼的Linux环境中用命令行创建文件,main.c截图如下图所示:

将.c文件反汇编成.s文件,使用-m32选项让它生成32位汇编指令

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

编译阶段命令截断后的产物
    C源程序
预处理 gcc -E 替换了宏的C源程序(没有了#define,#include…), 删除了注释
编译 gcc -S 汇编源程序
汇编 gcc -c 目标文件,二进制文件, 允许有不在此文件中的外部变量、函数
链接 gcc 可执行程序,一般由多个目标文件或库链接而成, 二进制文件,所有变量、函数都必须找得到

 反汇编源文件生成的汇编文件如下图所示:

代码中有许多以.开头的代码行,属于链接时候的辅助信息,在实际中不会执行,把它删除,得到下列的代码就是纯汇编代码了:

g:
    pushl    %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $23, %eax
    popl    %ebp
    ret
f:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    8(%ebp)
    call    g
    addl    $4, %esp
    leave
    ret
main:
    pushl    %ebp
    movl    %esp, %ebp
    pushl    $23
    call    f
    addl    $4, %esp
    addl    $1, %eax
    leave
    ret

函数执行
每次call一个函数,函数总是先把当前的栈底指针压入堆栈,然后把栈底指针移动到当前的栈顶,这样子做,相当于在旧的栈上新起了一个栈。然后在新栈上执行函数。
结束函数执行的时候,如果有堆栈变化,我们在写单片机汇编的时候,我们的习惯是一个函数有多少push就写多少pop,但是,由于我们新引进了一个寄存器,我们可以用movl %ebp, %esp来瞬间恢复堆栈。当然,如果没有堆栈的变化,我们当然可以优化编译器把这句话去了。
这时候,马上就要ret飞回调用它的函数了,在此之前,我们还需要恢复栈底指针,否则回去的日子就难过了。于是popl %ebp。然后如果可以的话,我们会用leave来代替刚刚的两行代码。

函数调用

call f:这是调用f(23)函数。先把23压栈,然后调用了f函数。等到ret后,返回了现在的call的下一行汇编代码。这时候,esp和ebp是一个值,所以这以后如果压栈的时候,会覆盖了栈底指针,把esp往栈顶上移动1个单位也就是4个字节,这时候就完美解决了调用后的问题,才是真正调用完成了。

总结:

  • 每次都是各种取指针执行,在程序中各种跳转。

  • 函数执行前要enter,函数执行后要leave(如果没有改变esp就可以省去把ebp赋值给esp的步骤了),ret

  • 函数取值可以靠ebp很方便做到

  • 函数调用结束后要记住恢复堆栈指针(esp)

  • x86体系结构栈地址是向下增长的(地址不断减小),由于是我们使用的32位的x86汇编指令,这里的堆栈是从高向低入栈的,和51单片机的堆栈不同。
原文地址:https://www.cnblogs.com/qingyu-sun/p/13834666.html