首页 > 代码库 > [转载]使用 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 调用系统例程