使用 bochs 可以很容易很直观地观察调试系统。下面选取一个 xp 启动的实际片断,如下:
sreg cs:s=0x001b, dl=0x0000ffff, dh=0x00cffa00, valid=1 ds:s=0x0023, dl=0x0000ffff, dl=0x00cff300, valid=31 ss:s=0x0023, dl=0x0000ffff, dl=0x00cff300, valid=31 ss:s=0x0023, dl=0x0000ffff, dl=0x00cff300, valid=31 fs:s=0x003b, dl=0xe000ffff, dl=0x7f40f3fd, valid=7 gs:s=0x0000, dl=0x00000000, dl=0x00000000, valid=0 ldtr:s=0x0000, dl=0x00000000, dh=0x00000000, valid=0 tr:s=0x0028, dl=0x200020ab, dh=800008b04, valid=1 gdtr:base=0x8003f000, limit=0x3ff idtr:base=0x8003f400, limit=0x7ff |
1、GDTR.base 是 0x8003f000, GDTR.limit 是 0x3ff
2、IDTR.base 是 0x8003f400, IDTR.limit 是 0x7ff
3、LDTR.selector 为 0x0000
这里没有建立 LDT,它的 selector 是 0x0000,也就是 NULL descriptor。
5.6.1、 观察 cs register 观察 cs 的信息:
1、cs 使用的 selector 正是前面提到的 0x1b
2、接下来的 dl=0x0000ffff, dh=0x00cffa00 其实就是 descriptor 信息。
来看一看 cs 的 descriptor 的什么:
x/2 0x8003f000+3*8 0x8003f018 : 0x0000ffff 0x00cffa00 |
cs 使用的 selector 是 0x1b,因此:selector.RPL = 3 使用的权限 3 selector.TI = 0,使用 GDT,selector.SI = 3
descriptor 的地址在:gdtr.base + 3 * 8 = 0X8003f018。
它的值按 64 位显示是:0x00cffa00_0000ffff。
那么,descriptor 的信息:
1、 base = 0x00000000,这是 32 位值。
2、 limit = 0xffffff,这是一个 20 位的值。
3、 DPL = 11b,也就是 3 级。
4、 S 位是 1,它是一个非系统的 descriptor,也就是属于 segment descriptor。
5、 type 是 1010b,显示它是一个 execute/readable non-conforming 类型的 code segment descriptor。
6、 limit 的粒度位 G 位是 1,显示它是 4K 粒度的。
7、 最后缺省位 D 位是 1, 表明目标 code segment 的 32 位代码。
对这个 descriptor 描述的信息,归纳一下为:segment 是 32 位的代码段,基地址是 0x00000000,访问权限是 3 级,limit 是 0xFFFFF * 0x1000 + 0xFFF = 4G。
物理上这个 descriptor 被加载到 cs register 里。
5.6.2、 观察 ds register 我们看看 ds 加载的 descriptor 的又是怎样的。
ds 使用的 selector 是 0x23:TI = 0,SI = 4 以及 RPL = 3。
获取 descirptor :
x/2 0x8003f000+4*8 0x8003f020 : 0x0000ffff 0x00cff300 |
这个 descriptor 的值是:0x00cff300_0000ffff (64 位值)
1、 base = 0x00000000,这是 32 位值。
2、 limit = 0xffffff,这是一个 20 位的值。
3、 DPL = 11b,也就是 3 级。
4、 S 位是 1,它是一个非系统的 descriptor,也就是属于 segment descriptor。
5、 type 是 0011b,显示它是一个具有 R/W 权限的 data segment descriptor。
6、 limit 的粒度位 G 位是 1,显示它是 4K 粒度的。
7、 最后缺省位 D 位是 1, 同 code segment descriptor 意义一致
---------------------------
这个 descriptor 与上面 cs 的 descriptor 不同之处仅是 type 不同。这个 descriptor 是个 data segment descriptor。
5.6.3、 平坦的内存模式 现在的操作系统绝大部分使用平坦的内存模式,这种模式下,所有 segment 的基址是 0x00000000,但是 windows 使用了 FS 来定义非零基址的段。 FS 描述的段的基地址是:0x7f3de000,使用 FS 来管理一些系统信息。
使用了平坦模式,导致可以使用 ds 读取 cs 的数据,或者可以执行 cs 以外的如:ds 或 ss 的代码。在 segmentation 这个阶段里 processor 无法阻止 stack 里的代码可以执行这一情况。直至在 paging 保护措施上才得到解决。
既然使用了平坦模式,逻辑地址与线性地址是一致的。导致现代操作系统已经弱化逻辑地址这个概念,虚拟地址一般就指线性地址
因此:对于两条指令
mov eax, dword ptr cs:[0x8012100]
mov eax, dword ptr ds:[0x8012100]
-------------------------------------
结果是完全一致的。当然这是有提前的。提前是:cs 装的这个 code segment 是可读的。
但是,对于这两条指令,情况就不同了:
mov dword ptr cs:[0x8012100],eax
mov dword ptr ds:[0x8012100],eax
-------------------------------------
第 1 条是会出错的。这里 cs 装的 code segment 是不可写的。