首页 > 代码库 > [转载]使用 int n 调用系统例程

[转载]使用 int n 调用系统例程

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 寄存器,然后返回原程序。

[转载]使用 int n 调用系统例程