skip to content

Search

CSAPP 3nd - 汇编与控制

9 min read Updated:

这篇文章介绍了CSAPP第三章的汇编与控制内容,包括寄存器类型、条件码、条件分支、循环、switch语句等。

汇编

寄存器类型

  • 64位为r
  • 32位为e
汇编术语位数字节数示例寄存器
byte81%al, %bl, %r8b
word162%ax, %bx, %r8w
long324%eax, %ebx, %r8d
quad648%rax, %rbx, %r8

Control

Condition codes

  • 条件码
  • cmpq
  • setX 基于条件码的值将单个寄存器的单个字节设置为0或1(取对应条件码低位的数字作为目标寄存器的低位,然后将剩余7位置为0)
  • movzbl x86-64机器上,结果为32位的数字会自动将高位的32位扩展为0,但是对于低于4字节的结果就不会进行扩展,所以会有movzbl指令来对单字节(两个字节为movzwl)的数字进行扩展到四个字节(z代表zero,b代表byte,l代表long)

Conditional branches

jX指令

指令条件描述对应 setX标志条件用于比较类型
je相等(equal)seteZF = 1有符号 / 无符号
jne不相等(not equal)setneZF = 0有符号 / 无符号
jg大于(signed)setgZF = 0 且 SF = OF有符号
jge大于等于(signed)setgeSF = OF有符号
jl小于(signed)setlSF ≠ OF有符号
jle小于等于(signed)setleZF = 1 或 SF ≠ OF有符号
ja高于(unsigned)setaCF = 0 且 ZF = 0无符号
jae高于等于(unsigned)setaeCF = 0无符号
jb低于(unsigned)setbCF = 1无符号
jbe低于等于(unsigned)setbeCF = 1 或 ZF = 1无符号
js负数(负号)setsSF = 1通用
jns非负setnsSF = 0通用
jo溢出(overflow)setoOF = 1通用
jno无溢出setnoOF = 0通用

生成跳转汇编语言的gcc指令(这个指令的意思是编译成汇编语言,-Og表示优化级别为0,-S表示只编译成汇编语言,不链接,-fno-if-conversion表示不使用条件移动指令)。

gcc -Og -S -fno-if-conversion control.c

📘 函数:计算两个 long 整数的差的绝对值

C 语言代码:

long absdiff(long x, long y) {
    long result;
    if (x > y)
        result = x - y;
    else
        result = y - x;
    return result;
}

汇编代码(AT&T 语法):

absdiff:
    cmpq    %rsi, %rdi        # 比较 x : y (x > y?)
    jle     .L4               # 如果 x <= y,跳到 .L4
    movq    %rdi, %rax        # x > y: 把 y 放入 rax
    subq    %rsi, %rax        # rax = y - x
    ret
 
.L4:                          # x <= y 分支
    movq    %rsi, %rax        # 把 x 放入 rax
    subq    %rdi, %rax        # rax = x - y
    ret

如果使用了条件移动指令,那么上面的的代码就会变成:

absdiff:
    movq   %rdi, %rax        # 把 x 放入 rax
    subq   %rsi, %rax        # rax = x - y 
    movq   %rsi, %rdx        # 把 y 放入 rdx
    subq   %rdi, %rdx        # rdx = y - x
    cmopq  %rsi, %rdi        # 比较 x : y (x > y?)
    cmovle %rdx, %rax        # 如果 x <= y,rax = x - y

条件移动会提前计算出所有的结果,然后根据条件码来决定使用哪个结果。

  • 对于简单地分支,使用条件移动指令会更快,但是对于复杂的分支,使用条件移动指令会更慢。
  • 对于空指针这种情况,不能使用条件移动,会导致程序异常。
      val = p ? *p:0; // 这里的条件移动会导致空指针异常
    
  • 对于前一个分支带有副作用的情况,也不能使用条件指令
      var = x ? x+=3:x*=7; 
    

Loops

在汇编层级,do-while循环是另外两种循环的基础结构,while和for循环都可以用do-while循环来实现。

do_while:
    .L2:
        # 循环体
        # ...
        # 判断条件
        cmpq    $0, %rdi       # 比较条件
        jne     .L2            # 如果条件不为0,跳到 .L2
while:
    .L3:
        # 判断条件
        cmpq    $0, %rdi       # 比较条件
        je      .L4            # 如果条件为0,跳到 .L4
        # 循环体
        # ...
        jmp     .L3            # 跳到 .L3
.L4:
for:
    # 初始化
    movq    $0, %rdi         # 初始化条件
    .L5:
        # 判断条件
        cmpq    $10, %rdi      # 比较条件
        jge     .L6            # 如果条件大于等于10,跳到 .L6
        # 循环体
        # ...
        incq    %rdi           # 条件自增1
        jmp     .L5            # 跳到 .L5
.L6:

Switch Statements

✅ switch 中 case 常量的规则总结:

要求说明
必须是常量表达式编译时能确定的整数,如 1, 3+4, #define A 5
不能重复每个 case 值必须唯一
不要求递增或连续case 可以无序、稀疏,顺序无关
类型必须是整型支持 int, char, short, enum 等,不支持 float、指针

🔁 switch 的跳转表原理

  • 跳转表(jump table)是一种将 case 常量值映射为 代码地址 的数组。
  • 编译器使用 jmp *table(x) 实现 O(1) 多分支跳转。
  • 只有当 case 值 连续且数量较多 时才会启用。

汇编结构概览:

cmp $max, %x
ja .Ldefault
jmp *.Ltable(,%x,8)
 
.Ltable:
    .quad .Lcase0
    .quad .Lcase1
    ...

📘 指令含义(switch 跳转表相关)

指令作用含义解释
ja label条件跳转(无符号)如果前面 cmp 中无符号大于成立,跳转到 label
jmp *%rdi间接跳转跳转到 %rdi 寄存器中存放的地址执行
jmp *.Ljump_table(,%rdi,8)间接跳转跳转到跳转表中第 %rdi 项所存放的地址执行

🧠 GDB x 命令(examine memory)用法速查表

GDB 的 x 命令用于查看任意内存地址的内容,支持以多种格式和单位显示,是调试汇编、指针、栈内容的重要工具。

🧾 基本语法

x/[个数] [格式] [单位] 地址

  • 个数:显示几个单元(默认1)
  • 格式:显示格式(hex、dec、string等)
  • 单位:每个单元的大小(byte、word等)

📌 常用格式符号

格式符含义示例
x十六进制(hex)x/4x $rsp
d十进制(signed)x/4d addr
u十进制(unsigned)x/4u addr
t二进制x/4t addr
f浮点数x/4f addr
s字符串(C 风格)x/s $rsi
i汇编指令(反汇编)x/i $rip

📌 常用单位符号

单位符单元大小说明
b1 字节byte
h2 字节halfword(少用)
w4 字节word(32 位)
g8 字节giant word(64 位)

🔍 常用命令示例

命令含义
x/x 0x601040查看地址 0x601040 的 1 个 word(4B)
x/4x $rsp查看 $rsp 开始的 4 个 word(共16字节)
x/gx $rbp查看 $rbp 指向的 1 个 giant word(64 位)
x/8gx $rsp连续查看 8 个 64 位值(常用于查看栈)
x/4xb $rax查看 $rax 指向地址的 4 个字节(逐字节显示)
x/s 0x6037d0查看地址处的 C 字符串
x/i $rip查看当前指令(反汇编)

🎯 实用技巧

  • 使用 $ 可引用寄存器名:如 $rsp$rbp$rax
  • 小端序:x86_64 架构下内存按小端序存储
  • 可结合 /b 查看字节级别数据结构(如结构体、栈帧等)

🧰 推荐组合操作

x/gx $rsp         # 栈顶64位
x/8x $rsp         # 栈上连续4字(16B)
x/4xb $rax        # 查看寄存器指向地址的4字节(字节级)
x/s $rsi          # 打印字符串指针的内容
x/i $rip          # 查看当前执行指令