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

2010年(4)

2009年(33)

2008年(1)

分类:

2009-05-04 00:04:19

直接调用/跳转的形式是:
  call / jmp selector:offset

  这里的 selector 是 code segment selector 直接使用 selector 来索引 code segment,这将引发 CS 的改变,code segment descriptor 最终会被加载到 CS 寄存器里。
  在 code segment descriptor 加载到 CS 之前,processor 会进行一系列的检查,包括权限检查、type 检查、limit 检查等,在通过检查后,processor 才加载 descriptor 到 CS,紧接着 eip = CS.base + offset,最后跳转到 cs:eip 执行。



以下面的指令为例:

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

0x20 是目标 code segment selector ,看看 processor 如何处理。




1、索引 code segment descriptor

  selector:0x20 的 RPL = 00,TI = 0,SI = 4
  processor 在 GDT 以 SI = 4 索引查找 descriptor,当查找到 descriptor,processor 将判断这个 descriptor 的 types 是什么,再做进一步的处理。


这个查找 descriptor 的过程表述如下:

RPL = 00;
TI = 0;
SI = 4;


if (TI == 0)
  DT = GDT;                 /* 在 GDT 表 */
else
  DT = LDT;                 /* 在 LDT 表 */


temp_descriptor = DT.base + SI * 8;               /* 获取 descriptor */


switch (temp_descriptor.type)
{
case CODE_DESC:                    /* 是个 code segment descriptor */
  goto do_code_desc;

case CALL_GATE:                    /* 是个 call gate descriptor */
  goto do_call_gate;     

case TSS_DESC:                     /* 是个 TSS descriptor */
  goto do_tss_desc;            

case TASK_GATE:                   /* 是个 task gate descriptor */
  goto do_task_gate;

default:                           /* 若不是上述几种类型,则产生 #GP 异常 */
  goto do_#GP_exception;               
};


  processor 在判断 descriptor 后作进一步处理,这里假设 descriptor 是 code segment descriptor,下一步是 processor 将作权限的检查,检查程序是否有权限访问目标 code segment。

  在上述获取 descriptor 之前,processor 还会对 GDT 的 limit 作检测,若发现 GDT.base + SI * 8 > GDT.limit 同样会引发 #GP 异常。这种情况也就是说:索引值越界了。




2、权限 check

  processor 用当前的权限与目标 code segment descriptor 作的权限 check。当前的权限就是 RPL & CPL。在这直接调用/跳转目标 code segment 的 check 中 conforming 与 nonconforming 类型的 descriptor 有着很大的区别。



这个权限的 check 表述如下:

DPL = temp_descriptor.DPL;

if (temp_descriptor.C == 0) {    /* code segment 是 non-conforming 类型 */

  if (CPL == DPL) {

    if (RPL <= DPL) {
      goto do_next;            /* 通过检查,允许访问 */  

    } else
      goto do_#GP_exception;

  } else
    goto do_#GP_exception;     /* 产生 #GP 异常 */


     
} else {                        /* code segment 是 conforming 类型 */

  if (CPL >= DPL) {
    goto do_next;            /* 通过检查,允许访问 */  
  } else
    goto do_#GP_exception;           /* 产生 #GP 异常 */
}


当 code segment 是 non-conforming 类型时,需要 CPL == DPL && RPL <= DPL 才能通过。
当 code segment 是 conforming 类型时,仅需要 CPL >= DPL 就能通过了。

当 code segment 是 conforming 类型时,CPL >= DPL,表示当前的代码可以向高权限级别跳转。这里无需判断 RPL 权限。
  假设当前运行在 3 级代码上,通过 call / jmp 到 conforming 类型的 0 级别代码时,当前的 CPL 依然是 3 级。因为在直接 call/jmp 目标 code segment 这种调用方式上,是不会改变当前的运行级别。


情景提示:
  在直接 call/jmp 目标 code segment 方式上,CPL 是不会改变的。既使由低权限代码调用高权限的 conforming 类型的代码,CPL 也不会改变。
  在由低权限直接 call/jmp 高权限的代码仅限于 conforming 类型的 code segment。


  conforming 类型的 code segment 允许低权限的代码向高权限的这类代码调用/跳转,而 non-conforming 则不允许直接调用/跳转。直接 call/jmp 目标 code segment 不改变 CPL,基于这个原因 non-conforming 类型的 code segment 必须要 CPL == DPL。
  若要向高权限的 non-conforming 类型 code segment 调用/跳转时,必须通过 call gate 进行 call / jmp。




3、加载 descriptor

  通过上述权限检查后,processor 会将目标的 selector 加载到 CS 寄存器中,而 descriptor 也会加载到 CS 寄存中。



加载 descriptor 过程表述为:

selector = selector | (SI << 3) | (TI << 2) | CS.selector.DPL;

CS.selector = selector;                          /* 加载 selector */

CS.base = temp_descriptor.base;                     /* 加载 base 进入 CS*/
CS.limit = temp_descriptor.limit;                  /* 加载 limit 进入 CS */
CS.attribute = temp_descriptor.attribute;         /* 加载 attribute 进入 CS */


selector = selector | (SI << 3) | (TI << 2) | CS.selector.DPL; 
-----------------------------------------------------------
  在这一步里,使用目标 code segment selector 的 SI 更新 CS.selector.SI,使用 TI 更新 CS.selector.TI。
  但是这里不更新 CS.selector.DPL,因为 CPL 不会改变。

  CS 内的信息(selector & descriptor)会保持下去,直至下一次重新加载 descriptor 到 CS 为止。所以,在同一 code segment 内的 call/jmp 是不会做权限检查等等。




4、执行目标 code segment

  processor 会加载 CS.base + offset 进入 eip ,然后执行 CS: eip 处的代码。这个 offset 就是 call/jmp 指令的 eip 值,也就是上述的 0x00040000 值。



push old_cs;
push old_eip;

eip = CS.base + offset;         /* 加载 eip */

(void (*)()) &eip;                      /* 执行 cs: eip */



  由于这里不会改变 CPL,所以也无需做检测是否需要 stack 切换的工作。






7.1.3.2.1、 long mode 的 64 bit 模式下的直接 call / jmp

  在 64 bit 模式下不支持 call/jmp selector:offset 这种指令形式,在 64 bit 模式下,这种形式将引发 #UD 异常。

  
在 64 bit 模式下仅支持:

  call far ptr [target_code]

  jmp far ptr [target_code]
---------------------------------------
  仅支持目标是内存操作数的指令形式。当然这个内存操作数可以是任一种内存寻址模式。
如:
  call far ptr [rax+rcx*8+0xc]
  call far ptr [rip+0x80140]

  指令从 [target_code] 中取出 32 位的 offset 和 16 位 selector。 32 位的 offset 被零扩展至 64 位再加上 rip。


情景提示:
  Intel 明确说明: call far ptr [target_code],在 [target_code] 中可以直接读取 64 位的 offset 值和 16 位 selector 值。当编译机器码为:48 ff /3 时可以支持 64 位 offset 值 + 16 位 selector。
  AMD 则明确说明:当 operands 为 64 位时,读取的仅是 32 位的 offset 值 + 16 位的 selector,32 位的 offset 将零扩展至 64 位的 offset。



情景提示:
  Intel 说的是在指令编码中使用 REX.W 将 operands 扩展为 64 位,则读取的是 64 位 offset。AMD 的文档中没有说明当使用 REX.W 将 operands 扩展为 64 位时 call far 指令将会读取多少?
  但是,在调试器 x64 版的 windbg 里实验表明:使用 REX.W 确实可以将 call far 指令扩展为读取 64 位 offset + 16 位的 selector 。




processor 的处理过程:
  
1、索引 code segment descriptor 的方法和 x86 的一致。但和 x86 下不同的是:
  (1)、64 bit 下不存在 task gate
  (2)、若使用 selector 查找到的 descriptor 是 TSS descriptor 将产生 #GP 异常。
  (3)、64 bit 下不进行 limit 的 check。
  (4)、64 bit 下 processor 将检测 code segment descriptor 的 L = 1 &&  D = 0,表明目标代码是 64 位代码,若 L = 0 或者 D = 1 则产生 #GP  异常





2、权限的 check  

64 bit 的权限 check 和 x86 的一致,即:

if (non-conforming == 1) {   /* 是 non-conforming 类型 */

  if ( CPL == DPL && RPL <= DPL)
    /* 通过,允许访问 */
  else
    /* 失败,拒绝访问,产生 #GP 异常 */


}  else {        /* 是 conforming 类型 */

  if (CPL >= DPL)
    /* 通过,允许访问 */
  else
    /* 失败,拒绝访问,产生 #GP 异常 */
}




3、加载 descriptor 进入 CS

  由于 64 bit 模式下,code segment descriptor 中仅 L、D、DPL、C 及 P 属性有效,其它都无效的,这一步意义不大。CS.base 和 CS.limit 都是无效的。base 被强制为 0,limit 是固定的 64 位空间。
  代替的是进行 canonical-address 地址检查。

  此时,CPL 也不会改变,即:CS.selector.DPL 不会被更新。所以也不会引发 stack 切换。



4、执行 code segment

  接下来 64 位的 offset 值被加到了 rip 寄存器中,然后执行 rip 处的指令。

阅读(3862) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~