CSAPP 第三章 程序在机器内部的表示

作者 QIFAN 日期 2017-04-11
CSAPP 第三章 程序在机器内部的表示

课程官网: http://csapp.cs.cmu.edu/3e/home.html

(unfinished)

3.2 Program Encoding

程序在机器中本质是一系列二进制编码的指令。

计数器(counter) :通常指 PC ,在 x86-64 系统中称为 %rip 。表明下一条被执行指令的内存地址。

寄存器(register):包括 16 个指定的地点用于存放 64-bit 的值。可以存储 C 指针地址或者 integer 数据。有一些保存程序中的关键部分,有一些存储临时值,比如参数、本地变量、方法返回值等。具体各个寄存器的分工在 3.4 中会详说。

C 的编译使用 Unix 命令行:gcc -Og -o p p1.c p2.c 。其中 -Og 是告诉编译器进行机器层的优化,这个可以作为一个基线与高层优化(如 -O1-O2)。-o p 参数是最后生成一个名为 p 的可执行文件。具体过程如下:

  1. 展开所有还有 #include 中包含的宏。
  2. 编译器将 C 代码(后缀为 .c)转为相应的汇编码版本的 .s 文件。
  3. Assembler 将 .s 文件转为二进制的对象编码 .o 文件。
  4. Linker 将所有对象编码文件合并,并生成一个可执行文件 p 。
    上述任一过程的文件都可用相应的命令行得到。

3.3 Data Formats

term word double word quad word
16-bit data type 32-bit quantites 64-bit quantites

如下是 x86-64 系统中 C 的数据类型:

C declaration Intel data type Assembly-code suffix size(bytes)
char Byte b 1
short Word w 2
int Double word l 4
long Quad word q 8
char * Quad word q 8
float Single precision s 4
double Double precision l 8

3.4 Accessing Information

寄存器

一个 x86-64 CPU 中有 16 个通用的寄存器,如下图(略糊见谅)。同一行内四个名字分别代表了生成 1、2、4、8 byte 值时改变的寄存器的位数。比如改 %eax 时,只改变低 32 位的值。

registers

就 8-bytes 的值来再对寄存器说一下,尽量记住,之后看汇编码的时候就可以少花点时间想这寄存器存的是啥。

%rax 用与存方法的返回值
%rdi 第一参数
%rsi 第二参数
%rdx 第三参数
%rcs 第四参数
%r8 第五参数
%r9 第六参数
%rsp 只用于存储运行时栈的结束位置地址。
其他的都用于存取本地变量

可以看见对于一个方法,只有前六个参数可以存在寄存器中,如果超过六个,多出来的会直接存在栈寄存器 %rsp 里。

汇编码

值的操作

x86-64 系统支持下面的一些运算形式来获取某个值。记住只要是有括号的都代表的是内存地址上的值。

Type Form Operand Value Note
Immediate $\$Imm$ $Imm$ 常数值
Register $r_a$ $R[r_a]$ 某个 register 的内容
Memory $Imm(r_b, r_i, s)$ $M[Imm + R[r_b] + R[r_i] * s]$ 某个内存地址。 offset $Imm$, base register $r_b$, index register $r_i$, scale factor $s$ ($s$ = 1, 2, 4 or 8) 。 effective address = $Imm + R[r_b] + R[r_i] * s$

值的移动

末尾字母对应不同位数的值,对应看 3.3 。
形式是: mov Source, Dest 说是移动,其实就是把 Source 中的值赋值到 Dest 。
当把一个较小位数的值赋值到较大位数的寄存器是,会使用下面两个 move:
movzb S,D 0 扩展,在多出来的位数填 0
movsb S,D 1 扩展,在多出来的位数填 1

指令 类型 操作
movl $0x4050, %eax 值到寄存器 把 0x4050 存到 %eax
movew %bp, %sp 寄存器到寄存器 把 %bp 中的值复制到 %sp 中
moveb (%rdi, %rcx), %al 内存到寄存器 把 %rdx + %rcx 地址里的值存到 %al 中
movel $-17, (%esp) 值到内存 把 %esp 存的地址对应的值变为 -17
moveq %rax, (%rbp) 寄存器到内存 把 %rbp 中的地址对应的值变为 %rax 存的值

move 操作有几点要注意:

  • dest 和 source 必须有一个对应的是内存地址或者寄存器
  • move 指令与移动的对象要一致,比如用 movl 就不能移动 8 位的值
  • Imm 不能作为被赋值的对象

例子:
swap 时的机器指令。

  • C code

    long exchange(long *xp, long y) {
    long x = *xp;
    *xp = y;
    return x;
    }
  • 汇编语言

    // 设 xp 在 %rdi, y 在 %rsi
    exchange:
    movq (%rdi), %rax // 另 x 存储 *xp 地址指向的值
    movq %rsi, (%rdi) // 另 *xp 的地址指向 y 地址指向的值
    ret

栈的操作

pushq 把地址推入栈
pupq 把地址抛出

%rsp 负责运行栈的地址存储,在进入一个方法前,会先把 %rip 中,也就是当前代码的下一行地址放入栈中,作为方法结束后返回的地址。

3.5 Arithmetic and Logical Operations

参数 含义
leaq S,D &S –> D 有点像 mov,不过是赋地址
addq S,D D + S –> D
imulq S,D D * S –> D
salq k,D D << k –> D 算术左移
sarq k,D D >> k –> D 算术右移
shlq k,D D << k –> D 逻辑左移
shrq k,D D >> k –> D 逻辑右移
xorq S,D D ^ S –> D
andq S,D D & S –> D
orq S,D D ` ` S –> D
incq D D + 1 –> D
decq D D - 1 –> D
negq D -D –> D
notq D ~D –> D

3.6 Control

状态代码

  • CF : Carry Flag 。上一次操作在最高位有进一位,通常用于标记 unsigned overflow
  • ZF : Zero Flag 。上一次操作标记值为 0
  • SF : Sign Flag ,上一次操作标记为负
  • OF : Overflow Flag ,上一次操作产生 two’s-complement overflow

条件运算

这一类对应的是代码中的条件运算,if..else..
比较与测试:
| 参数 | 含义 |
| —| — |
| cmpq S1,S2 | $S_1 - S_2$|
| testq S1,S2 | $S_1 \& S_2$|

对应操作:
set : 设置 flag
jmp : 跳到指定地址

条件都是类似的,这就就列举 jmp 的。

指令 条件
jmp
je 相等或为零
jne 不相等/不为零
js 为负
jns 不为负
jg signed 大于
jge signed 大于等于
jl signed 小于
jle signed 小于等于
ja unsigned 大于
jae unsigned 大于等于
jb unsigned 小于
jbe unsigned 小于等于

Loops

使用一个 cmp 或者 test ,若满足条件就跳到循环开始处。