深入理解计算机系统-读书笔记(第3章)--程序的机器级表示

Posted by Taolee on May 14, 2016

Ch3 程序的机器级表示

机器级代码

  • 机器级别代码的两种抽象(是指机器级代码对硬件的抽象)

    • ISA抽象,它定义了处理器状态,指令的格式,以及每条指令对状态的影响。大多数ISA,将程序的行为描述成好像每条指令是 按照顺序执行的,一条指令结束后,下一条再开始。处理器的硬件远比描述的更为精细复杂。他们并发的执行许多指令,但是可以采取措施保证整体行为和与ISA指定的 顺序执行完全一致。

    • 存储器模型抽象。机器级程序使用的地址是虚拟地址,提供的存储器模型看上去是一个非常大的数组,但实际上存储器系统的实现是将多个硬件存储器和软件结合起来的。

  • IA32机器代码和原始的C代码差异很大,一些通常对C语言程序员隐藏的状态,是可见的:
    • 程序计数器(PC,在IA32中用 %eip表示)
    • Integer register file. 8个命名的位置,分别存储32位的值。他们用来存储地址值或者整数值,有的寄存器被用来记录重要的程序状态。而其他的寄存器则 用来保存临时数据,例如过程的局部变量和函数的返回值。
    • 条件码寄存器,最近执行的算术或者逻辑指令的状态信息,用来实现控制或者数据流中的条件变化。比如用来实现if或者while语句
    • 一组浮点寄存器,存放浮点数据
  • 程序使用的地址为虚拟地址,虽然可以访问4GB空间,但是通常一个程序只会访问几兆的空间。在任意时刻,可以认为只有一部分有限的虚拟地址是 合法的。如果超出虚拟地址的范围,会引发操作系统的缺页机制等,操作系统会重新将需要的虚拟地址范围映射到物理地址。 操作系统负责将一个程序的虚拟地址,转化为物理地址。

  • 一条机器指令只执行一个非常基本的操作。例如将两个寄存器中的数字相加,在存储器和寄存器之间传送数据,或者是条件分支转移到新的指令地址。 编译器必须产生这些指令的序列,从而实现(像算术表达式求值,循环或过程调用和返回这样的)程序结构。

  • 汇编代码是机器级代码的文本形式。在Linux下 使用带 -d选项的objdump程序可以实现反汇编,也就是把对象文件(一般是.o格式)的机器级代码转换为 汇编代码。

  • 机器代码和它的反汇编特性
    • IA32是变长指令的,指令长度从1到15个字节不等,常用的指令编码长度较多,不常用的指令编码长度较长。(大概是为了压缩?)
    • 设计指令的格式是,从某个给定位置开始,可以将字节唯一的解释成机器指令。(例如,只有pushl %ebp是以字节55开头的)

简单的描述从高级语言代码到机器码(以gcc为例)

  • 预处理器是干什么的?
    • 处理 #include #define等预编译指令
    • 文本替换
    • 加 -E 选项
  • 编译器(C或者C++)干什么的?
    • 把高级语言代码(c)转换为汇编代码。.c -> .s
    • 编译时加 -S 选项
  • 汇编器是干什么的?
    • 把汇编代码转为机器码(也叫目标文件)
    • 加 -c 选项
  • 链接器是干什么的?
    • 把不同的目标文件中的指令和数据(全局和局部变量等)重新分配地址(为了能够互相访问)
    • 举例来说,A.o中的全局变量a本来的地址是0,在使用链接器将A.o和B.o链接为一个文件时:全局变量a的地址可能就变成了 0x804a018。那么本来在 A.o中所有引用的0地址都要换成0x804a018。

上述的转换过程有的过程可逆且比较简单。比如从汇编到机器码,可逆比较简单。但是比如从汇编到原始的C语言代码就很难了。从一个链接好的目标文件中拆出来每一个似乎也很难。 从机器码到汇编码的可逆可以使用 objdump 工具

IA32汇编基础

寻址模式

IA32操作数的寻址模式很多,可以分为基础的三类:立即数,寄存器和存储器。它们之间的组合也能提供有效的寻址。所有的寻址方式见下图: 寻址模式

数据传送指令

将数据从一个位置,复制到另一个位置是最频繁使用的指令。常用的数据传送指令如下: 数据传送

同时IA32的限制是,mov类指令的操作数不能同时为存储器,要将一个数从存储器的一个位置传送到另一个位置,必须至少使用两条指令。 第一条将数据从存储器加载到寄存器,第二条将数据从寄存器存储到存储器。

栈指针

栈空间向下生长,即栈顶的元素的地址是栈中所有地址的最小值。栈顶指针总是存放在寄存器 %esp中。将一个双字压入栈中,即

pushl %ebp

相当于执行了两个过程:首先将栈顶指针减4,然后将源地址的数存入栈顶指针指向的存储器中。即相当与如下两个指令:

subl $4, %esp
movl %ebp, (%esp)

相反的,弹出一个双字相当于首先将栈顶指针的数据传到目的地址,然后将栈顶指针加4。 因为栈和程序代码使用相同的存储器,所以程序可以使用标准的存储器寻址方法,访问栈当中的任何一个数据