内存查看与修改——物理内存、虚拟内存和格式后缀详解
x vs xp:物理 vs 虚拟
| 命令 | 含义 | 地址类型 | 翻译方式 |
|---|---|---|---|
xp | eXamine Physical | 物理地址 | 直接访问,无翻译 |
x | eXamine(虚拟) | 虚拟地址 | 通过当前 CR3 页表翻译 |
OS 开发优先用 xp——内核启动早期分页没开,虚拟地址 = 物理地址;分页开启后,xp 依然能绕过页表直接看物理内存,调试页表错误时反而更有用。
格式后缀控制显示方式
基本语法:x /<count><format><size> <address>
格式字符
| 字符 | 含义 |
|---|---|
x | 十六进制(默认) |
d | 十进制(带符号) |
u | 十进制(无符号) |
o | 八进制 |
c | ASCII 字符(不可打印显示 .) |
s | 字符串(以 null 结尾) |
i | 反汇编指令(和 u 命令效果相同) |
大小字符
| 字符 | 字节数 | C 类型 |
|---|---|---|
b | 1 | unsigned char |
h | 2 | short |
w | 2(注意:在 Bochs 里 w = 2 字节) | word |
g | 4 | dword |
l | 4 | long |
q | 8 | quad |
注意:Bochs 的大小字符和 GDB 略有不同。b、w、l 分别是 1/2/4 字节,q 是 8 字节。别混用了。
常用示例
查看 Bootloader 引导扇区(物理)
bochs:1> xp /512bx 0x7c00 # 0x7c00 开始,512 字节十六进制
Bootloader 引导扇区的最后两字节(偏移 0x1FE, 0x1FF)必须是 0x55 0xAA——BIOS 才认它是可引导扇区。在输出的最后两行检查:
...
0x00007dfe: 55 aa ...
如果不是 0x55AA,你的 Bootloader 不会被 BIOS 加载。
查看 IDT(中断描述符表)
bochs:1> info sreg # 先看 IDTR 的值
idtr=000000007c253c00 # 这是 IDT 的物理基址 + 段限
bochs:2> xp /16gx 0x7c253c00 # 看 IDT 的前 16 个条目(每个 8 字节)
查看虚拟地址对应的物理内存
bochs:1> vb 0xc0100000 # 给内核入口下虚拟断点
bochs:2> c # 运行到断点
bochs:3> x /8lx 0xc0100000 # 用 x 看虚拟地址(内核代码)
bochs:4> xp /8lx <物理地址> # 用 xp 看翻译后的物理地址
写内存(wx 和 w)
bochs:1> wx 0x7c00 0xEB # 物理地址 0x7c00 写入 0xEB(jmp short 指令)
bochs:2> wx 0x7c01 0xFE # 下一字节:跳转 -2 偏移(0xFE = -2 的补码)
bochs:3> w 0xc0100000 0x48 # 虚拟地址写入(内核会通过 CR3 翻译)
写入的立即数可以是 1/2/4/8 字节——Bochs 根据值的范围自动决定大小,或者加后缀强制:
bochs:1> wx /4bx 0x7c00 0x00 0x00 0x00 0x00 # 写 4 个 0
搜索内存
Bochs 调试器没有内置搜索命令,但可以用 format 后缀组合变通——把内存当字符看:
bochs:1> xp /128cx 0x7c00 # 以字符方式看 128 字节
# 输出里 ASCII 字符会直接显示,方便找字符串常量
GDB 有 find/grep 命令,Bochs 没有。对于 OS 开发者来说,通常的做法是手动 dump 感兴趣的内存范围、用 HxD 或 objdump 在宿主上搜索。
虚拟地址翻译失败怎么办
用 x 命令时,如果 CR3 指向的页表里没有该虚拟地址的映射,Bochs 会提示:
bochs:1> x 0xdeadbeef
Cannot translate virtual address 0xdeadbeef to physical: page not present
常见原因:
- 内核还没初始化这个地址的页表(正确,继续调)
- CR3 写错了(内核 bug,需要调 CR3 赋值代码)
- 这个地址真的不存在(用 xp 绕过,直接看物理内存)
页表查看进阶:walk page table
Bochs 调试器有一个隐藏的"walk page table"能力——你可以手动跟踪 CR3 → PML4 → PDPT → PD → PT → 物理页的完整路径。
32 位 PAE 模式下:
bochs:1> info cr
cr3=0x12345000 # PML4 基址(低 20 位是物理页号)
bochs:2> xp /4bx 0x12345000 + (虚拟地址>>39 & 0x1ff)*8
# 依次跟踪每一级表...
这很繁琐,但在调试内核页表初始化 bug 时是必要的。比如"用户进程访问一个地址触发 page fault,但你确信应该有映射"——就得手动 walk page table 看哪一级表项写错了。
内存检查清单(OS 开发场景)
| 检查目标 | 命令 | 说明 |
|---|---|---|
| Bootloader 入口 | xp /512bx 0x7c00 | 最后两字节 0x55AA |
| 中断向量表(实模式) | xp /256gx 0x0 | 实模式 IVT 在物理 0x0 |
| IDT(保护模式) | 先用 info sreg 找 IDTR,再 xp 看基址 | |
| GDT | 同上,用 info sreg 找 GDTR | |
| 内核入口 | 内核编译地址 → x 看虚拟,xp 看物理 | |
| 栈 | info registers rsp 找栈顶,xp 看栈内容 | |
| VGA VRAM | xp /256bx 0xa0000 | 显存起始地址 |
| BIOS 区域 | xp /256bx 0xfffc0000 | ROM BIOS |