首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

深入理解 x86/x64 的中断体系(2)

深入理解 x86/x64 的中断体系(2)

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 表中就可以了
  现在我们可以测试了:
  
        int 0x40
  结果如下:
  
  我们的 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 入口  在发生中断时,processorIDTR.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 时   
  在 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,执行结果如图:
  
继承事业,薪火相传
返回列表