Chinaunix首页 | 论坛 | 博客
  • 博客访问: 362983
  • 博文数量: 38
  • 博客积分: 1935
  • 博客等级: 上尉
  • 技术积分: 724
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-23 16:34
文章存档

2010年(4)

2009年(33)

2008年(1)

分类:

2009-05-05 00:13:37

  直接 call / jmp 目标 code segment 不能改变当前的 CPL,若要 call / jmp 高权限的 code segment 必须使用 call gate,在 x86 下还要可以 call / jmp TSS descriptor 或者 call / jmp task gate,但在 64 bit 模式下 TSS 任务切换机制不被支持。



同样以下面的指令为例:

(1)  call  0x20:0x00040000
(2)  jmp 0x20:0x00040000

--------------------------------
  这里的 0x20 是 call gate selector,0x0004000 是 offset ,看看 processor 怎样处理。



1、索引 call gate descriptor 及 目标 code segment descriptor

  (1)第一步先找到 call gate descriptor,索引查找 call gate descriptor 的方法与 7.1.3.2 节中的 “索引 code segment descriptor “ 是一样的。

  (2)第二步再根据找到的 call gate descriptor,使用同样的方法用 descriptor 里的 selector 再找到目标 code segment descriptor。


两个过程表述如下:

call_gate_descriptor = get_descriptor(0x20);     /* 用 selector 0x20 先找到 call gate */

selector = call_gate_descriptor.selector;         /* 使用 call gate 中的 selector */

temp_descriptor = get_descriptor(selector);       /* 再找到 code segment descriptor */


  查找 call gate descriptor 与 code segment descriptor 的方法是一样的。根据得到的 selector 找到相应的 descriptor 结构。




2、权限的 check

  processor 检查权限,既要检查是否有权限访问 call gate,还要检查是否有权限访问 code segment。


check 过程表述如下:

DPLg = call_gate_descriptor.DPL;              /* call gate 的 DPL */
DPLs = temp_descriptor.DPL;                   /* code segment descriptor 的 DPL */


if (RPL <= DPLg && CPL <= DPLg) {                /* 检查是否有权限访问 call gate */
  /* pass */

  if (temp_descriptor.C == 0) {         /* 目标 code segment 是 non-conforming 类型 */
    if (Opcode == JMP)          /* 假如使用 jmp 指令 */
      if (CPL == DPLs) {
                        /* 通过,允许访问 */
      } else {
        goto do_#GP_exception;         /* 失败,拒绝访问,#GP 异常 */
      }
  }

  if (CPL >= DPLs) {           /* 检查是否有权限访问 code segment */
                                  /* 通过,允许访问 */
  } else {
    goto do_#GP_exception;        /* 失败,拒绝访问,#GP 异常 */
  }


} else {

  goto do_#GP_exception;                 /* 失败,拒绝访问 #GP 异常产生 */
  
}



  代码中,DPLg 代表 call gate descriptor 的 DPL,DPLs 代表目标 code segment descriptor 的 DPL。


检查通过的条件是:

  (1)(RPL <= DPLg)  &&  (CPL <= DPLg) 表示有权访问 call gate。
并且:
  (2)CPL >= DPLs  表示有权访问 code segment,表示:只允许低权限向高权限转移,或者平级转移。不允许高权限向低权限转移。

-------------------------------------------------------
在第(2)步的条件里:
  假如使用 call 指令:则无论是 conforming 类型还是 non-conforming 类型的 code segment,都可以成功通过。
  假如使用 jmp 指令:目标是 conforming 类型 code segment 可以通过。但是目标是 non-conforming 类型的 code segment 的情况下,必须:CPL == DPLs(CPL 必须等于 code segment descriptor 的 DPL)才能通过。

  这是基于 jmp 指令访问 non-conforming 类型的代码不改变 CPL 的原因。       


所以,这两个条件是:
  (1)RPL <= DPLg && CPL <= DPLg
并且:
  (2)CPL >= DPLs (call/jmp conforming 类型或者 call non-conforming 类型)
    或:
     CPL == DPLs (jmp non-conforming 类型)



  call gate 用来是建立一个保护的系统例程机制,目的是由低权限的代码调用高权限的系统例程。所以:CPL >= DPLs,当前的 CPL 权限要低于 DPL 权限。
  conforming 类型 code segment 的目的是可以由低权限向高权限代码转移。non-conforming 类型则要求严格按照规定的权限进行。




3、加载 descriptor 进入 CS

  同样,通过权限 check 后,processor 会加载 selector 和 descriptor 进入 CS 寄存器。但是,在一步里 processor 的额外工作是判断是否进行 CPL 改变。


  假设当前代码是 3 级,目标代码是 0 级,则发生权限的改变,CPL 改变也导致 3 级的 stack 切换到 0 级的 stack。

加载 descriptor 的表述如下:

CS.selector = temp_descriptor.selector;       /* 加载目标 code segment 的 selector */

CS.selector.DPL =  temp_descriptor.DPL;       /* 更新 CPL */

CS.base = temp_descriptor.base;
CS.limit = temp_descriptor.limit;
CS.attribute = temp_descriptor.attribute;


  CS.selector.DPL = temp_descriptor.DPL;
  由于权限的改变,CPL 需要更新,因此目标 code segment 的 DPL 将被更新至 CS.selector 的 DPL(或者说 RPL)中。




4、 stack 的切换

  由于 CPL 的改变,导致 stack pointer 也要进行切换。新的 stack pointer 在 TSS 中相应权限级别的 stack pointer 中获取。

  接上所述,stack 将由 3 级切换至 0 级。


stack 的切换表述如下:

DPL = temp_descriptor.DPL;

old_ss = SS;
old_esp = esp;

SS = TSS.stack_pointer[DPL].SS;                    /* 加载 SS */
esp = TSS.stack_pointer[DPL].esp;                /* 加载 esp */


push(old_cs);
push(old_esp);


if (call_gate_descriptor.count) {
  copy_parameter_from_old(call_gate_descriptor.count, old_ss, old_esp);
}


push(old_cs);
push(old_eip);




stack 切换主要做以下 5 个工作:

(1)用 code segment descriptor 的 DPL 来索引相应的级别 stack pointer(0 级)
(2)将索引找到的 stack pointer(0 级) 加载到 SS 和 ESP 寄存器,当前变为 0 级的 stack pointer。
(3)将原来的 stack pointer(0 级) 保存到新的 stack 中。
(4)如果 call gate 中的 count 不为 0 时,表示需要传递参数。
(5)保存原来的 CS 和 EIP

----------------------------------------------
  上面代码中的红色部分是判断 call gate 中是否使用了 count 域来传递参数。复制多少个字节?复制 count * sizeof(esp) 个字节。参数会被复制到新的 stack 中,也就是 0 级的 stack 中,以供例程使用。

  在将 SS selector 加载到 SS 寄存器时,processor 同样要做权限的检查。CPL 已经更新为 0,SS selector.RPL == 0 && stack segment descriptor.DPL == 0,所以条件:CPL == DPL && RPL == DPL 是成立的,新的 SS selector 加载到 SS 寄存器是成功的。

  SS selector 加载到 SS ,processor 会自动加载 stack segment descriptor 到 SS 寄存器,SS.selector.RPL 就是当前的 stack 的运行级别,也就是 0 级。
  旧的 SS selector(3 级) 被保存在 0 级的 stack 中,在例程返回时,会重新加载 old_SS 到 SS 寄存器,实现切换回原来的 stack pointer。



5、执行系统例程 code segment

  成功加载 CS 和 SS 后,EIP 将由 call gate 中的 offset 加载。


执行例程表述为:

eip = call_gate_descriptor.offset;              /* 加载 eip */

(void (*)()) &eip;                                /* 执行例程 */



  由于例程的入口地址在 call gate 中指定,所以指令中的 offset 是被忽略的。

指令:
  call 0x20:0x00040000
------------------------------------
  指令中的 offset 值 0x0004000 将被 processor 忽略。真正的 offset 在 call gate 中指出。但是从指令格式上必须给出 offset 值,即:cs:eip 这个形式对于 call far 指令来说是必须的。










7.1.3.3.1、 long mode 下的 call gate


指令:
  call 0x20:0x00040000

---------------------------------------
  当前 processor 运行在 long mode 的 compatibility 模式下,这条指仅是有效的。若在 long mode 的 64 bit 模式下,有条指令是无效的,产生 #UD 异常。

在 64 bit 模式下:

指令:  call far ptr [mem32/mem64]
--------------------------------------
  这种形式的 far call 才被支持。memory 操作数可以是 32 位 offset + 16 位 selector 或者 64 位 offset + 16 位 selector



所以最终的指令形式是:

(1) call far ptr 0x20:0x00040000    或  call far ptr [call_gate64]        /* compatibility 模式 */

(2) call far ptr [call_gate64]                                                       /* 64 bit 模式 */




情景提示:
  long mode 下仅允许 64 位的 gate 存在。无论是 compatibility 模式还是 64 bit 模式,都不允许 32 位的 gate 存在。


  因此 long mode 下 call gate 是 64 位的 call gate(共 16 个字节),offset 被扩展为 64 位。processor 会对 gate 中的 selector 指向的 code segment 进行检查。64 位的 call gate 指向的 code segment 必须是 64 位 code segment,即:L = 1 并且 D = 0。
  processor 若发现 L = 0 或者 D = 1 将会产生 #GP 异常。

情景提示:
  由于 long mode 的 gate 是 64 位的,当在 compatibility 模式下的 32 位代码执行调用 call-gate 执行系统服务例程,或由中断指令 INT n 陷入中断服务例程时,执行的是 64 bit 的系统服务例程(64 位的 OS 组件)。





  因此,0x20 是一个 64 位 call gate 的 selector。


1、获取 call gate 和 code segment。

  processor 对 call gate 的索引查找以及 code segment 的索引查找和 x86 下是一样的。见:上述第 1 步。



2、processor 对 call gate 和 code segment 的检查

  在索引到 call gate 后,processor 会对 call gate 首先进行检查,包括:

(1)检查 call gate 的高半部分的 types 是否为 0000,不为 0 则产生 #GP 异常。
(2)检查 call gate 中的 selector 指向的 code segment 是否为 L = 0 并且 D = 0,表明目标 code segment 是 64 bit 的。否则产生 #GP 异常。



3、权限的 check

  与 x86 下的 call gate 检查机制一样。

即:
(1)RPL <= DPLg && CPL <= DPLg  (访问 gate 的权限)
(2)CPL >= DPLs     (call/jmp conforming 类型或者 call non-conforming 类型)
  或:CPL == DPLs    (jmp non-conforming 类型)



同样:CPL >= DPLs  表明由低权限调用高权限代码
   CPL == DPLs  表明不能改变 CPL,这个情况是由 jmp non-conforming 时产生。



4、加载 code segment descriptor

  同样,目标 code segment 的 selector 和 descriptor 将被加载到 CS 寄存器中。

情景提示:  
  在 64 bit 模式下仅 CS.L、CS.D、CS.DPL、CS.C 以及 CS.P 是有效的,其它属性和域都是无效的。

  可是,即使 processor 当前处于 compatibility 模式下,在使用 gate 的情况下,加载到 CS 的结果和 64 bit 模式下是完全一样的。因为:在 long mode 下 gate 是 64 位的,所使用的目标 code segment 也是 64 位的。


  因此,当 CS 加载完后:CS.L = 1、CS.D = 0。
即:
  此时 processor 由 compatibility 模式切换到 64 bit 模式
  当系统服务例程执行完毕返回时,processor 会由 64 bit 切换回到 compatibility 模式,直至最软件退出返回 OS,最终 processor 再次切换回到 64 bit 模式。


code segment descriptor 在 long mode 下的意义是:

(1)建立一个 segmentation 保护机制。
(2)控制目标 code segment 是 compatibility 模式还是 64 bit 模式。



同样,若发生权限的改变,CPL 需要更新,stack 也需要切换。假设当前的代码为 3 级调用 0 级的代码:

(1)CS.selector.DPL = temp_descriptor.DPL  (使用目标 code segment 的 DPL 更新 CPL)
(2)接下着进行 stack 的切换




5、stack 切换

  经由 call-gate 转到服务例程,此时 processor 必定处于 64 bit 模式下。发生权限的改变时,processor 从 TSS 里取出相应级别的 stack pointe,即:RSP。此时的 TSS 是 64 位的 TSS


这个过程表述如下:

DPL = temp_descriptor.DPL;         

old_ss = ss;
old_rsp = rsp;

ss = NULL;                                  /* NULL selector 被加载到 ss */
ss.selector.RPL = DPL;                     /* 更新当前的 stack 的级别 */
rsp = TSS.stack_pointer[DPL];              /* 索引到相应的 rsp,加载到 rsp 中 */

push64(old_ss);
push64(old_rsp);

push64(old_cs);
push64(old_rip);



在这里的 stack 切换中注意:

(1)SS 被加载为 NULL selector(0x00)。
(2)SS.selector.RPL 需要被更新为 CPL,指示当前的 stack 级别。

------------------------------------------------------------

在由 compatibility 模式切换到 64 bit 模式的情况下:

(1)原来的 SS 和 32 位的 ESP 被扩展为 64 位压入 stack 中。
(2)同样,原来的 CS 和 32 的 EIP 被扩展为 64 位压入 stack 中。
(3)返回后,SS 和 32 的 ESP 被加载回 SS 和 ESP,RSP 的高半部分被抛弃。
(4)同样,CS 和 32 的 EIP 被加载回 CS 和 EIP,RIP 的高半部分被抛弃。





6、执行系统服务例程

  processor 从 call gate 处获取 offset 值,加载到 RIP 中,从而执行 RIP 处的代码。
 
 
 
 
 
 
:)
阅读(1884) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~