反汇编当前指令——实时看 CPU 正在跑什么机器码
u 命令基本用法
bochs:1> u # 反汇编当前 EIP 指向的一条指令
bochs:2> u 0x7c00 # 反汇编指定地址的一条
bochs:3> u 0x7c00 16 # 从指定地址开始反汇编 16 条指令
输出示例(Bootloader 入口):
bochs:1> u 0x7c00
0000000000007c00: jmp .+0x16 (0x7c18)
0000000000007c02: xor ax, ax
0000000000007c04: mov ds, ax
0000000000007c06: mov es, ax
0000000000007c08: mov ss, ax
0000000000007c0a: mov sp, 0x7c00
...
每行格式:<物理地址>: <mnemonic> <operands>
反汇编器能力范围
Bochs 内置的反汇编器覆盖:
- x86 基础指令集(386 起):全部
- MMX / SSE / SSE2 / SSE3 / SSSE3 / SSE4.x / AVX / AVX2:支持(取决于编译时开关和 CPU model)
- x86-64(AMD64/EM64T):完整支持,包括 REX 前缀
- 特权指令(HLT、UD2、WRMSR/ rdmsr、CLI/STI 等):全部
- SYSCALL/SYSRET / SYSENTER/SYSEXIT:支持
- VMX:需要编译时
--enable-vmx
限制:
- 不支持 AVX-512 部分指令(某些太新的编码)
- 不支持 Intel AMX(还在持续开发中)
- 32 位模式下不会错误地尝试解码 64 位指令
32 位 vs 64 位反汇编
Bochs 自动根据当前 CPU 模式(实模式/保护模式/长模式)选择反汇编的指令长度和语义:
实模式(16 位代码段)
bochs:1> u
0000000000007c00: jmp .+0x16 (0x7c18) ← 相对跳转,16 位操作数
0000000000007c02: mov sp, 0x7c00 ← 16 位 sp
保护模式(32 位代码段)
bochs:1> u
00000000c0100000: mov eax, 0x12345678 ← 32 位操作数
00000000c0100005: call 0xc0100100 ← 32 位跳转
长模式(64 位代码段)
bochs:1> u
0000000000100000: mov rax, 0xdeadbeef ← 64 位操作数,REX.W 前缀
0000000000100007: mov rdi, rax ← REX.R + REX.W 前缀
Bochs 反汇编器会正确处理 REX 前缀——你不会看到单独一行 rex,而是直接合并到反汇编结果里。
和 objdump 的对比
内核编译时 objdump -d 可以反汇编内核二进制文件里的所有函数,GDB 可以在运行时反汇编。Bochs 的 u 命令的独特价值是:
- 能看到运行时的指令——objdump 看到的是编译出来的静态指令;Bochs 看到的是CPU 当前正在执行的那条(包括内核自己在运行时写入的动态代码)
- 不依赖内核源码——甚至不依赖内核——Bootloader 刚加载完、内核还没启动时就能反汇编
- 不依赖 ELF/DWARF——纯粹看机器码,对任何裸机代码都能反汇编
OS 开发的典型工作流:
# 1. 宿主上反汇编内核二进制
objdump -d kernel.bin > kernel.asm
# 2. Bochs 里下断点到内核入口
bochs:1> vb 0xc0100000
bochs:2> c
# 3. 断点触发后,Bochs 里反汇编 + 宿主 objdump 交叉对照
bochs:3> u 0xc0100000 20
undisasm:每步都反汇编
undisasm on 让 Bochs 每条指令执行前都反汇编并打印:
bochs:1> undisasm on
bochs:2> c
Next at t=12350
(0) [0x0000000000007c02] 0000:7c02: xor ax, ax
Next at t=12351
(0) [0x0000000000007c04] 0000:7c04: mov ds, ax
...
极慢(每条指令都要反汇编+打印),但极其有用——当你想"让 CPU 跑一会儿,我看看它干了什么"时,开启 undisasm 然后让它跑几百条指令(Ctrl+C 随时中断),输出里能看到完整的执行轨迹。
undisasm off 关闭。
trace:更重的追踪
trace on 每执行一条指令都打印寄存器状态+反汇编——比 undisasm 输出更多。bochsrc 里也可以配置 trace 模式自动开启。但对于 OS 开发通常不需要这么重,undisasm 足够了。
手动汇编:db / asm(进阶)
Bochs 调试器有一个隐藏能力——可以在调试器里直接汇编指令并写入内存:
bochs:1> asm 0x7c00
asm> jmp short $
asm> ^D # Ctrl+D 结束输入
# Bochs 汇编你的输入并写入 0x7c00
bochs:2> u 0x7c00
0000000000007c00: jmp 0x7c00
这在调试"我想快速试一条指令看看效果"的场景非常方便——不需要汇编、链接、写入磁盘、重启 Bochs 这一整套流程。
反汇编常见问题
反汇编结果里出现 "invalid"
Bochs 反汇编器无法识别当前位置的指令——可能原因:
- 内存里放的不是代码(是数据被误解码成指令)
- 指令编码用了 Bochs 反汇编器不支持的前缀(某些非常新的 AVX-512 变体)
- 反汇编起始地址对齐问题(从一条指令的中间开始反汇编)
想反汇编更大范围的代码块
bochs:1> u 0x7c00 1000 # 反汇编 1000 条指令(可能几千行输出)
输出会刷屏,建议用 bochsrc 里的 debugger_log 把输出记录到文件,然后在宿主上慢慢看。
debugger_log: bochs-trace.log