跟踪地址映射过程
1.通过命令./dbg-asm
启动调试器,在linux-0.11运行test.c文件,使其进入死循环,我们的任务就是找到i的地址并将其修改为0使test.c程序退出循环。
2.在命令行输入crit+c使Boch暂停,一般会显示下列信息
(0) [0x00fc8031] 000f:00000031 (unk. ctxt): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
其中的 000f 如果是 0008,则说明中断在了内核里。那么就要 c,然后再 ctrl+c,直到变为 000f 为止。
如果显示的下一条指令不是 cmp …(这里指语句以 cmp 开头),就用 n 命令单步运行几步,直到停在 cmp …。
3.使用命令 u /8,显示从当前位置开始 8 条指令的反汇编代码,从反汇编代码可以看到,变量i保存在ds:0x3004地址上。
接下来,开始寻找ds:0x3004对应的物理地址
ds:0x3004为虚拟地址,我们首先找到段表,根据ds的值在段表中找到ds段的具体信息。
段表保存在ldtr寄存器中,我们在调试窗口输入sreg
命令就会得到
<bochs:4> sreg
cs:s=0x000f, dl=0x00000002, dh=0x10c0fa00, valid=1
ds:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=3
ss:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
es:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
fs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
gs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
ldtr:s=0x0068, dl=0xa2d00068, dh=0x000082fa, valid=1
tr:s=0x0060, dl=0xa2e80068, dh=0x00008bfa, valid=1
gdtr:base=0x00005cb8, limit=0x7ff
idtr:base=0x000054b8, limit=0x7ff
可以看到ldtr
的值是0x0068=0000000001101000(二进制
,表示LDT
表存放在GDT
表的1101(二进制)=13(十进制
号位置(每位数据的意义参考后文叙述的段选择子)。
而GDT
的位置已经由gdtr
明确给出,在物理地址的0x00005cb8
。
GDT表中的每一项占 64 位(8 个字节),所以我们要查找的项的地址是0x00005cb8+13*8
。
输入xp /2w 0x00005cb8+13*8
,得到:
<bochs:6> xp /2w 0x00005cb8+13*8
[bochs]:
0x00005d20 <bogus+ 0>: 0xa2d00068 0x000082fa
“0xa2d00068 0x000082fa”将其中的加粗数字组合为“0x00faa2d0”,这就是 LDT 表的物理地址(为什么这么组合,参考后文介绍的段描述符)。
xp /8w 0x00faa2d0,得到:
<bochs:7> xp /8w 0x00faa2d0
[bochs]:
0x00faa2d0 <bogus+ 0>: 0x00000000 0x00000000 0x00000002 0x10c0fa00
0x00faa2e0 <bogus+ 16>: 0x00003fff 0x10c0f300 0x00000000 0x00fab000
这就是LDT表的前四项内容了
ds选择子:保存段在段表中的索引值,用这个索引值可以从段表中选择出相应的段描述符
段选择子的结构为
其中 RPL 是请求特权级,当访问一个段时,处理器要检查 RPL 和 CPL(放在 cs 的位 0 和位 1 中,用来表示当前代码的特权级),即使程序有足够的特权级(CPL)来访问一个段,但如果 RPL(如放在 ds 中,表示请求数据段)的特权级不足,则仍然不能访问,即如果 RPL 的数值大于 CPL(数值越大,权限越小),则用 RPL 的值覆盖 CPL 的值。
而段选择子中的 TI 是表指示标记,如果 TI=0,则表示段描述符(段的详细信息)在 GDT(全局描述符表)中,即去 GDT 中去查;而 TI=1,则去 LDT(局部描述符表)中去查。
通过前面的sreg
命令我们知道ds=0x0017
,展开为二进制为0000000000010111
,所以 RPL=11,可见是在最低的特权级(因为在应用程序中执行),TI=1,表示查找 LDT 表,索引值为 10(二进制)= 2(十进制),表示找 LDT 表中的第 3 个段描述符(从 0 开始编号)
LDT 和 GDT 的结构一样,每项占 8 个字节。所以第 3 项 0x00003fff 0x10c0f300(上一步骤的最后一个输出结果中) 就是搜寻好久的 ds 的段描述符了。
段选择符内容
可以看到,段描述符是一个 64 位二进制的数,存放了段基址和段限长等重要的数据。其中位 P(Present)是段是否存在的标记;位 S 用来表示是系统段描述符(S=0)还是代码或数据段描述符(S=1);四位 TYPE 用来表示段的类型,如数据段、代码段、可读、可写等;DPL 是段的权限,和 CPL、RPL 对应使用;位 G 是粒度,G=0 表示段限长以位为单位,G=1 表示段限长以 4KB 为单位;其他内容就不详细解释了。
费了很大的劲,实际上我们需要的只有段基址一项数据,即段描述符 “0x00003fff 0x10c0f300” 中加粗部分组合成的 “0x10000000”。这就是 ds 段在线性地址空间中的起始地址。用同样的方法也可以算算其它段的基址,都是这个数。
段基址+段内偏移,就是线性地址了。所以 ds:0x3004 的线性地址就是:
0x10000000 + 0x3004 = 0x10003004
用calc ds:0x3004
命令可以验证这个结果.
找到了虚拟内存的线性地址,接下来就是找对应的物理地址,而实际操作系统管理内存使用的是分页机制,因此我们需要查询页表
前面找到的线性地址为0x10003004
首先需要算出线性地址中的页目录号、页表号和页内偏移,它们分别对应了 32 位线性地址的 10 位 + 10 位 + 12 位,所以 0x10003004 的页目录号是 64,页号 3,页内偏移是 4。
通过creg
命令可以查看页目录表的位置,页目录表的位置保存在CR3寄存器中
CR0=0x8000001b: PG cd nw ac wp ne ET TS em MP PE
CR2=page fault laddr=0x10002f68
CR3=0x00000000
PCD=page-level cache disable=0
PWT=page-level writes transparent=0
CR4=0x00000000: osxmmexcpt osfxsr pce pge mce pae pse de tsd pvi vme
说明页目录表的基址为0,通过xp/68w 0
查看其内容
0x00000000 : 0x00001027 0x00002007 0x00003007 0x00004027
0x00000010 : 0x00000000 0x00024764 0x00000000 0x00000000
0x00000020 : 0x00000000 0x00000000 0x00000000 0x00000000
0x00000030 : 0x00000000 0x00000000 0x00000000 0x00000000
0x00000040 : 0x00ffe027 0x00000000 0x00000000 0x00000000
0x00000050 : 0x00000000 0x00000000 0x00000000 0x00000000
0x00000060 : 0x00000000 0x00000000 0x00000000 0x00000000
0x00000070 : 0x00000000 0x00000000 0x00000000 0x00000000
0x00000080 : 0x00ff3027 0x00000000 0x00000000 0x00000000
0x00000090 : 0x00000000 0x00000000 0x00000000 0x00000000
0x000000a0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x000000b0 : 0x00000000 0x00000000 0x00000000 0x00ffb027
0x000000c0 : 0x00ff6027 0x00000000 0x00000000 0x00000000
0x000000d0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x000000e0 : 0x00000000 0x00000000 0x00000000 0x00000000
0x000000f0 : 0x00000000 0x00000000 0x00000000 0x00ffa027
0x00000100 : 0x00faa027 0x00000000 0x00000000 0x00000000
页目录表和页表中的内容很简单,是 1024 个 32 位(正好是 4K)数。这 32 位中前 20 位是物理页框号,后面是一些属性信息(其中最重要的是最后一位 P)。其中第 65 个页目录项就是我们要找的内容,用“xp /w 0+64*4”查看:
0x00000100 : 0x00faa027
其中的 027 是属性,显然 P=1,其他属性实验者自己分析吧。页表所在物理页框号为 0x00faa,即页表在物理内存的 0x00faa000 位置。从该位置开始查找 3 号页表项,得到(xp /w 0x00faa000+3*4):
0x00faa00c : 0x00fa7067
其中067是属性,显然P=1;
线性地址 0x10003004 对应的物理页框号为 0x00fa7,和页内偏移 0x004 接到一起,得到 0x00fa7004,这就是变量 i 的物理地址。可以通过两种方法验证。
第一种方法是用命令 page 0x10003004,可以得到信息:
linear page 0x10003000 maps to physical page 0x00fa7000
第二种方法是用命令 xp /w 0x00fa7004,可以看到:
0x00fa7004 : 0x12345678
现在,通过直接修改内存来改变 i 的值为 0,命令是: setpmem 0x00fa7004 4 0,表示从 0x00fa7004 地址开始的 4 个字节都设为 0。然后再用“c”命令继续 Bochs 的运行,可以看到 test 退出了,说明 i 的修改成功了,此项实验结束。
总结一下求物理地址的步骤
-
找出LDT在GDT中的位置
-
根据ds的值得到段描述符索引,TI
-
在段表中找段描述符,求出线性地址
-
根据线性地址查页表
- 线性地址前十位为页目录项,通过CR3寄存器找到页目录的起始地址,然后根据页目录项找到对应的物理页框
- 线性地址中间十位为页表号,根据页表号在物理页框的位置找到页表号在物理内存的位置
- 线性地址最后十二位为业内偏移,由前面得到的页表号在物理内存的位置加上业内偏移得到物理内存
性地址前十位为页目录项,通过CR3寄存器找到页目录的起始地址,然后根据页目录项找到对应的物理页框
* 线性地址中间十位为页表号,根据页表号在物理页框的位置找到页表号在物理内存的位置
* 线性地址最后十二位为业内偏移,由前面得到的页表号在物理内存的位置加上业内偏移得到物理内存