程序的机器级表示
程序的机器级表示
程序编码
### 机器级代码
机器代码==汇编代码
计算机系统总存在大量抽象,屏蔽了实现的细节
- 指令集架构ISA 定义机器语言的格式和行为 具体实现是并发的
- 虚拟内存地址 内存模型为一个大字节数组
一些对c隐藏的寄存器在汇编可见
- 程序计数器 PC
- 整数寄存器 16个
- 条件寄存器
- 一组向量寄存器
汇编代码不区分有无符号、指针类型 甚至不区分指针与整数
### 寄存器
%r 64位
%e 32位 (为%r的低32位)
寄存器通过名字存取
汇编会丢失变量名等执行 无关的信息,反汇编时不能恢复

指令可以对16个寄存器的低位字节中存放的不同大小的数据进行操作
- 1字节、2字节指令会保持剩下的字节不变
- 4字节指令会把高4字节置0


- 以立即数位目的无意义,不允许直接从内存复制到内存(mem→mem)
- Imm(rb,ri,s) == Imm+R[rb]+R[ri]*s
- 指针就是地址
- 局部变量存在寄存器中,如果寄存器数量不足,则局部变量被存储在称栈段的内存中。
- 类型转换时
- 大转小:读取%rax低字节
- 小转大:零扩展、符号扩展
- 不存在movzlq,可以直接使用movl。因为当使用movl传输数据到32位目的寄存器中时, 会自动将目的寄存器的高位4字节置零。
- cltq没有操作数,总是以%eax作为源寄存器,以%rax作为目的寄存器,等价于movslq %eax,%rax。
- 一些惯例
%rsp 指向栈顶
%rax 为返回值
位移运算只能用%cl地位 即完成ch2中的取模
### 加载有效地址 lea
load effect addr
lea是计算内存地址,然后把内存地址本身放进寄存器里。
因为计算地址是Imm(rb,ri,s) == Imm+R[rb]+R[ri]*s 的形式
所以c编译器喜欢用于加速计算
e.g.计算2的幂次 x+n*x
- 目的操作数只能是寄存器。
## 控制
### 条件码
CF 进位(无符号) ZF 零 SF 符号 OF 溢出(有符号)
leaq不改变条件码,因为是用于进行地址计算的
访问条件码
1. set 根据条件码的组合,将某个字节设置 为0或1
2. 条件跳转到程序的某个其他部分
3. 有条件地传输数据
### 跳转
jmp 无条件跳转
jmp *Operand 间接跳转
跳转指令的编码
- PC相对地址
- 绝对地址
### 条件分支
使用条件控制来实现条件分支
t = test-expr;
if(!t)
goto false;
then-statement
doto done;
false:
else-statement
done:
- 传统方法:使用控制的条件转移
- 使用数据的条件转移 cmov* 也叫条件传送
计算一个条件操作的两种结果,根据条件是否满足再选择一个
这样可以使得处理器流水线效率最高
但并不是所有条件表达式都可以用条件传送
return (xp? *xp : 0);
### 循环
#### do-while
jg 是实现循环的关键指令,决定了是继续重复还是退出循环
loop:
body-statement
t = test-expr;
if(t)
goto loop;
#### while
jump-to-middle
goto test;
loop:
body-statement
test:
t=test-expr;
if(t)
goto loop;
guarded-do
t=test-expr;
if(!t)
goto done;
loop:
body-statement;
t=test-expr;
if(!t)
goto loop;
done:
#### for
在while循环的基础上加上初始化表达式和更新表达式
jump-to-middle
**init-expr;**
goto test;
loop:
body-statement
** update-expr;**
test:
t=test-expr;
if(t)
goto loop;
guarded-do
**init-expr;**
t=test-expr;
if(!t)
goto done;
loop:
body-statement;
** update-expr;**
t=test-expr;
if(!t)
goto loop;
done:
### switch
根据整数索引 进行多重分支
使用跳转表 使得实现更加高效:执行开关语句的时间与开关情况的数量无关
&& 创建一个指向代码位置的指针
过程 Procedure
机制: P调用过程Q
- 传递控制
PC设置为Q的起始地址,返回时PC设置为P中调用Q的下一条指令的地址
- 传递数据
P向Q提供参数 Q向P返回一个值
可以通过寄存器传递最多6个整型参数(整型或指针)
超出6个的部分通过栈来传递,数据向8的倍数对齐
- 分配和释放内存
#### 栈上的局部存储
必须将数据放在内存中的情况
- 寄存器不足
- 使用了地址运算符&
- 局部变量是数组或结构
通过减小栈指针在栈上分配空间
#### 寄存器中的局部存储
寄存器是唯一被所有过程共享的资源。
被调用者保存寄存器6个:%rbx、%rbp和%r12—%r15
其他的 除了%rsp,均为调用者保存寄存器
#### 递归
每个过程调用在栈中都会有自己的私有空间,因此多个未完成调用的局部变量不会相互影响。
数组
一种将标量数据聚集成更大数据类型的方式(同质)
C语言可以产生指向数组中元素的指针,并对这些指针进行运算
### 基本原则
通过声明,在内存中分配L*N字节的连续区域,并引标识符,作为指向数组开头的指针
指针数组每个元素都是8字节的指针
C语言会根据指针引用的数据类型的大小进行伸缩
### 嵌套数组
按照“行优先”的原则在内存中排列
T D[R][C];
元素D[i][j]的内存地址为:&D[i][j]=Xd+L(C*i+j);
### 变长&定长
C99允许数组的维度是表达式,在数组分配时才被计算出来
- 增加了参数n,寄存器的使用变化了
- 使用了乘法指令计算n*i,而不是leaq,会带来严重的性能处罚
如果允许优化,GCC能识别出程序访问多维数组的元素的步长
异质的数据结构
### 结构
将可能不同类型的对象聚合到一个对象中
编译器维护关于每个结构类型的信息,指示每个字段的字节偏移。
结构的各个字段的选取完全是在编译时处理的,机器代码不包含关于字段声明或字段名字的信息
### 联合体
规避C语言的类型系统,允许以多种类型来引用一个对象。
用不同的字段来引用相同的内存块
### 数据对齐
简化了处理器和内存系统之间接口的设计
控制&数据的结合
- 代码注入
- 编写更健壮的代码
- 系统完成
- ASLR 随机化栈初始位置
- 将区域标记为不可执行
- 使用金丝雀canary检测溢出
- 面向返回编程

浙公网安备 33010602011771号