来自《深入理解计算机系统》CMU-CSAPP课程 一、汇编代码
二、程序转换与机器级表示
汇编代码
1.区分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溢出标志,需要程序员检查或设置指令
寄存器
- 两数相乘。一般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 CC DS FS GS(段寄存器的值和偏移量组合成内存单元的物理地址)
- 指令指针寄存器:EIP
- 标志寄存器:EFLAGS
- 64位寄存器:16个通用寄存器,用rdi,rsi,rdx,rcx,r8,r9保存1 $\sim$ 6个参数,用rax保存返回值
比较及循环结构
标志寄存器EFLAGS/RFLAGS
包含多个标志位
- CF-carry flag:表示无符号运算的进位或借位
- 无符号数加法产生的进位,或无符号数减法产生的借位。都将CF设置为1,表示无符号数运算的溢出。
- ZF-zero flag:若结果为0,则ZF=1
- SF-sign flag:运算结果的符号。若结果为负,则SF为1
- 可以用作条件跳转、分支控制等操作
- OF-overflow flag:表示有符号数运算发生溢出
显式设置标志位的指令
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个字节。