来自《深入理解计算机系统》CMU-CSAPP课程
一、汇编代码
二、程序转换与机器级表示
汇编代码
1.区分CPU架构—X86、ARM、MIPS
01.细节比较
| 特征 | x86 架构 | ARM 架构 | MIPS 架构 |
|---|---|---|---|
| 架构类型 | CISC | RISC | RISC |
| 寄存器名 | eax, ebx, esp, eip 等 |
r0–r15, sp, lr, pc |
$t0–$t9, $s0–$s7, $zero, $ra, $sp 等 |
| 语法风格 | Intel(常见)或 AT\&T | ARM 语法(类 Intel 风格) | 类似于 MIPS 自己的风格 |
| 典型指令 | mov, push, call, jmp |
ldr, str, mov, bl, b |
lw, sw, li, jal, jr |
| 调用指令 | call |
bl (Branch with Link) |
jal (Jump and Link) |
| 返回指令 | ret |
bx lr |
jr $ra |
| 注释符号 | ; |
@ |
# |
| 字节序 | 小端(默认) | 可大端可小端 | 多为大端,也支持小端 |
02.二进制反编译工具
ELF文件可用readelf -h查看Machine字段判断是ARM, MIPS, Intel 80386等
2.区分Intel与AT&T汇编
01.细节比较
| 特征 | Intel 语法 | AT\&T 语法 |
|---|---|---|
| 汇编器 | MASM, NASM, IDA 默认 | GNU as(GAS)、objdump, GCC 默认 |
| 操作数顺序 | 目标 ← 源(Destination first) |
源 → 目标(Source first) |
| 寄存器前缀 | 无前缀,如:eax |
以 % 开头,如:%eax |
| 立即数前缀 | 无前缀 | 以 $ 开头,如:$0x10 |
| 内存引用 | DWORD PTR [eax] |
(%eax) |
| 语法风格 | 类似 C 语言赋值 | 函数式语法风格 |
| 注释符 | ; |
# |
Intel更接近C语言赋值顺序:mov eax, 1就像eax = 1
02.常见二进制编译与反编译工具的默认语法
| 环境 | 默认语法 |
|---|---|
| NASM、MASM、IDA Pro | Intel |
| GCC、objdump、GAS | AT\&T |
| Ghidra / Binary Ninja / Radare2 | 可自定义,默认多数为 Intel |
程序转换与机器级表示
写在前面
数的表示
- 原码:正数符号位为0;负数符号位为1
- 问题:无法进行直接加减运算
- 反码:【正数】反码与原码相同;【负数】将原码除符号位以外取反
- 问题:0有两种表示方法:
+0(0000 0000)和-0(1111 1111)
- 问题:0有两种表示方法:
- 补码:【正数】补码与原码相同;【负数】反码加1
- 注意:求负数的补码步骤:将其绝对值的二进制表示取反后加1。
- 原码表示范围
-127~+127;补码表示范围-128~+127。因此表示-128时是没有办法从原码-反码-加1这个步骤得到补码的,因为一开始就超出了原码的表示范围。整数溢出
- 加法
- 两正数相加,超过正数表示范围
- 两负数相加,超过负数表示范围
- 减法
- 正减负
- 负减正
- 乘法
- 两数相乘。一般
CPU不会直接设置OF溢出标志,需要程序员检查或设置指令
- 两数相乘。一般
寄存器
- 控制寄存器:
CR0~CR4 - 8位寄存器:
AL CL DL BL AH CH DH BH - 16位寄存器:
AX CX DX BX SP BP SI DI - 32位寄存器:
- 8个通用寄存器:
EAX ECS EDX EBX ESP EBP ESI EDI - 段寄存器:
ES CS SS DS FS GS(段寄存器的值和偏移量组合成内存单元的物理地址) - 指令指针寄存器:
EIP,存放下次将要执行的指令在代码段中的偏移量 - 标志寄存器:
EFLAGS
- 8个通用寄存器:
- 64位寄存器:16个通用寄存器,用
rdi,rsi,rdx,rcx,r8,r9保存1 $\sim$ 6个参数,用rax保存返回值
一、寄存器分类
控制寄存器
- CR0:
- 系统控制寄存器
- 0bit: protected enable,PE=1时启动保护模式
- 1bit: moniter coprocessor,监控协处理位
- 2bit: emulate coprocessor,模拟协处理器位,EM=1时不能使用协处理器
- 3bit: task switch,任务转换完成后TS置为一,随之不能使用协处理器(与MP合作)
- 4bit: processor extension type,保存处理器拓展类型的信息,ET=0表示使用287协处理器,ET=1表示使用387浮点协处理器
- 16bit: write-able protected 写保护未即位,WP=0则禁止写保护
- 31bit: paging enable。
- CR1
- 未定义的控制寄存器
- CR2
- 页故障线性地址寄存器
- CR3
- 页目录基址寄存器
- 保存页目录表的物理地址
- CR4
- 在pertium系列处理器中实现
- 如何启动虚拟8086模式

8位和16位寄存器
| 如表所示 : |
|分类 | 名称| |————|————————-| |8位寄存器 | AL CL DL BL AH CH DH BH| |16位寄存器 | AX CX DX BX SP BP SI DI|
32位8个通用寄存器
|寄存器名称 |类型 | 含义 |惯例| |————–|———|———-|——-| |EAX |数据寄存器|累加器 accumulator,操作数和结果数据累加器|调用者保存| |ECX |数据寄存器|计数器 counter, 字符串和循环操作计数器 |调用者保存| |EDX |数据寄存器|乘法形成的部分结果或除法之前的部分被除数 |调用者保存| |EBX |数据寄存器|base,基地址,在内存寻址时存放基地址 |被调用者保存| |ESP |指针寄存器|stack pointer,指向栈顶,即栈指针 |被调用者保存| |EBP |指针寄存器|base pointer,保存栈帧的起始地址(基地址),即帧指针。这样才顺利返回到调用函数。ss段的数据指针|被调用者保存 |ESI |变址指针寄存器|string操作的源指针 source,ss段的数据指针|不得占用| |EDI |变址指针寄存器|string操作的目标指针 destination,ES段的数据指针|不得占用|
段寄存器
用于分段内存管理,段寄存器[^A] 的值+偏移量组合成内存单元的物理地址 在早期16位实模式中很关键 在32位保护模式、64位长模式中主要是FS\GS用于TLS线程管理
- 实模式下,
物理地址 = 段寄存器 * 16 + 偏移量- 如
[ds:0x1234]表示地址DS × 16 + 0x1234
- 如
| 寄存器名 | 全称 | 典型用途(在实模式/保护模式) |
|---|---|---|
CS |
Code Segment | 代码段寄存器,指示代码所在的段地址(与 IP 配合) |
DS |
Data Segment | 数据段寄存器,默认用于访问数据(变量、数组) |
SS |
Stack Segment | 栈段寄存器,与 SP/ESP 配合访问栈 |
ES |
Extra Segment | 额外段寄存器,字符串操作和扩展用途 |
FS |
Additional Segment | 附加段寄存器,操作系统/线程局部存储使用 |
GS |
General Segment | 一般段寄存器,和 FS 类似,用于高级系统用途 |
64位的16个通用寄存器
64位CPU有16个通用寄存器,64位寄存器与32位寄存器的区别:
-
32位
CPU用栈帧作为参数的保存位置,64位CPU用rdi,rsi,rdx,rcx,r8,r9保存1 $\sim$ 6个参数,用rax保存返回值 -
32位
cpu用ebp作为栈帧指针,64位cpu将rbp作为通用寄存器 -
64位支持形式的以
PC相关的寻址
标志寄存器EFLAGS/RFLAGS
包含多个标志位
- CF-carry flag:表示无符号运算的进位或借位
- 无符号数加法产生的进位,或无符号数减法产生的借位。都将CF设置为1,表示无符号数运算的溢出。
- ZF-zero flag:若结果为0,则ZF=1
- SF-sign flag:运算结果的符号。若结果为负,则SF为1
- 可以用作条件跳转、分支控制等操作
-
OF-overflow flag:表示有符号数运算发生溢出
flags 含义 备注 CF carry 进位符 PF parity 奇偶标志位 用于奇偶校验 AF auxiliary carry 辅助进位符 字操作中,发生低字节向高字节进位或借位;字节操作中,发生低4位向高4位进位或借位 ZF zero flag SF Sign flag 正数为0,负数为1 OF overflow TF trap flag 用于程序执行,状态控制标志位 IF interrupt-enable 中断允许 状态控制标志位 DF direction flag 状态控制标志位
二、寄存器寻址
16位CPU寄存器的寻址
- 只能使用基地址寄存器(
BX和BP)和变址寄存器(SI和DI)来作为偏移地址的一部分。这里需要补充16位寄存器的寻址方式
32位CPU寄存器的寻址
- 指令1:
mov ebx,[eax+edx*2+300]- 地址中寄存器的书写顺序决定该寄存器是基址寄存器还是变址寄存器,在指令1中
\%eax为基址寄存器,\%edx为变址寄存器,300为偏移常量。
- 地址中寄存器的书写顺序决定该寄存器是基址寄存器还是变址寄存器,在指令1中
附录一、标志寄存器的标志位
显式设置标志位的指令
cmp指令
减法操作,只影响标志位
test指令
按位与操作,只影响标志位(
ZF和SF)。 通常用于检查特定位的状态(如检查某个位是否为1;检查某个值是否为0)。
test ax 1检查ax的最低位是否为1。如果是,则ZF为0。test %rax %rax当%RAX的值为0时,结果为0,则ZF设置为1。test指令将运算结果的最高位赋值给SF。
如何使用条件码(标志位)
理论上可以直接从条件寄存器上读取,但一般不这样做【读取或写入标志寄存器通常需要借助 PUSHF、POPF或类似的指令,这些操作通常会导致不必要的性能开销。】。
可以使用条件跳转指令配合cmp或test指令。
setcc
根据标志寄存器的某些标志位设置目的寄存器的值(通常是8位寄存器、或者是16位、32位寄存器的低8位,也就是单个字节的值) 用来判断某些条件是否成立。
- setcc只能修改目的寄存器单个字节的值,如果要扩展到32位、64位,需要配合mov指令
movzbl %al %eax:z表示零扩展(对应s表示符号扩展);前缀b表示源操作数的位宽是1个字节;前缀l表示目的操作数的位宽是long类型4个字节。