第三章 程序的机器级表示
3.1历史观点
Intel处理器俗称x86。
IA32就是“Intel32位体系结构”。本章主要了解IA32指令集。
Linux采用了平坦寻址方式,使程序员将整个存储空间看做一个大的字节数组。
3.2程序编码
Gcc编译器在gcc命令时调用了一系列程序,将源代码转化成可执行代码。即:
C预处理器扩展源代码(插入所有用#include命令指定的文件,并扩展所有用#define声明指定的宏)—→编译器产生两个源代码的汇编代码(名字分别为p1.s,p2.s)—→汇编器将汇编代码转化成二进制目标代码文件名为p1.o,p2.o—→链接器实现两个目标代码文件与实现库函数的代码合并,产生最终的可执行代码文件p(处理器执行的代码格式)。
1.机器级代码
两种抽象:
(1) 指令集体系结构ISA——机器级程序的格式和行为,定义了处理器状态、指令的格式以及每条指令对状态的影响。
(2) 机器级结构使用的存储地址虚拟地址,存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。
2.代码实例
(1)
意思为:
(2)反汇编器——查看目标代码文件的内容
带-d命令行标志的程序objdump可以实现:objdump –d
code.o
(3)关于格式的注解
以“.”开头的行都是指导汇编器和链接器的命令。
- 数据格式
Intel用“字”表示16位数据类型。称32位数为双字节,64位数为四字节。
!大多数常用数据类型都是以双字形式存储,包括int和long
int,并不考虑是否有符号
!所有的指针都存储为4字节的双字
汇编代码指令后的字符后缀表明操作数的大小:
4.访问信息
(1)一个IA中央处理单元(CPU)包含一组8个存储32位值的寄存器,用来存储整数数据和指针。
(2) 操作数指示符
操作数三种类型:
●立即数,即常数值。 如:
●寄存器,表示寄存器的内容
●存储器引用,它会根据计算出来的地址(有效地址)访问某个存储器位置。
(3) 数据传送指令
将一个只从一个存储器位置复制到另一个存储器位置需要两条指令——第一条将原址加载到寄存器中,第二条将该寄存器值写入目的位置。
●三个字节传送指令的差别(movb、movsb1、movzb1)
5.算术和逻辑操作
(1)加载有效地址
如果寄存器%edx的值为x,则
(2)一元操作和二元操作
一元操作——只有一个操作数,既是源又是目的。
二元操作——第一个是源操作数(立即数、寄存器或存储器位置),第二个是目的操作数(寄存器或存储器位置)。
(3)移位操作
左移指令:SAL、SHL,均将右边填上0;
右移指令:SAR执行算术移位(填上符号位),SHR执行逻辑移位(填上0)。
(4)特殊的算术操作
6.控制
数据相关的控制流是实现有条件行为的方法。通常,C语言中的语句和指令根据他们在程序中出现的次序顺序执行。
(1)
条件码
(2)
访问条件码
条件码通常不会直接读取,有三种办法:
a.根据条件码的某个组合,将一个字节设置为0或1——SET指令;
b.条件跳转到程序的其他某个部分;
c.有条件的传送数据。
(3)跳转指令及其编码
Jmp指令是无条件跳转,可以直接跳转(.L1),即跳转目标是作为指令的一部分编码的;也可以间接跳转(*),即跳转目标是从寄存器或存储器位置中读出的。
!跳转指令有几种不同的编码,最常用的是PC相关的。
(4)
翻译条件分支
结合有条件和无条件跳转讲条件表达适合语句从C语言翻译成机器代码
(5)
循环
(6)条件传送指令
处理器通过使用流水线来获得高性能;采用非常精密的分支预测逻辑猜测每条跳转指令是否执行。
(6)
Switch语句
进行多重分支。使用跳转表这种数据结构。
!执行switch语句的关键步骤是通过跳转表来访问代码位置。
7.过程
(1)
栈帧结构
(2)
转移控制
(3)
寄存器使用惯例
8.数组分配和访问
(1)
基本原则
(2)
指针运算
(3) 嵌套的数组
(4)
定长数组和变长数组
9.异质的数据结构
创建数据类型机制的两种不同类型的对象:
●结构:用关键字struct声明,将多个对象集合到一个单位中
●联合:用关键字union声明,允许用几种不同的类型来引用一个对象。
【这里主要看书上的例子讲解】
10.综合:理解指针
(1)每个指针都对应一个类型
(2)每个指针都有一个值
(3)指针用&运算符创建
(4)操作符用于指针的间接引用
(5)数组与指针紧密联系
(6)指针也可以指向函数
11.应用:使用 GDB调试器
这些前几周已经开始接触并练习
12.存储器的越界引用和缓冲区溢出
!对抗缓冲区溢出攻击
a.栈随机化
b.栈破坏检测
c.限制可执行代码区域
13.X86-64: 将IA32扩展到64位(了解知识)
14.浮点程序的机器级表示
浮点体系结构——存储模型、指令和传递规则的组合。(x87和SSE)
遇到的问题: