既然 complier 能帮助我们将 code 转换为 low-level language 我们为什么还要学习呢?
- By invoking the compliler with appropriate command-line parameters(通过使用适当的命令行参数调用编译器)
编译器将生成一个以汇编代码形式显示其输出的文件 - 通过阅读代码,我们可以了解编译器的优化功能,并分析代码中潜在的低效率
程序员学习机器代码的需求已经转变为阅读和理解编译器生成的代码。
读汇编的过程实际上是一个 a form of reverse engineering(逆向工程的一种形式)----->试着通过研究系统和向后工作来理解系统创建的过程
- 项目的展示基于 X86-64:一台32位的机器只能使用大约4gb(2^32)位的随机存取存储器
3.2 Program Encodings
当我们写完 c program 我们可以compile通过
linux> gcc -Og -o p p1.c p2.c
- -Og表示编译器应用一定级别的优化,生成遵循原始C代码整体结构的机器代码
- 当然优化程度越高, C代码和汇编的差距越大,因此采用不那么大的 -Og 来进行学习
3.2.1 Machine-Level Code
计算机系统有两种重要的抽象
第一种 指令集: 机器级程序的格式和行为由指令集体系结构(ISA)定义
(1)描述处理器状态
(2)指令的格式
(3)每条指令执行之后的status
第二种 Virtual Memory(虚拟内存)
一些在C代码中隐藏的细节,可以在汇编代码中看到
(1)program counter(计数器)在x86-64中被叫做 %rip
(2)integer registee file(整数注册文件):可以存储 地址/指针;integer data;存储某些程序重要状态
(3)Condition Code Registers(条件代码寄存器):实现对于不同指令状态的记录, 比如跳转啥的
(4)A set of vector registers 存储 interger / floating point: 一组整数/浮点数向量寄存器
3.2.2 Code Examples
linux> gcc -OG -S mstore.c
-S:表明告诉编译器 to generate assembly file and go no further(以生成程序集文件,并没有进一步)
long mult2(long, long);
void multstore(long x, long y, long* dest){
long t = mult2(x, y);
*dest = t;
}
assembly:
mulstore:
pushq %rbx //将rbx中内容 push 到 program stack
movq %rdx, %rbx
call mult2
moveq %rax, (%rbx)
popq %rbx
ret
如果我们选择了
linux> gcc -Og -c mstore.c
这样会generate an object-codee file mstore.c:
类似
53 48 86 d3 e8 00 00 00 00 48 5b c3
想知道binary code 代表什么意思,可以使用disassemblers
linux 系统中有 OBJDUMP 可以 实现
linux> objdump -d mstore.c
对于机器表示的内容
- X86-64 指令集 每条指令 1-15个 bytes
一般来说 操作数越多,指令bytes越多 - 每个指令都是 prefix-free, 类似霍夫曼coding
从给定的起始位置开始,有一个唯一的字节解码成机器指令
比如 只有pushq %rbx
can start with byte value 53
Notes on Formatting
一般我们采用
linux> gcc -Og -S mstore.c
Data Formats
由于历史设计的原因
Intel uses “word” to descript 16-bit data type
- 32-bit 被叫做 double word
- 64 bit quad words
- X86家族中 Microprocessors (微处理器)实现了 all floating-point operations with a special 80-bit floating-point format(所有具有特殊80位浮点格式的浮点操作)
- 大多数的 assembly-code (汇编代码)通过后缀表明到底操作的是 什么 word
(1)movb (move byte)
(2)movw(move word)
(3)movl(move double word)
(4)movq(move quad word)
Accessing Information(访问信息)
X86-64 CPU 包含了 16个 64位 寄存器
- used to store integer data as well as pointer(用于存储整数数据和指针+
- Byte-level operations can access the least significant byte(字节级操作可以访问最不重要的字节)
- 16-bit operations can access the least significant 2 bytes(16位操作可以访问最不重要的2个字节)
- 32-bit operations can access the least significant 4 bytes(32位操作可以访问最不重要的4个字节)
- 64-bit operations can access the whole register(64位操作可以访问整个寄存器)
3.4.1Operand Specifiers操作数说明符
指令中几种不同的操作数:
- immediate -> constant values $577
- register --> $5
- memory reference(内存引用):
(1)因为我们认为内存是一个巨大的数组
(2)我们采用数组形式access:Mb[Addr] 表示 a reference to the b-byte value store in memory starting at address
为啥要有这么复杂的 内存reference格式?
它们在引用数组和结构元素时非常有用
3.4.2 Data Movement Instructions(数据移动说明)
Move class, 最简单的形式, 就是从一个地方拷贝到另外一个地方
注意这个图片, 如果 move bytes 那么只动后面两个
MOVZ class 会将dest 的剩余数字全部填充为 0
MOVS class 会将剩下的数字填充 based on sign extension
Data Movement Example
C code
long exchange(long* xp, long y){
long x = *xp;
*xp = y;
return x;
}
Assembly Code
对上述code进行两点剖析
-
在 C中的pointer 实际上 deferencing a pointer involves
(1)Copying that pointer into a register(将该指针复制到寄 存器中)
(2)use this register in a memory reference(在内存引用中使用此寄存器) -
local variables such as X are often kept in registers rathre than stored in memory locations(局部变量(如X)通常保存在寄存器中,而不是存储在内存中)
(1)因为 Register Access 通常比 Memory Locations 快很多
Pushing and Poping Data
The stack plays a vital role in the handling of procedure calls(堆栈在过程调用的处理中起着至关重要的作用)
注意:
- push操作涉及堆栈指针的递减
(1)实际上分两步, 第一步:堆栈指针减8
(2)然后在策略地址的新顶部写入值 - pop包括从内存中读取和堆栈指针的增量
pushq % rbp 和 下面两步相同
popq %rax 和下面两步相同
3.5 Arithmetic and Logical Operations(算术和逻辑运算)
蛮多汇编指令有 variant (变体)在 word size 比如:
- addb
- addw
- addl
- addq
3.5.1 Load 指令
当然load指令也有一些用处 当实施简单的加减法
long scale(long x, long y, long z){
long t = x + 4 * y + 12 * z;
return t;
}
3.5.2 Unary and Binary Operations(一元运算和二元运算)
第二组指令是 只有一个操作数 的指令
- 这个操作数可以是一个寄存器,也可以是一个内存位置
incq(%rsp) //这条指令让 stack 顶的元素 + 1
类似与x ++,y –
第三组指令是
subq %rax, %rdx
实际上的作用是 %rdx - %rax
英文语义是 subtract %rax from %rdx
同时对于 MOV 指令来说, 两个 oprand 不能同时是 memory location
- 如果 destination 是 memory 那么处理器必须从内存中读取值,执行操作,然后将结果写回内存