Segment Register 也可以称作 Selector Register,它在整个 x86/x64 体系下实在太重要了。
这里讲解的是 user segment 寄存器,包括:
这些段寄存器由 user segment descriptor 装载进去。
我们很有必要先去了解 segment 寄存器的真实结构:
上图是我画的 Segment Registers 内部结构图,这分为 4 个部分:
图中已经标出灰色部分是 processor 内部使用的,仅仅只有 Selector 部分可以由用户使用
但是在 64 位模式下,它很特别:
注意上面所说的是针对在 64 位模式下。
segment registers 结构体现了 protected mode 下的几大元素: selector descriptor descriptor table |
---|
Segment registers 的 base 提供基址,无论是 real mode 下还是 protected mode 下,其地址的计算方法都是:base + offset
如果有下面代码:
jmp next next: |
---|
目标代码地址在 CS.base + eip
Segment Registers 的 Attribute 是描述段的属性,它在装载 segment descriptor 的时候加载进去设置的,它包括:
在 real mode 下 segment registers 的 Attribute 一般情况下是初始状态。在 protected mode 下它随着 descriptor 而改变。
在 64 位模式下大多数属性位都是无效的,并且在 code segment register 和 data segment register 之间也存在差别:
在 CS 寄存器里新增了一个属性位:
在 64 位下 CS 寄存器只有下面的一些属性位是有效的:
但是请注意:
你必须要设置 CS 寄存器的 S 属性和 C/D 属性: CS.S = 1 表示:用户的段寄存器 CS.C/D = 1 表示:该 segment register 是代码段寄存器 |
---|
这也就是说:加载到 CS 寄存器的 code segment descriptor 你必须将它的 S 属性设为 1,C/D 属性设为 1 才能加载到 CS 寄存器中
S 属性用来设置 system 还是 user 的段寄存器,属性 system 的段寄存器有:LDTR 寄存器和 TR 寄存器
C/D 属性用来指示是 Code 还是 Data 段。
对 CS 寄存器来说,您必须设置这两个属性位为 1 表明它是用户代码段寄存器,否则会产生 #GP 异常
对于这几个 data 段寄存器(SS 寄存器有些特别,除外)来说,只有下面这个属性才有效:
但是同样需要注意:
必须设置 data 段寄存器的 S 属性和 C/D 属性(以 DS 寄存器为例): DS.S = 1 表示:用户的段寄存器 DS.C/D = 0 表示:它是 Data 段寄存器 但是有一种例外: 使用 NULL selector 加载到 data segment register 是允许的,processor 将加载 invalid 不可用的 descriptor 到 segment registers 中 |
---|
但是有一种例外:
也就是说:除了使用 NULL selector 加载外,data segment descriptor 的 S 属性需设为 1,C/D 属性需设为 0 才能加载到 data segment registers 中
对 DS 段寄存器来说,S 必须为 1 并且 C/D 为 0 表明它是用户的数据段寄存器,否则会产生 #GP 异常
SS 寄存器是 data segment register 的其中一种,除了要遵循上面的 data segment register 规则外。
它还必须:
在 SS 寄存器的 attribute 域的 type 里: W = 1(Writable) E 忽略 A 忽略 |
---|
这表示:由 SS 寄存器访问的 stack segment必须是可写的,因此:加载到 SS 寄存器的 data segment descriptor 它的 W 属性必须设为 1(表示可写)
在 64 位模式下可以使用 NULL selector 加载到 SS 寄存器中,processor 不会去读取 GDT 表中的第 1 项,而是以 invalid unused 的 segment 到 SS 寄存器中
但是在 legacy x86 模式和 compatibility 模式下是不能使用 NULL selector 加载来 SS 寄存器的。
了解 segment registers 结构有什么好处呢? 我们来了解一下 segment register 的 base 更新情况。
base 的更新在 real mode 和 protected mode 下是不同的。
我们来看一下在 real mode 下 segment register 的情形
以上面的代码为例:
mov ax, cs mov ds, ax |
---|
这两条代码是在 real mode 下,它有什么奥秘呢?
指令 mov ds, ax 改变了 DS 寄存器的两个值: DS.selector = ax DS.base = DS.selector << 4 |
---|
DS 寄存器的 selector 得到更新,同时 base 也得到更新,这个 DS 寄存器的 base 的更新规则就是 real mode 下的寻址方式:
在 protected mode 下,base 的更新是在加载 descriptor 进入 registers 时发生:base 被更新为 segment descriptor 的 base 域
在 real mode 下 segment register 的 limit 一般情况下固定为 0xFFFF(64K 大小),这个值在 processor 初始化时设定。
在 protected mode 下这个值最大范围为 0xFFFFFFFF(4G 大小)依赖于加载的 descriptor 的 limit 值。
下面代码是在 protected mode 下执行:
bits 32 code32_entry: mov ax, data32_sel mov ds, ax |
---|
将 data segment descriptor 的 selector 赋给 ax,下面的指令:
将会引发 processor 做一系列的工作:
通过权限检查后 processor 将 data segment descriptor 相应的信息更新 DS 内部结构,包括:
但是在 real mode 下如果不转到 protected mode 下更新,segment register 的 attribute 和 limit 是永远得不到更新的。它的 limit 值固定为 0xFFFF
关于real mode 下 segment 的 attrbiute 属性,请参见:http://www.mouseos.com/arch/001.html 里有详细描述。
前面已经讲过,selector 会得到更新,而 base 会被更新为 selector << 4
看看下面这段伪代码:
struct SELECTOR selector = 8; /* selector = 0x08 */ struct DESCRIPTOR descriptor = get_descriptor(selector); /* get segment descriptor */ /* update DS register ... */ DS.selector = selector; DS.attribute = descriptor.attribute; DS.limit = descriptor.G ? descriptor.limit * 4096 + 0xfff : descriptor.limit; DS.base = descriptor.base; |
---|
使用了 selector 为 8 获得 descriptor 后进行 DS 寄存器的更新,对 base 的更新要视乎 G 标志位:
下面是 C 代码的描述:
struct SEGMENT_REGISTER { unsigned short selector; //16bit unsigned short attribute; unsigned int limit; //32bit #ifdef __x64__ unsigned long long base; #else unsigned int base; #endif }; |
---|