• 从C到汇编:栈是计算机工作的基础


     
           作者:r1ce
           原创作品转载请注明出处
          《Linux内核分析》 MOOC课程http://mooc.study.163.com/course/USTC-1000029000
     
           关于计算机是如何工作的,这是一个容易概括却难以详解的问题。大家非常清楚的冯诺依曼体系,以存储程序为最重要的特性,实际上就是CPU像一个大管家一样,通过种种方式在浩如烟海的内存中,找出需要执行的指令,和需要使用的数据。那么CPU如何区分指令和数据,如何知道确定指令执行的顺序呢?
           我们先从上至下来看计算机。普通用户使用计算机上的软件,软件是由程序员编写的,一般使用高级语言,如Python、C、Java等,这些语言易于人类理解、阅读和编写,但是计算机却不能直接识别。无论是Python还是C,前者需要通过解释器来执行,后者需要编译器编译为可执行文件。计算机最底层的实现是基于电路实现0和1的识别,这也是可执行文件的真貌——一大堆0和1的表示。那么高级语言到0和1之间,看起来好像隔着很大的一条鸿沟,于是汇编语言作为二者的中介,便显得十分重要了。向上而言,高级语言可以用汇编语言表示;向下而言,每一个汇编语言的指令都可以用二进制0和1表示,从而被计算机CPU识别。理解了汇编语言的操作过程,也就能够理解计算机究竟是如何工作的。
           汇编语言究竟是什么东西呢?想要理解汇编语言,要先理解计算机的组成。为了简化,只提CPU和内存。CPU是处理器,内存存放着指令和数据,处理器就像一个管家,从内存中取指令执行,对数据进行出来,并将数据储存起来。对于CPU来说,每一个程序的执行要解决三个问题:1. 待处理的数据在哪里;2. 如何处理数据;3. 处理好的数据放在哪里。为了解决这三个问题,CPU需要借助一些工具的帮助,这些工具就是各种寄存器。汇编语言实际上就是对这些寄存器进行处理,通俗点说,就是把一大堆数据在寄存器和内存倒腾过来倒腾过去,做一些复制和加加减减的运算。其实学习汇编语言很简单,只要记住十几条汇编指令和各种寄存器以及堆栈的用法就可以了。
           在这篇文章中,我们通过对一个简单的C程序反汇编得到汇编代码,分析汇编代码来了解计算机工作的基础。
           这段C程序是这样的:
     1 int a(int x)
     2 {
     3       return x + 5;
     4 }
     5 
     6 int b(int x)
     7 {
     8       return a(x);
     9 }
    10 
    11 int main(void)
    12 {
    13       return b(5) - 2;
    14 }
           可以看到程序中有很多函数的调用和返回。为什么要这样设置呢?因为程序中的函数调用时计算机工作运行的关键,分析函数调用的具体实现能够帮助理解计算机运行的原理。
           我们将上述代码写入main.c文件中。然后使用
    gcc -S -o main.s main.c -m32

           命令生成汇编代码。结果如下图。后面加-m32是为了让其按照32位的方式反汇编。

     

           我们只需要看汇编代码的关键部分,可以把点开头的语句全部删去,得到如下的汇编指令。

     1 a:
     2 
     3     pushl    %ebp
     4     movl    %esp, %ebp
     5     movl    8(%ebp), %eax
     6     addl    $5, %eax
     7     popl    %ebp
     8     ret
     9 
    10 b:
    11 
    12     pushl    %ebp
    13     movl    %esp, %ebp
    14     subl    $4, %esp
    15     movl    8(%ebp), %eax
    16     movl    %eax, (%esp)
    17     call    a
    18     leave
    19     ret
    20 
    21 main:
    22 
    23     pushl    %ebp
    24     movl    %esp, %ebp
    25     subl    $4, %esp
    26     movl    $5, (%esp)
    27     call    b
    28     subl    $2, %eax
    29     leave
    30     ret
           接下来我们分析C代码和汇编程序究竟是如何对应起来的,以及汇编语言是如何工作的。
           我们先看C程序,从main函数看起,它返回了一个函数b再进行运算的结果。那么我们来看函数b,它返回的是函数a的结果,而函数a的作用是将传递给它的参数x加5。所以对于这个程序,最后得到的数值应该是5+5-2=8。
           再来看汇编代码,我们还是从main函数看起,一条指令一条指令地分析。第n条表示指令执行的顺序,后面列出了代码的行号和执行的指令。
           第1条:23 pushl %ebp
           一看到push,我们就知道这是在对栈进行操作。ebp是栈顶指针,esp是栈当前位置指针,栈是自上向下生长的,后进先出。先把ebp压栈,实际上是先将esp-4再将ebp放到栈当前位置。
           第2条:24 movl %esp, %ebp
           将esp的值放到ebp中,也就是说现在ebp的指向改变为esp的指向。
           第3条:25 subl $4, %esp
           将esp-4。
           第4条:26 movl $5, (%esp)
           将5移入esp指向的地址中。
           第5条:27 call b
           调用函数b,这里等于两个操作,一个是先将现在的eip入栈,此时eip应为subl $2,%eax这条指令的位置,我们记为28。另一个操作是将b函数的地址放入eip,也就是说此时程序要从10开始执行。
           第6条:12 pushl %ebp
           第7条:13 movl %esp, %ebp
           第8条:14 subl $4, %esp
           此时已跳转到b函数,指令之前已经讲过了,与7、8条一起不再赘述。
           第9条:15 movl 8(%ebp), %eax
           movl 8(%ebp), %eax,是将ebp的值+8指向的内容放入eax,实际上就是eax = 5。
           第10条:16 movl %eax, (%esp)
           将eax的内容放入现在esp指向的内容中。
           第11条:17 call a
           调用函数a,与前面的步骤类似。
           第12条:3 pushl %ebp
           第13条:4 movl %esp, %ebp
           第14条:5 movl 8(%ebp), %eax
           是a函数的pushl %ebp,与13、14条一同省略。
           第15条:6 addl $5, %eax
           将eax中的值+5得到10。
           第16条:7 popl %ebp
           将现在esp指向的内容放入ebp,esp+4,所以现在ebp=4。
           第17条:8 ret
           是ret,即popl %eip,也就是现在的eip更改为18,回到函数b,从leave开始执行。
           第18条:18 leave
           leave,表示两条指令,movl %ebp,%esp和popl %ebp。
           第19条:19 ret
           ret回到main函数,从28处执行。
           第20条:28 subl $2, %eax
           将eax中的内容-2,即8。
           第21条:29 leave
           第22条:30 ret
           如图所示。从图中我们可以看到,栈又回到了初始的位置。
          
           至此,汇编代码就分析完了。
           从上面的过程可以看出,计算机最本质的工作原理,是对存储的数据进行处理,并把结果保存,然后不断循环这个处理数据的过程。指令就是对数据进行处理的依据。具体的方法就是借助CPU中的寄存器,以及内存中的栈,依据一个约定的步骤对数据进行操作。计算机其实很简单,它是一个认死理的家伙,只要确定了每一步要做什么,它就会严格地按照步骤把操作完成,绝对不打折扣。因此,相比与人打交道,与计算机打交道可是要轻松多了。
  • 相关阅读:
    HDFS的工作流程分析
    linux文本分析利器awk
    二叉树遍历
    tomcat 工作原理简析
    徐汉彬:Web系统大规模并发——电商秒杀与抢购
    牛人博客 列表
    消息队列 概念 配合SpringBoot使用Demo
    Redis 实例排除步骤
    Java分布式 一些概念理解
    消息成功失败回调demo
  • 原文地址:https://www.cnblogs.com/r1ce/p/5209519.html
Copyright © 2020-2023  润新知