[转载]使用 call/jmp 直接调用/跳转目标 code segment

直接调用/跳转的形式是:

  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 处的指令。

时间: 2024-10-26 12:06:47

[转载]使用 call/jmp 直接调用/跳转目标 code segment的相关文章

[转载]目标 code segment 的访问

当程序中使用指令 call / jmp,以及通过 int 引发中断例程的执行,这将都是对目标的 code segment 进行访问,当通过权限的检查后程序将会跳转到目标的 code segment 进行执行. 在 code segment 的访问过程中涉及到权限级别的改变,stack 的改变等问题. 访问目标 code segment 的几种情形: 1.call / jmp offset 在段内直接 call / jmp,不改变目标 code segment 2.call / jmp code_

[转载]通过 call gate 访问目标 code segment

直接 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 --------

5.4.1 中间代码生成与优化_UCC编译器的优化_删除无用的临时变量和优化跳转目标

5.4.1  删除无用的临时变量和优化跳转目标 UCC编译器在优化方面做的工作不多,其中与优化有关的函数主要有以下几个: (1)    Symbol  Simplify(Type ty, int opcode, Symbol src1,Symbol src2); 用于进行"代数恒等式"的简化,例如表达式"a<<0"可简化为a. (2)    Symbol  TryAddValue(Type ty, int op, Symbol src1,Symbol s

【转载】C# 跨线程调用控件

转自:http://www.cnblogs.com/TankXiao/p/3348292.html 感谢原作者,转载以备后用 在C# 的应用程序开发中, 我们经常要把UI线程和工作线程分开,防止界面停止响应.  同时我们又需要在工作线程中更新UI界面上的控件, 下面介绍几种常用的方法 阅读目录 线程间操作无效 第一种办法:禁止编译器对跨线程访问做检查 第二种办法: 使用delegate和invoke来从其他线程中调用控件 第三种办法: 使用delegate和BeginInvoke来从其他线程中控

【转载】VC中如何调用其他的可执行程序

在开发项目的时候,有的时候会分开来开发,分开的有时是exe文件,有的时候也会调用现成的工具包里面的一些exe文件,这样在项目里面就要通过调用exe文件来使用. 那么在C++里面直接调用exe文件的方法有哪些呢?现在可考虑的方法主要有: a.使用system函数 b.使用exec或者是execv函数 c.使用WinExec函数 d.使用CreateProcess函数 e.使用ShellExcecuteEx函数 1)上面的5中方法中,system函数,函数原型system(执行shell命令)定义函

转载 iOS js oc相互调用(JavaScriptCore)

iOS js oc相互调用(JavaScriptCore) 从iOS7开始 苹果公布了JavaScriptCore.framework 它使得JS与OC的交互更加方便了. 下面我们就简单了解一下这个框架 首先我导入framework 方法如下 点击Linked Frameworks and Libraries 的添加后 选择 JavaScriptCore.framework 选中JavaScriptCore.framework后 点击右下角Add 添加完成 好 创建完成之后我们导入一下头文件 [

[转载]Linux下关于system调用

曾经的曾经,被system()函数折磨过,之所以这样,是因为对system()函数了解不够深入.只是简单的知道用这个函数执行一个系统命令,这远远不够,它的返回值.它所执行命令的返回值以及命令执行失败原因如何定位,这才是重点.当初因为这个函数风险较多,故抛弃不用,改用其他的方法.这里先不说我用了什么方法,这里必须要搞懂system()函数,因为还是有很多人用了system()函数,有时你不得不面对它. 先来看一下system()函数的简单介绍: int system(const char *com

(转载)在m文件里调用simulink模型---sim命令的用方法

在m文件里调用模型mdl关键是用sim命令. [ t, x, y ] = sim( model, timespan, options, ut) [ t, x, y1, y2, -, yn] = sim( model, timespan, options, ut) (1)model:需要进行仿真的系统模型框图名称: (2)timespan:系统仿真的时间范围(起始至终止时间),可有如下形式: tFinal:设置仿真终止时间.仿真起始时间默认为0: [tStarttFinal]:设置起始时间(tSt

setTimeout递归调用跳转页面

1 <!DOCTYPE html> 2 <html> 3 <head lang="en"> 4 <meta charset="UTF-8"> 5 <title></title> 6 <script> 7 window.onload = function () { 8 function $(id){ 9 return document.getElementById(id); 10 }