- UID
- 1029342
- 性别
- 男
|
1.3 设置自己的中断服务例程 在中断向量表里还有许多空 vector 是未使用的,我们可以在这些空白的向量里设置自己的中断服务例程,典型的如: DOS 操作系统中使用了 0x21 号向量作为 DOS 提供给用户的系统调用!
在这里我将展示,使用 0x40 向量作为自己的中断服务例程向量,我所需要做的是:
- 写一个自己的中断服务例程,在本例中的 my_isr
- 设置向量 0x40 的 segment 和 offset 值
- 调用 int 0x40 进行测试
中断服务例程 my_isr 很简单,仅仅是打印信息:
;------------------------------------------------
; our Interrupt Service Routine: vector 0x40
;-------------------------------------------------
my_isr:
mov si, msg3
call printmsg
iret | 接下来设置 vector 0x40 的 segment 和 offset:
mov ax, cs
mov bx, [new_IVT+2] ; base of IVT
mov dword [bx+0x40*4], my_isr ; set offset 0f my_isr
mov [bx+0x40*4+2], ax ; set segmet of my_isr | 记住 segment 在高位,offset 在低位,这个 segment 是我们当前的 CS,offset 是我们的 ISR 地址,直接写入 IVT 表中就可以了
现在我们可以测试了:
结果如下:
我们的 ISR 能正常工作了,我提供了完整的示例源码和磁盘映像下载:interrupt_demo
2. 保护模式下的中断机制 引入保护模式后,情形变得复杂多了,实施了权限控制机制,为了支持权限的控制增添了几个重要的数据结构,下面是与中断相关的结构:
- gate descriptor(门描述符):用于描述和控制 Interrupt Service Routine 的访问,中断可使用的 gate 包括:
- Interrupt-gate descriptor(中断门描述符)
- Trap-gate descriptor(陷井门描述符)
- Task-gate descriptor(任务门描述符)-- 在 64 位模式下无效
- interrupt descriptor table(中断描述符表):用于存在 gate descriptor 的表格
在 32 位保护模式下,每个 gate descriptor 是 8 bytes 宽,在 64 位模式下 gate descriptor 被扩充为 16 bytes, 同时 64 位模式下不存在 task gate descriptor,因此在 64 位下的 IDT 只允许存放 interrupt/trap gate descriptor。
当我们执行调用中断时,processor 会在 IDT 表中寻找相应的 descriptor,并通过了权限检查转入相应的 interrupt service routine(大多数时候被称为 interrupt handler),在中断体系里分为两种引发方式:
然而软件上发起的中断调用还可分为:
- 引发 exception(异常) 执行 interrupt handler
- 软件请求执行 system service(系统服务),而执行 interrupt handler
硬件引发的中断请求还可分为:
- maskable interrupt(可屏蔽中断)
- non-maskable interrupt(不可屏蔽中断)
无论何种方式,进入 interrupt handler 的途径都是一样的。
2.1 查找 interrupt handler 入口 在发生中断时,processor 在 IDTR.base 里可以获取 IDT 的地址,根据中断号在 IDT 表里读取 descriptor,descriptor 的职责是给出 interrupt handler 的入口地址,processor 会检查中断号(vector)是否超越 IDT 的 limit 值。
上图是 interrupt handler 的定位查找图。在 32 位模式下的过程是:
- 从 IDTR.base 得到 IDT 表地址
- 从 IDTR.base + vector * 8(每个 descriptor 为 8 bytes)处读取 8 bytes 宽的 escriptor
- 对 descriptor 进行分析检查,包括:
- descriptor 类型的检查
- IDT limit 的检查
- 访问权限的检查
- 从 gate descriptor 里读取 selector
- 判断 code segment descriptor 是存放在 GDT 表还是 LDT 表
- 使用 selector 从 descriptor table 读取 code segment descriptor,当然这里也要经过对 code segment descriptor 的检查
- descriptor 类型检查
- GDT limit 检查
- 访问权限的检查
- 从 code segment descriptor 中读取 code segment base 值
- 从 gate descriptor 里读取 interrupt handler 的 offset 值
- 得取 interrupt handler 的入口地址:base + offset,转入执行 interrupt handler
它的逻辑用 C 描述,类似下面:
long IDT_address; /* address of IDT */
long DT_address; /* GDT or LDT */
DESCRIPTOR gate_descriptor; /* gate descriptor */
DESCRIPTOR code_descriptor; /* code segment descriptor */
short selector; /* code segment selector */
IDT_address = IDTR.base; /* get address of IDT */
gate_descriptor = IDT_address + vector * 8; /* get descriptor */
selector = gate_descriptor.selector;
DT_address = selector.TI ? LDTR.base : GDTR.base; /* address of GDT or LDT */
code_descriptor = GDT_address + selector * 8; /* get code segment descriptor */
interrupt_handler = code_descriptor.base + gate_descripotr.offset; /* interrupt handler entry */
((*(void))interrupt_handler)(); /* do interrupt_handler() */
| 上面的 C 代码显示了 processor 定位 interrupt handler 的逻辑过程,为了清楚展示这个过程,这里省略了各种的检查机制!
2.2 IDT 表中 descriptor 类型的检查 processor 会对 IDT 表中的 descriptor 类型进行检查,这个检查发生在:
在 IDT 中的 descriptor 类型要属于:
- S = 0:属于 system 类 descriptor
- descriptor 的 type 域应属于:
- 1110:32-bit interrupt gate
- 1111:32-bit trap gate
- 0101:task gate
- 0110:16-bit interrupt gate
- 0111:16-bit trap gate
非上述所说的类型,都将会产生 #GP 异常。当 descriptor 的 S 标志为 1 时,表示这是个 user 的 descriptor,它们是:code/data segment descriptor。可以看到在 32 位保护模式下 IDT 允许存在 interrupt/trap gate 以及 task gate
2.3 使用 16-bit gate descriptor 在 32 位保护模式下 interrupt handler 也能使用 16-bit gate descriptor,包括:
- 16-bit interrupt gate
- 16-bit trap gate
这是一个比较特别的现象,假如使用 16-bit gate 来构建中断调用机制,实际上等于 interrupt handler 会从 32-bit 模式切换到 16-bit 模式执行。只要构建环境要素正确这样切换执行当然是没问题的。
这个执行环境要素需要注意的是:当使用 16-bit gate 时,也要相应使用 16-bit code segment descriptor。也就是在 gate descriptor 中的 selector 要使用 16-bit code segment selector。下面我写了个使用 16-bit gate 构建 interrupt 调用的例子:
; set IDT vector
mov eax, BP_handler
mov [IDT+3*8], ax ; set offset 15-0
mov word [IDT+3*8+2], code16_sel ; 16-bit code selector
mov word [IDT+3*8+4], 0xc600 ; DPL=3, 16-bit interrupt gate
shr eax, 16
mov [IDT+3*8+8], ax ; offset 31-16 | 上面这段代码将 vector 3 设置为使用 16-bit interrupt gate,并且使用了 16-bit selector
下面是我的 interrupt handler 代码:
bits 16
;-----------------------------------------------------
; INT3 BreakPoint handler for 16-bit interrupt gate
;-----------------------------------------------------
BP_handler:
jmp do_BP_handler
BP_msg db 'I am a 16-bit breakpoint handler on 32-bit proected mode',0
do_BP_handler:
mov edi, 10
mov esi, BP_msg
call printmsg16
iret
| 这个 interrupt handler 很简单,只是打印一条信息而已,值得注意的是,这里需要使用 bits 16 来指示编译为 16 位代码。
那么这样我们就可以使用 int3 指令来调用这个 16-bit 的 interrupt handler,执行结果如图:
|
|