x86 下 segment 保护核心就是 gate 机制,x64 的 long 模式下 segment 保护有所弱化,AMD 号称 long 模式取消了 segment 机制,实际上,现阶段的的 x64 体系中,segment 机制是不可取消的。只不过是将平坦内存模式从软件层移到硬件层,等取于取消了 segment 机制。
现在的操作系统都将 protected 重心放在了 paging 阶段,x64 体系从硬件层体现了这个思想。
5.7.3.1、 gate 是什么 gate 机制体现在 gate descriptor 的设置,显然,gate descriptor 像是一道通关令符。顾名思义,门是通过另一片天地的必经之路。这片天地通常会得到更多的资源,这片天地通常就是系统的核心代码部分。
gate descripotr 门符就是指引通往这片天地的钥匙。有权限获得门符就可以访问更底层的代码。
gate descriptor 的作用相当于 C 语言里的指针,并且这个指针就是函数指针。
如下:
void foo() { ... ... }
int main() { void (*pf)() = foo; ... ... pf(); /* call gate 类似 */ ... ... }
|
gate 机制是一种间接的转移控制方式。gate 的主要作用是用来检查权限、切换权限。上面的 c 代码中,函数指针 pf 相当一个 call gate descritpor。gate descriptor 结构里指出了真正要跳转代码的地方。
有以下几种 gate 类型:
● call gate
● interrupt gate
● trap gate
● task gate
上面的图是这几种 gate descriptor 的格式。
1、在 task gate descriptor 里,关键的元素就是 TSS selector,这个 TSS selector 给出目标进程的 TSS selector,通过使用 task gate descriptor 达到使用 TSS 来进行进程的切换。
2、可见 interrupt gate descriptor 与 trap gate descriptor 的格式是完全一致的,它们的匹别只是 type 的不同。
3、而 call gate descriptor 与 interrupt/trap gate descriptor 的区别只是,使用 call gate 可以传递总共 31 个参数。
5.7.3.2、 如何使用 call gate 传递参数 在 call gate descriptor 的 parameter count 部分里指出了参数的个数。这个参数是告诉目标代码将要有多少个参数传递过来。
看下面的例子:
/* caller */
push param1 /* parameter */ push param2 push param3
call 0x20:00000000 /* call gate */
... ...
|
在调用方里使用 call gate 进行切换。caller 先压入了 3 个参数,然后通过 call gate 来调用子例程
/* callee */
... ...
ret 0x0c
|
子例程 callee 完成工作后,很正常地 ret 返回。
那么,当发生 stack 切换时,即:caller 在 CPL =3 下, callee 在 CPL = 0 下,这时会发生 stack 的切换。
此时,processor 会做相当多的必要工作,在假设通过权限的检查下:
1、首先从 TSS segment 里得到 0 级下的 stack 指针(即:SS 和 ESP)加载到 SS 以及 ESP。这时属于 0 级下的 stack 空间了。
2、将原来的 SS 及 ESP 保存到新的 stack 里,也就是压入旧的 SS 及 ESP。
3、然后,这里根据 call gate descriptor 里的 parameter count 参数个数,从旧的 stack 里复制参数到新的 stack 里。在这个例子里是 3 个参数,也就是依次压入 param1、param2 及 param3 在 stack 上。
在这一步里就实现了传递参数。4、然后,再保存返回指针(即:CS 及 EIP)。
5、在 callee 返回时执行 ret 0x0c 时,pop 出 CS 及 EIP,ret 0x0c 会消去压入的 12 个字节的。
6、pop 出 原来的 stack 指针(SS 及 ESP)。从而 0 级的 stack 恢复正常。
使用 call gate 传递参数,是一种 caller 与 callee 的约定方式,caller 压入参数,并明确告诉 callee 有多少个参数。而 callee 必须负责做消除 stack 工作。
这个例程的使用参数一旦定下来,caller 与 callee 就不能改变。若一方改变了这种约定,就会造成 stack 混乱。