地址断点——物理断点、虚拟断点和 Bootloader 入口下断
三种断点命令
| 命令 | 含义 | 断点类型 | 触发时机 |
|---|---|---|---|
b <addr> | 默认断点 | 自动检测 | 看 addr 值选物理或虚拟 |
pb <addr> | Physical Breakpoint | 物理地址 | 任何时刻 CPU 取指到该物理地址 |
vb <addr> | Virtual Breakpoint | 虚拟地址 | CPU 取指到该虚拟地址(由当前 CR3 翻译) |
b 命令的自动检测逻辑
当你写 b 0x7c00 时,Bochs 会:
- 检查当前 CPU 模式
- 如果是实模式(CS 基址 + IP):把 addr 当作物理地址
- 如果是保护模式且开启分页(CR0.PG=1):把 addr 当作虚拟地址
- 不确定时提示你用 pb 或 vb 显式指定
OS 开发场景(特别是启动早期),显式用 pb 更安全——因为此时分页还没开(CR0.PG=0),虚拟地址就是物理地址,没有歧义。等内核启动了分页再用 vb。
经典场景:Bootloader 入口断点
Bochs OS 开发实验的最经典第一条命令:
bochs:1> pb 0x7c00
bochs:2> c
为什么是 0x7c00?
真实 PC 上,BIOS 上电后做 POST、枚举硬件、找引导设备。找到可引导设备后,会读取设备的首扇区(引导扇区,512 字节)到物理地址 0x7c00,然后设置 CS=0x0000、IP=0x7c00,跳转到这里执行。
这就是你的 Bootloader 的加载位置。在 0x7c00 下物理断点,BIOS 刚跳过来调试器就停住——你可以看到 Bootloader 的第一条指令。
示例调试器输出:
bochs:1> pb 0x7c00
bochs:2> c
(0) Breakpoint 1, 0x0000000000007c00 in ???
Next at t=12345
(0) [0x0000000000007c00] 0000:7c00: (jmp 0x7c18)
bochs:3> u
0000000000007c00: jmp .+0x16 (0x7c18)
0000000000007c02: ...
bochs:4> info registers
...
cs=0x0000 ds=0x0000 es=0x0000 ss=0x0000
eip=0x000000007c00
...
虚拟地址断点:分页开启后
内核初始化分页后,虚拟地址 ≠ 物理地址。此时用 vb 下断点——内核代码编译时通常有固定的虚拟地址(如 0xc0100000 或 0xffffffff80100000):
bochs:1> vb 0xc0100000
bochs:2> c
调试器会在 CR3 翻译后的物理地址上设断点。如果 CR3 换了(进程切换),断点目标也会跟着变——这正是你想要的:无论内核页表怎么换,只要虚拟地址 0xc0100000 被取指就停住。
vb 的陷阱
vb 依赖当前 CR3 指向的页表。如果你在内核早期(CR3 还没初始化或正在切换 CR3)就设 vb,断点可能不会触发。此时用 pb + 手动算物理地址更可靠。
物理地址计算公式(内核线性映射场景):
物理地址 = 虚拟地址 - 内核偏移
# 比如内核在 0xc0000000 之上有 3GB 偏移
# 虚拟 0xc0100000 → 物理 0x00100000
算出物理地址后用 pb 下断点就不受页表变化影响。
单步断点:执行一次自动删除
sb(single breakpoint)设置一个一次性断点——触发后自动删除:
bochs:1> sb 0x7c10 # 下次跑到 0x7c10 就停,之后不影响
bochs:2> c
适合"我只想在某处停一次看看"的场景,用完自动清理。
断点管理
bochs:1> info break # 列出所有断点
Num Type Disp Enb Address
1 breakpoint keep y 0x0000000000007c00 in ???
bochs:2> d 1 # 删除 1 号断点
bochs:3> lb # 删除所有断点(clear all breakpoints)
Disp 列是"Disposition"——keep 表示每次触发后保留,del 表示触发一次后删除(就是 sb 的行为)。
条件断点(Bochs 支持有限)
Bochs 调试器不支持 GDB 那种复杂的条件断点(如 break 0x7c00 if eax==0)。但有一个变通方法:
在断点触发后立即检查寄存器:
bochs:1> pb 0x7c00
bochs:2> c
# 断点触发,检查 EAX
bochs:3> info registers eax
eax=00000000
# 如果不是期望的值,继续
bochs:4> c
Bochs 3.0+ 版本引入了少量条件支持(b addr if <condition>),但功能远不如 GDB 完整。OS 开发者通常的做法是用 Bochs 下断点、快速查看、继续跑——不追求自动化条件。
内存断点(watchpoint)
Bochs 还支持内存访问断点(watchpoint):
bochs:1> watch read 0x7c00 # 有人读 0x7c00 时停住
bochs:2> watch write 0x1000 # 有人写 0x1000 时停住
bochs:3> watch access 0x2000 # 任何访问 0x2000 时停住
对于调试"某个变量被谁改了"这类问题特别有用——内核的页表项被篡改、设备驱动的状态寄存器被意外重置等场景。
info watch 列出所有 watchpoint,unwatch <num> 删除。