时隔一年把 CS:APP 再看一遍,尤其针对栈的运行机制加深理解。
16个通用寄存器
一个 x86-64 CPU 包含一组16个存储64位值的通用目的寄存器
。虽然是通用寄存器,但也有一些约定成俗的用法。r8 r9 … 为80386之后扩展的8个寄存器
大多数指令有一个或多个操作数(operand
)指示出执行一个操作中要使用的源操作数,以及放置结果的目的操作数。根据源数据和目的位置的取值可分为三种类型
immediate
), 用来表示常数值register
), 表示某个寄存器的内容类型 | 格式 | 操作数值 | 名称 |
---|---|---|---|
立即数 | $$Imm $ | I m m Imm Imm | 立即数寻址 |
寄存器 | r a r_a ra | R [ r a ] R[r_a] R[ra] | 寄存器寻址 |
存储器 | I m m Imm Imm | M [ I m m ] M[Imm] M[Imm] | 绝对寻址 |
存储器 | ( r a ) (r_a) (ra) | M [ R [ r a ] ] M[R[r_a]] M[R[ra]] | 间接寻址 |
存储器 | I m m ( r b ) Imm(r_b) Imm(rb) | M [ I m m + R [ r a ] ] M[Imm + R[r_a]] M[Imm+R[ra]] | (基址 + 偏移值)寻址 |
存储器 | ( r b , r i ) (r_b, r_i) (rb,ri) | M [ R [ r b ] + R [ r i ] ] M[R[r_b] + R[r_i]] M[R[rb]+R[ri]] | 变址寻址 |
存储器 | I m m ( r b , r i ) Imm(r_b, r_i) Imm(rb,ri) | M [ I m m + R [ r b ] + R [ r i ] ] M[Imm + R[r_b] + R[r_i]] M[Imm+R[rb]+R[ri]] | 变址寻址 |
存储器 | ( , r i , s ) (, r_i, s) (,ri,s) | M [ R [ r i ] ⋅ s ] M[R[r_i] \cdot s ] M[R[ri]⋅s] | 比例变址寻址 |
存储器 | I m m ( , r i , s ) Imm(, r_i, s) Imm(,ri,s) | M [ I m m + R [ r i ] ⋅ s ] M[Imm + R[r_i] \cdot s ] M[Imm+R[ri]⋅s] | 比例变址寻址 |
存储器 | $ (r_b, r_i, s)$ | M [ R [ r b ] + R [ r i ] ⋅ s ] M[R[r_b] + R[r_i] \cdot s ] M[R[rb]+R[ri]⋅s] | 比例变址寻址 |
存储器 | $ Imm(r_b, r_i, s)$ | M [ I m m + R [ r b ] + R [ r i ] ⋅ s ] M[Imm + R[r_b] + R[r_i] \cdot s ] M[Imm+R[rb]+R[ri]⋅s] | 比例变址寻址 |
指令 | 效果 | 描述 |
---|---|---|
M O V S , D MOV \quad S, D MOVS,D | D ← S D \leftarrow S D←S | 传送 |
m o v a b s q I , R movabsq \quad I, R movabsqI,R | R ← I R \leftarrow I R←I | 传送绝对的四字 |
M O V Z S , R MOVZ \quad S, R MOVZS,R | R ← 零 扩 展 ( S ) R \leftarrow 零扩展(S) R←零扩展(S) | 以零进行扩展进行转送 |
M O V S S , R MOVS \quad S, R MOVSS,R | R ← 符 号 扩 展 ( S ) R \leftarrow 符号扩展(S) R←符号扩展(S) | 转送符号扩展的字节 |
m o v s b w S , R movsbw \quad S, R movsbwS,R | 将符号扩展的字节传送到字 | |
c t l q ctlq ctlq | % r a x ← 符 号 扩 展 ( % e a x ) \%rax \leftarrow 符号扩展(\%eax) %rax←符号扩展(%eax) | 把 %eax 符号扩展到 %rax |
指令 | 效果 | 描述 |
---|---|---|
l e a q S , D leaq \quad S, D leaqS,D | D ← & S D \leftarrow \&S D←&S | 加载有效地址 |
I N C D INC \quad D INCD | D ← D + 1 D \leftarrow D + 1 D←D+1 | 加 1 |
D E C D DEC \quad D DECD | D ← D − 1 D \leftarrow D - 1 D←D−1 | 减 1 |
N E G D NEG \quad D NEGD | D ← − D D \leftarrow -D D←−D | 取负 |
N O T D NOT \quad D NOTD | D ← ∼ D D \leftarrow \sim D D←∼D | 取反 |
A D D S , D ADD \quad S, D ADDS,D | D ← D + S D \leftarrow D + S D←D+S | 加 |
S U B S , D SUB \quad S, D SUBS,D | D ← D − S D \leftarrow D - S D←D−S | 减 |
I M U L S , D IMUL \quad S, D IMULS,D | D ← D ∗ S D \leftarrow D * S D←D∗S | 乘 |
X O R S , D XOR \quad S, D XORS,D | D ← D D \leftarrow D D←D ^ S S S | 异或 |
O R S , D OR \quad S, D ORS,D | D ← D ∣ S D \leftarrow D \mid S D←D∣S | 或 |
A N D S , D AND \quad S, D ANDS,D | D ← D & S D \leftarrow D \& S D←D&S | 与 |
S A L k , D SAL \quad k, D SALk,D | D ← D < < k D \leftarrow D << k D←D<<k | 左移 |
S H L k , D SHL \quad k, D SHLk,D | D ← D < < k D \leftarrow D << k D←D<<k | 左移, 等同于 SAL |
S A R k , D SAR \quad k, D SARk,D | D ← D > > A k D \leftarrow D >>_A k D←D>>Ak | 算术左移(考虑符号) |
S H R k , D SHR \quad k, D SHRk,D | D ← D > > L k D \leftarrow D >>_L k D←D>>Lk | 逻辑 |
支持两个 64 位数字的全 128(8字, oct word) 位乘积以及整数除法的指令, 可以看到除法是分步对高低64位操作的
指令 | 效果 | 描述 |
---|---|---|
i m u l q S imulq \quad S imulqS | R [ % r d x ] : R [ % r a x ] ← S × R [ % r a x ] R[\%rdx]:R[\%rax] \leftarrow S \times R[\%rax] R[%rdx]:R[%rax]←S×R[%rax] | 有符号全乘法 |
m u l q S mulq \quad S mulqS | R [ % r d x ] : R [ % r a x ] ← S × R [ % r a x ] R[\%rdx]:R[\%rax] \leftarrow S \times R[\%rax] R[%rdx]:R[%rax]←S×R[%rax] | 无符号全乘法 |
c l t o S clto \quad S cltoS | R [ % r d x ] : R [ % r a x ] ← 符 号 扩 展 R [ % r a x ] R[\%rdx]:R[\%rax] \leftarrow 符号扩展R[\%rax] R[%rdx]:R[%rax]←符号扩展R[%rax] | 转换为8字 |
i d i v q S idivq \quad S idivqS | R [ % r d x ] ← R [ % r d x ] : R [ % r a x ] m o d S R [ % r d x ] ← R [ % r d x ] : R [ % r a x ] ÷ S R[\%rdx] \leftarrow R[\%rdx]:R[\%rax] mod S \\ R[\%rdx] \leftarrow R[\%rdx]:R[\%rax] \div S R[%rdx]←R[%rdx]:R[%rax]modSR[%rdx]←R[%rdx]:R[%rax]÷S | 有符号除法法 |
d i v q S divq \quad S divqS | R [ % r d x ] ← R [ % r d x ] : R [ % r a x ] m o d S R [ % r d x ] ← R [ % r d x ] : R [ % r a x ] ÷ S R[\%rdx] \leftarrow R[\%rdx]:R[\%rax] mod S \\ R[\%rdx] \leftarrow R[\%rdx]:R[\%rax] \div S R[%rdx]←R[%rdx]:R[%rax]modSR[%rdx]←R[%rdx]:R[%rax]÷S | 无符号除法法 |
条件码会发生改变的操作
这两个系列指令不修改任何寄存器的值,只设置条件码
指令 | 效果 | 描述 |
---|---|---|
C M P S 1 , S 2 CMP \quad S_1, S_2 CMPS1,S2 | S 2 − S 1 S_2-S_1 S2−S1 | 比较 |
T E S T S 1 , S 2 TEST \quad S_1, S_2 TESTS1,S2 | S 1 & S 2 S_1 \& S_2 S1&S2 | 测试 |
指令 | 同义名 | 效果 | 描述 |
---|---|---|---|
s e t e D sete \quad D seteD | s e t z setz setz | D ← Z F D \leftarrow ZF D←ZF | 相等/零 |
s e t n e D setne \quad D setneD | s e t n z setnz setnz | D ← ∼ Z F D \leftarrow \sim ZF D←∼ZF | 不等/非零 |
s e t s D sets \quad D setsD | D ← S F D \leftarrow SF D←SF | 负数 | |
s e t n s D setns \quad D setnsD | D ← ∼ S F D \leftarrow \sim SF D←∼SF | 负数 | |
s e t g D setg \quad D setgD | s e t n l e setnle setnle | D ← ∼ ( S F ∧ O F ) & ∼ Z F D \leftarrow \sim(SF \land OF) \& \sim ZF D←∼(SF∧OF)&∼ZF | 大于(有符号>) |
s e t g e D setge \quad D setgeD | s e t n l setnl setnl | D ← ∼ ( S F ∧ O F ) D \leftarrow \sim(SF \land OF) D←∼(SF∧OF) | 大于等于(有符号 >=) |
s e t l D setl \quad D setlD | s e t n g e setnge setnge | D ← S F ∧ O F D \leftarrow SF \land OF D←SF∧OF | 小于(有符号<) |
s e t l e D setle \quad D setleD | s e t n g setng setng | D ← ∼ ( S F ∧ O F ) ∣ Z F D \leftarrow \sim(SF \land OF) \mid ZF D←∼(SF∧OF)∣ZF | 小于等于(有符号 <=) |
s e t a D seta \quad D setaD | s e t n b e setnbe setnbe | D ← ∼ C F & ∼ Z F D \leftarrow \sim CF \& \sim ZF D←∼CF&∼ZF | 大于(无符号>) |
s e t a e D setae \quad D setaeD | s e t n b setnb setnb | D ← ∼ C F D \leftarrow \sim CF D←∼CF | 大于等于(无符号>=) |
s e t b D setb \quad D setbD | s e t n a e setnae setnae | D ← C F D \leftarrow CF D←CF | 小于(无符号<) |
s e t b e D setbe \quad D setbeD | s e t n a setna setna | D ← C F ∣ Z F D \leftarrow CF \mid ZF D←CF∣ZF | 小于等于(无符号<=) |
访问条件码
指令 | 同义名 | 跳转条件 | 描述 |
---|---|---|---|
j m p L a b e l jmp \quad Label jmpLabel | 1 | 直接跳转 | |
j m p ∗ O p e r a n d jmp \quad *Operand jmp∗Operand | 1 | 间接跳转 | |
j e L a b e l je \quad Label jeLabel | j z jz jz | Z F ZF ZF | 相等/零 |
j n e L a b e l jne \quad Label jneLabel | j n z jnz jnz | ∼ Z F \sim ZF ∼ZF | 不相等/非零 |
j s L a b e l js \quad Label jsLabel | S F SF SF | 负数 | |
j n s L a b e l jns \quad Label jnsLabel | ∼ S F \sim SF ∼SF | 非负数 | |
j g D jg \quad D jgD | j n l e jnle jnle | D ← ∼ ( S F ∧ O F ) & ∼ Z F D \leftarrow \sim(SF \land OF) \& \sim ZF D←∼(SF∧OF)&∼ZF | 大于(有符号>) |
j g e D jge \quad D jgeD | j n l jnl jnl | D ← ∼ ( S F ∧ O F ) D \leftarrow \sim(SF \land OF) D←∼(SF∧OF) | 大于等于(有符号 >=) |
j l D jl \quad D jlD | j n g e jnge jnge | D ← S F ∧ O F D \leftarrow SF \land OF D←SF∧OF | 小于(有符号<) |
j l e D jle \quad D jleD | j n g jng jng | D ← ∼ ( S F ∧ O F ) ∣ Z F D \leftarrow \sim(SF \land OF) \mid ZF D←∼(SF∧OF)∣ZF | 小于等于(有符号 <=) |
j a D ja \quad D jaD | j n b e jnbe jnbe | D ← ∼ C F & ∼ Z F D \leftarrow \sim CF \& \sim ZF D←∼CF&∼ZF | 大于(无符号>) |
j a e D jae \quad D jaeD | j n b jnb jnb | D ← ∼ C F D \leftarrow \sim CF D←∼CF | 大于等于(无符号>=) |
j b D jb \quad D jbD | j n a e jnae jnae | D ← C F D \leftarrow CF D←CF | 小于(无符号<) |
j b e D jbe \quad D jbeD | j n a jna jna | D ← C F ∣ Z F D \leftarrow CF \mid ZF D←CF∣ZF | 小于等于(无符号<=) |
跳转指令一般将目标指令的地址与紧跟在跳转指令后面那条指令之间的差作为编码
有下C代码
int foo() {
for (int i = 0; i < 3; i++)
if (i == 1)
return 1;
return 0;
}
反汇编二进制代码
think@pc$ gcc -O0 -c foo.c
think@pc$ objdump -S foo.o
foo.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <foo>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
b: eb 11 jmp 1e <foo+0x1e>
d: 83 7d fc 01 cmpl $0x1,-0x4(%rbp)
11: 75 07 jne 1a <foo+0x1a>
13: b8 01 00 00 00 mov $0x1,%eax
18: eb 0f jmp 29 <foo+0x29>
1a: 83 45 fc 01 addl $0x1,-0x4(%rbp)
1e: 83 7d fc 02 cmpl $0x2,-0x4(%rbp)
22: 7e e9 jle d <foo+0xd>
24: b8 00 00 00 00 mov $0x0,%eax
29: 5d pop %rbp
2a: c3 retq
看第一个跳转指令在地址 b
, 跳转的地址为 1e
, 其值为 11 + d
, 这里比较特殊的是
看内存地址为 0x22
的那条 jle
指令, 其跳转地址为 d = 0x24(unsigned) + 0xe9(-0x17,signed)
, 或者用两个地址相加取未溢出的部分。与 PC计数器 指向下一条执行的指令的现象相符合,这样就可以比较轻易的完成链接操作。
对于我们一般的认识就是过程可以理解为函数调用。
过程的机器级支持需要处理多种属性
指令 | 效果 | 描述 |
---|---|---|
p u s h q S pushq \quad S pushqS | R [ % r s p ] ← R [ % r s p ] − 8 M [ R [ % r s p ] ] ← S R[\%rsp] \leftarrow R[\%rsp] - 8 \\ M[R[\%rsp]] \leftarrow S R[%rsp]←R[%rsp]−8M[R[%rsp]]←S | 四字入栈 |
p o p q D popq \quad D popqD | D ← M [ R [ % r s p ] ] R [ % r s p ] ← R [ % r s p ] + 8 D \leftarrow M[R[\%rsp]] \\ R[\%rsp] \leftarrow R[\%rsp] + 8 D←M[R[%rsp]]R[%rsp]←R[%rsp]+8 | 四字出栈 |
一个简单的示例代码
// c 代码
long three_n_sum(long a1, long a2, long a3) { return a1 + a2 + a3; }
long sum(long a1, long a2, long a3, long a4, long a5, long a6, long a7, long a8) {
long b1 = three_n_sum(a1, a2, a3);
long b2 = three_n_sum(a4, a5, a6);
long b3 = three_n_sum(a7, a8, 0);
long b = b1 + b2 + b3;
return b;
}
int main() { long s = sum(1, 2, 3, 4, 5, 6, 7, 8); }
// 反汇编的二进制代码
0000000000001125 <three_n_sum>:
1125: 55 push %rbp
1126: 48 89 e5 mov %rsp,%rbp
1129: 48 89 7d f8 mov %rdi,-0x8(%rbp)
112d: 48 89 75 f0 mov %rsi,-0x10(%rbp)
1131: 48 89 55 e8 mov %rdx,-0x18(%rbp)
1135: 48 8b 55 f8 mov -0x8(%rbp),%rdx
1139: 48 8b 45 f0 mov -0x10(%rbp),%rax
113d: 48 01 c2 add %rax,%rdx
1140: 48 8b 45 e8 mov -0x18(%rbp),%rax
1144: 48 01 d0 add %rdx,%rax
1147: 5d pop %rbp
1148: c3 retq
0000000000001149 <sum>:
1149: 55 push %rbp
114a: 48 89 e5 mov %rsp,%rbp
114d: 48 83 ec 50 sub $0x50,%rsp
1151: 48 89 7d d8 mov %rdi,-0x28(%rbp)
1155: 48 89 75 d0 mov %rsi,-0x30(%rbp)
1159: 48 89 55 c8 mov %rdx,-0x38(%rbp)
115d: 48 89 4d c0 mov %rcx,-0x40(%rbp)
1161: 4c 89 45 b8 mov %r8,-0x48(%rbp)
1165: 4c 89 4d b0 mov %r9,-0x50(%rbp)
1169: 48 8b 55 c8 mov -0x38(%rbp),%rdx
116d: 48 8b 4d d0 mov -0x30(%rbp),%rcx
1171: 48 8b 45 d8 mov -0x28(%rbp),%rax
1175: 48 89 ce mov %rcx,%rsi
1178: 48 89 c7 mov %rax,%rdi
117b: e8 a5 ff ff ff callq 1125 <three_n_sum>
1180: 48 89 45 f8 mov %rax,-0x8(%rbp)
1184: 48 8b 55 b0 mov -0x50(%rbp),%rdx
1188: 48 8b 4d b8 mov -0x48(%rbp),%rcx
118c: 48 8b 45 c0 mov -0x40(%rbp),%rax
1190: 48 89 ce mov %rcx,%rsi
1193: 48 89 c7 mov %rax,%rdi
1196: e8 8a ff ff ff callq 1125 <three_n_sum>
119b: 48 89 45 f0 mov %rax,-0x10(%rbp)
119f: 48 8b 45 18 mov 0x18(%rbp),%rax
11a3: ba 00 00 00 00 mov $0x0,%edx
11a8: 48 89 c6 mov %rax,%rsi
11ab: 48 8b 7d 10 mov 0x10(%rbp),%rdi
11af: e8 71 ff ff ff callq 1125 <three_n_sum>
11b4: 48 89 45 e8 mov %rax,-0x18(%rbp)
11b8: 48 8b 55 f8 mov -0x8(%rbp),%rdx
11bc: 48 8b 45 f0 mov -0x10(%rbp),%rax
11c0: 48 01 c2 add %rax,%rdx
11c3: 48 8b 45 e8 mov -0x18(%rbp),%rax
11c7: 48 01 d0 add %rdx,%rax
11ca: 48 89 45 e0 mov %rax,-0x20(%rbp)
11ce: 48 8b 45 e0 mov -0x20(%rbp),%rax
11d2: c9 leaveq
11d3: c3 retq
00000000000011d4 <main>:
11d4: 55 push %rbp
11d5: 48 89 e5 mov %rsp,%rbp
11d8: 48 83 ec 10 sub $0x10,%rsp
11dc: 6a 08 pushq $0x8
11de: 6a 07 pushq $0x7
11e0: 41 b9 06 00 00 00 mov $0x6,%r9d
11e6: 41 b8 05 00 00 00 mov $0x5,%r8d
11ec: b9 04 00 00 00 mov $0x4,%ecx
11f1: ba 03 00 00 00 mov $0x3,%edx
11f6: be 02 00 00 00 mov $0x2,%esi
11fb: bf 01 00 00 00 mov $0x1,%edi
1200: e8 44 ff ff ff callq 1149 <sum>
1205: 48 83 c4 10 add $0x10,%rsp
1209: 48 89 45 f8 mov %rax,-0x8(%rbp)
120d: b8 00 00 00 00 mov $0x0,%eax
1212: c9 leaveq
1213: c3 retq
1214: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
121b: 00 00 00
121e: 66 90 xchg %ax,%ax
将控制从函数 P P P转移到 Q Q Q只需要简单的把 P C PC PC设置为 Q Q Q的代码起始位置。
程序的返回处理器需要记录 P P P的执行的代码位置,这个信息由指令 c a l l   Q call \, Q callQ来记录
call
指令将地址 A 压入栈中,并将 P C PC PC设置为 Q Q Q的起始地址,压入的地址 A 是紧跟在 call 指令后面的那条指令的地址
,被称为返回地址。一个函数调用栈的变化,用函数 sum()
做示例
leaveq
等价于 movl %ebp %esp
popl %ebp
; ret
弹出返回地址并且跳转用一张图来表示栈的变化, 观察汇编代码地址 119f
和 11ab
, 在第三次调用 three_n_sum()
时参数的取值时存在于 main 函数栈帧中,而参数都存在于栈顶位置也利于子例程取值。
上图是CS:APP中的图,我用processon画的,折腾后面上图2中的栈结构真的是麻烦,书里的图文不符,只能根据这个汇编代码重画一下。
后面又在 15213 的课件中找到一张图,在我自己的图上栈帧的大小表示上又和课件的图有出入,拿 sum()
来看,%ebp 入栈后,栈指针继续分配栈上内存,向地址减小 0x50,课件中的栈帧包含了那个 %ebp,而我自己做的图单独列出了,这样看应该是课件中的图准确点,一个栈帧应该包含当前栈的所有信息(栈上内存 + 栈基地址 + 返回地址),不过processon上的图么有了,这里就插个眼再传个课件的图吧。
)