原地址:https://www.cnblogs.com/disturbia/p/4869229.html
第三章 程序的机器级表示
3.1 历史观点
- Intel处理器系列俗称x86,开始时是第一代单芯片、16位微处理器之一。
- 每个后继处理器的设计都是后向兼容的——较早版本上编译的代码可以在较新的处理器上运行。
- X86 寻址方式经历三代:
1 DOS时代的平坦模式,不区分用户空间和内核空间,很不安全
2 8086的分段模式
3 IA32的带保护模式的平坦模式
3.2 程序编码
gcc -01 -o p p1.c
- -01 表示使用第一级优化。优化的级别与编译时间和最终产生代码的形式都有关系,一般认为第二级优化-02 是较好的选择。
- -o 表示将p1.c编译后的可执行文件命名为p
3.2.1机器级代码
- 计算机系统使用了多种不同形式的抽象,对于机器级编程来说,两种抽象尤为重要。第一种是机器级程序的格式和行为,定义为指令集体系结构(ISA),他定义了处理器状态、指令的格式,以及每条指令对状态的影响。
-
几个处理器:
- 程序计数器(CS:IP)
- 整数寄存器(AX,BX,CX,DX)
- 条件码寄存器(OF,SF,ZF,AF,PF,CF)
- 浮点寄存器
3.2.2代码示例
在命令行上使用“-S”选项,就能得到C语言编译器产生的汇编代码:
unix> gcc -01 -S code.c
这会使GCC运行编译器产生一个汇编文件code.s,但不做其他进一步工作
如果在命令行上使用“-C”选项,GCC会编译并汇编该代码:
unix> gcc -01 -c code.c
这就会产生目标代码文件code.o,他是二进制格式,无法直接查看。
要查看目标代码文件的内容,需要使用反汇编器:
unix> objdump -d code.o
机器代码和它的反汇编表示的一些特性:
- IA32指令长度从1到15个字节不等。
- 设计指令格式的方式是,从某个给定位置开始,可以将字节唯一的解码成机器指令。
- 反汇编器只是基于机器代码文件中的字节序列来确定汇编代码,不需要访问程序的源代码或汇编代码。
- 反汇编器使用的指令命名规则与GCC生成的汇编代码使用的有些差别。
3.3 数据格式
1.Intel中:
8 位:字节
16位:字
32位:双字
64位:四字
2.c语言基本数据类型对应的IA32表示
char 字节 1字节
short 字 2字节
int 双字 4字节
long int 双字 4字节
long long int (不支持) 4字节
char * 双字 4字节
float 单精度 4字节
double 双精度 8字节
long double 扩展精度 10/12字节
3.数据传送指令的三个变种:
- movb 传送字节
- movw 传送字
- movl 传送双字
3.4 访问信息
3.4.1操作数指示符
大多数指令都有一个或多个操作数,指示出执行一个操作中要引用的源数据值,以及放置结果的目标位置。
操作数的三种类型
- 立即数
- 寄存器
- 存储器
寻址方式:
1.立即数寻址方式
2.寄存器寻址方式
3.存储器寻址方式
- 直接寻址方式
- 寄存器间接寻址方式
- 寄存器相对寻址方式
- 基址变址寻址方式
- 相对基址变址寻址方式
3.4.2 数据传送指令
mov类指令:将源操作数的值复制到目的操作数中。源操作数指定的值是一个立即数,存储在寄存器中或存储器中。目的操作数制指定一个位置,要么是一个寄存器,要么是一个存储器。
- movb 传送字节
- movw 传送字
- movl 传送双字
- movs 符号位扩展
- movz 零扩展
push:把数据压入栈中
pop:删除数据
- 后进先出
- 栈指针指向栈顶元素
- 栈朝低地址方向增长
3.4.3 数据传送示例
- c语言中的指针其实就是地址,间接引用指针就是将该指针放在一个寄存器中,然后在存储器引用中使用这个寄存器
- 局部变量通常保存在寄存器中,而不是存储器
3.5 算术和逻辑操作
3.5.1 加载有效地址
加载有效地址指令leal实际上就是movl指令的变形。它的指令形式是从存储器读取数据到寄存器,但实际根本没引用存储器。
3.5.2 一元操作和二元操作
一元操作:只有一个操作数,既是源又是目的,可以是一个寄存器,或者存储器位置。
二元操作:源操作数 目的操作数
-
第一个操作数可以是立即数、寄存器或者存储器位置
-
第二个操作数可以是寄存器或者存储器位置
-
但是不能同时是存储器位置
3.5.3 移位操作
- SAL 算术左移
- SHL 逻辑左移
- SAR 算术右移(补符号位)
- SHR 逻辑右移(补0)
3.5.5 特殊的算术操作
3.6 控制
3.6.1 条件码
-
CF:进位标志
-
ZF:零标志
-
SF:符号标志
-
OF:溢出标志
3.6.2 访问条件码
条件码不会直接读取,常用的使用方法有三种:
- 可以根据条件码的某个组合,将一个字节设置为0或者1
- 可以条件跳转到程序的某个其他的部分
- 可以有条件地传送数据
执行比较指令,根据计算t=a-b设置条件码。
3.6.3 跳转指令及其编码
跳转指令会导致执行切换到程序中一个全新的位置。在汇编代码中,这些跳转的目的地通常用一个标号指明。
跳转指令有几种不同的编码,最常用的是PC(程序计数器)相关的。
jump分为:
直接跳转:后面跟标号作为跳转目标
间接跳转:*后面跟一个操作数指示符
当执行与PC相关的寻址时,程序计数器的值是跳转指令后面的那条指令的地址,而不是跳转指令本身的地址。
3.6.4 翻译条件分支
将条件表达式和语句从c语言翻译成机器语言,最常用的方式就是结合有条件和无条件跳转。
3.6.5 循环
汇编中没有do-while、while和for相应的指令存在,可以用条件测试和跳转组合起来实现循环的效果。大多数汇编器中都要先将其他形式的循环转换成do-while格式。
1.do-while循环
通用形式:
do
body-statement
while(test-expr);
循环体body-statement至少执行一次。
可以翻译成如下条件和goto语句:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
每次循环,程序会执行循环体的语句,然后执行测试表达式。
2.while循环
通用形式:
while (test-expr)
body-statement
GCC的方法是使用条件分支,在需要时省略循环体的第一次执行:
if(!test-expr)
goto done;
do
body-statement
while(test-expr);
done:
接下来,这个代码可以直接翻译成goto代码:
t = test-expr;
if(!t)
goto done:
loop:
body-statement
t = test-expr;
if(t)
goto loop;
done:
3.for循环
通用形式:
for(init-expr;test-expr;update-expr) body-satament do-while形式: init-expr; if(!test-expr) goto done; do{ body-statement update-expr; }while(test-expr); done: 翻译成goto代码: init-expr; t=test-expr; if(!t) goto done; loop: body-statement update-expr; t= test-expr; if(t) goto-loop; done:
3.6.6 条件传送指令
实现条件操作的传统方法是利用控制的条件转移。
数据的条件转移是一种替代的策略。此方法先计算一个条件操作的两种结果,然后再根据条件是否满足从而选取一个。
只有在一些受限制的情况下,这种策略才可行,但如果可行,就可以用一条简单的条件传送指令来实现它。
条件传送指令更好地匹配了现代处理器的性能特性。
3.6.7 Switch语句
3.7 过程
3.7.1 栈帧结构
栈用来传递参数、存储返回信息、保存寄存器,以及本地存储。
1.栈帧:
为单个过程分配的那部分栈称为栈帧。
2.最顶端的栈帧以两个指针界定:
-
寄存器%ebp-帧指针
-
寄存器%esp-栈指针
3.7.2转移控制
1.call
CALL指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。
返回地址是还在程序中紧跟在call后面的那条指令的地址。
2.ret
ret指从栈中弹出地址,并跳转到这个位置。
3.leave
这个指令使栈做好返回的准备
3.7.3 寄存器使用惯例
程序寄存器组是唯一能被所有过程共享的资源。
3.8 数组分配和访问
3.8.1 基本原则
3.8.2 指针运算
3.8.3 嵌套的数组
3.8.5 变长数组
3.9 异质的数据结构
创建数据类型机制的两种不同类型的对象:
●结构:用关键字struct声明,将多个对象集合到一个单位中
●联合:用关键字union声明,允许用几种不同的类型来引用一个对象。
3.10 综合:理解指针
(1)每个指针都对应一个类型
(2)每个指针都有一个值
(3)指针用&运算符创建
(4)操作符用于指针的间接引用
(5)数组与指针紧密联系
(6)指针也可以指向函数
3.11 应用:使用 GDB调试器
3.12 存储器的越界引用和缓冲区溢出
!对抗缓冲区溢出攻击
a.栈随机化
b.栈破坏检测
c.限制可执行代码区域
3.13 X86-64: 将IA32扩展到64位(了解知识)
3.14 浮点程序的机器级表示
浮点体系结构——存储模型、指令和传递规则的组合。(x87和SSE)