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

2010年(4)

2009年(33)

2008年(1)

分类:

2009-05-05 00:27:26

 IDT(Interrupt Descriptor Table)仅能存放 interrupt-gate、trap-gate 和 task-gate。


指令:
  int 0x80
-----------------------------------
  0x80 是 vector (中断向量号)

  在 x86 下,gate-descriptor 是 8 个字节,所以:gate = IDTR.base + vector * 8,在 long mode 下,gate-descrptor 是 16 字节,所以:gate = IDTR.base + vector * 16。

  在 real-mode 下,IVT(Interrupt Vector Table)是 IDT 在 real-mode 下的表现形式。IVT entry 是 4 个字节,构成 CS:offset 这种形式,offset 在低端,CS 在高端。所以: entry = IDTR.base + vector * 4。 IDTR.base 初始为 0。




1、索引 gate 和 descriptor

  在 IDT 中查找 gate descriptor 是以 vector 为索引,不存在 selector,vector 从 0 ~ 255 共 1 个字节。

gate = IDTR.base + vector * 8;

selctor = gate.selector;

temp_descriptor = get_descriptor(selector, GDT /* or LDT);


  用 IDTR.base + vector * 8 方法得取 gate-descriptor,再用 gate_descriptor 的 selector 来获取目 code segment descriptor。



2、权限 check

  在访问 IDT 中不使用 selector,所以不存在 RPL 检查,仅需要检查 CPL 、DPLg 和 DPLs 就行了。DPLg 代表 gate-descriptor 的 DPL,DPLs 代表 code segment descriptor 的 DPL。

  若 gate 是个 task-gate 仅需判断 CPL <= DPLg 就行了


if (gate.type == TASK_GATE) {                         /* task gate */
  if (CPL <= DPLg) {
     /* 通过,允许访问 */
  } else {
     /* 失败,#GP 异常 */
  }

} else if (CPL <= DPLg && CPL >= DPLs) {           /* interrupt / trap gate */

  /* 通过,允许访问 */
} else {
  /* 失败,#GP 异常 */
}


  无论 code segment 是 conforming 还是 non-conforming 都是可以的。





3、stack 切换

  interrupt / trap 服务例程必然在运行在 supervisor 权限下,若切入 interrupt/trap 例程的代码运行在 user 权限下,这将导致权限改变,stack 也发生切换。


DPL = temp_descriptor.DPL;                          /* code segment descriptor's DPL */

old_SS = SS;
old_ESP = ESP;

SS = TSS.stack[DPL].SS;                                /* 加载目标 stack */
ESP = TSS.stack[DPL].ESP;


push(old_SS);
push(old_ESP);

push(Eflags);

push(CS);
push(EIP);

if (ERROR_CODE) {
  push(error_code);
}


stack 切换时,processor 做以下工作:
(1)以目标 code segment 的 DPL 为索引在 TSS 获取相应权限级别的 stack pointer 加载到 SS & ESP
(2)将旧的 SS & ESP 保存在当前 stack (新 stack) 中。
(3)EFLAGS 寄存器入栈。
(4)返回地址(CS & EIP)入栈。
(5)由异常引发的 interrupt/trap 例程,还将异常码入栈,以供 interrupt/trap 例程使用。

 SS.RPL 会被更新为 DPL,表示当前 stack 的权限级别就是 SS.RPL。


当发生同级权限的转移时,不会发生 stack 切换,使用的是当前的 stack:

push(Eflags);
push(CS);
push(EIP);

if (ERROR_CDOE)
  push(error_code)





4、Eflags 寄存器的处理

  processor 将旧的 Eflags 入栈保存,将修改当 Eflags 的标志

if (gate == TASK_GATE)                    /* task gate */
  Eflags.NT = 1;
else                                    /* interrupt/trap gate */
  Eflags.NT = 0;

Eflags.TF = 0;
Eflags.VM = 0;
Eflags.RF = 0;

if (gate == INTERRUPT_GATE) {                 /* interrupt gate */
  Eflags.IF = 0;
} else if (gate == TRAP_GATE) {        /* trap gate */
  /* do nothing */
}                                            /* task gate */


(1)若是 task gate,processor 将置 Eflags.NT = 1 进入 Nest Task 模式,在 Iret 指令返回时将检查 Eflags.NT = 1 进入 task 切换。若是 interrupt/trap 的话,将 Eflags.NT 清 0,防止 IRET 指令回时进行 task 切换。
(2)Eflags.TF 将被清 0,将 Eflags.TF = 1 将引发 #DB 异常,processor 将进入单步调试模式,在进入响应 #DB 异常的例程前,processor 会将 Eflags.TF 清 0。
(3)Eflags.RF 清 0,在 IRET 指令返回时,processor 将 Eflags.RF 置为 1,使得 #DB 异常断点下一条指令能顺利执行。




5、加载 code segment descriptor

  同样,processor 会加载 code segment selector & descriptor 到 CS 寄存器

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


  加载后,CS.RPL 就是新的 CPL,


  
6、执行服务例程

  processor 将加载 EIP = CS.base + gate.offset,然后执行 CS:EIP 处的中断服务例程。




7、中断例程返回

  中断例程使用 iret 指令返回时,processor 同样会进行一系统的权限检查,中断例程不能向高权限的代码返回。


if (Eflags.NT == 1) {
  /* 发生 task 切换 */
}


pop(temp_EIP);
pop(temp_CS);

if (temp_CS.RPL == CPL) {                        /* CPL == RPL */

  goto return_same;       /* 同级返回 */

} else if (temp_CS.RPL > CPL)                  /* CPL < RPL */

  goto return_less;        /* 向低权限代码返回

else {                                         /* CPL > RPL */
  /* 异常,#GP 异常 */
  /* 拒绝向高权限代码返回 */
}



return_same:
  CS = temp_CS;
  EIP = temp_EIP;

  pop(Eflags);

  return;


return_less:

  pop(temp_Eflags);

  pop(temp_ESP);
  pop(temp_SS);

  if (temp_SS.RPL == temp_CS.RPL) {
    Eflags = temp_Eflags;

    SS = temp_SS;
    ESP = temp_ESP;

      CS = temp_CS;
    EIP = temp_EIP;

  } else {
    /* 失败,#GP 异常 */
  }

  return;



(1)iret 指令检查 Eflags.NT 是否为 1,为 1 时,直接进行任务切换出去,这个 task 就是 TSS.link 里的 TSS selector。

(2)pop 出旧的 CS & EIP,并进行权限检查,temp_CS.RPL 代表原来的 CPL 运行级别,必须 CPL <= temp_CS.RPL,若 pop 出的 CS.RPL 是低于 CPL 的,则会产生 #GP 异常,processor 防止向高权限的代码返回。

  当向同级的代码返回时,仅需加载回原来的 CS & EIP 以及 EFLAGS 寄存器就行了。

  当向低权限代码返回时,processor 会检查 pop 出来的 SS.RPL 是否等于 temp_CS.RPL(原来的 CS.RPL),不相同则产生 #GP 异常,在 x86 下,无论什么情况 CPL.RPL 必须等于 SS.RPL。通过后,processor 相应地加载 SS & ESP、CS & EIP 以及 EFLAGS 寄存器,然后返回原程序。
阅读(7499) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~