天天看点

重新认识Intel中断(一)Intel中断原理实验

文章系列:

重新认识Intel中断(一)

重新认识Intel中断(二)

重新认识Intel中断(三)

文章目录

  • Intel中断原理
  • 实验

Intel中断原理

  • 一句话解释,Intel中断和调用门很像,都通过门描述符间接实现代码转移,把任务门、中断门、陷阱门放在IDT中实现中断
  • 中断是统称,可以细分成系统产生的Fault/Trap/Abort和用户定义的Interrupt。Fault和Trap的区别是从处理例程返回时CPU动作不一样,Fault要纠正错误,重新执行产生错误的指令,Trap会跳过错误,继续执行错误指令之后的一条指令。注意,系统产生的Fault/Trap/Abort,也能通过INT指令触发,后面的实验会提到。
  • 中断发生时,代码转移的权限检查规则和调用门一样,但中断没有RPL,所以RPL不参加检查。检查规则参见重新认识Intel权限检查(二) 。一句话解释,IDT表中的门描述符被看做数据段,CPL权限必须高于或者等于DPL_G(数字上小于),同时必须低于或者等于DPL_B时,才能触发中断代码转移
  • 中断发生时,如果DPL_B的权限和CPL一样,不会切换堆栈,只把EFLAGS,CS,EIP入栈。如果是系统产生的中断(前32位向量)会把ERROR CODE信息入栈,然后加载中断例程指令地址,执行
  • 中断发生时,如果DPL_B的权限比CPL高,会发生堆栈切换。切换过程一句话解释,从CPU当前TSS中取出对应特权级的堆栈指针(假如从ring3升高到ring0,就取ring0对应的栈指针),保存当前SS,ESP到临时内存,加载ring0对应的CS,EIP地址到寄存器,完成堆栈切换,把先前内存中的SS,ESP入栈,再把EFLAGS,CS,EIP入栈,如果是系统产生的中断(前32位向量)会把ERROR CODE信息入栈,然后加载中断例程指令地址,执行。
重新认识Intel中断(一)Intel中断原理实验
  • 有堆栈切换的中断,执行中断例程时CPL会升高到目标代码段的DPL

实验

  • 异常

    对比 INT 0模拟除0异常和系统产生的除0异常

  1. INT 0模拟除0异常

1)实验代码

1)准备除0异常处理例程,向屏幕打印一个D字符
_DEHandler:
DEHandler       equ _DEHandler  -   $$
    mov bx, 11
    mov cx, 76
    mov dl, 'D'
    call SelectorInitCode:PrintChar
    jmp $
2)安装异常处理例程到IDT
[SECTION .gdt]
; GDT
;                              段基址,       段界限     , 属性
LABEL_GDT:     Descriptor       0,              0,      0   ; 空描述符
LABEL_DESC_INITCODE: Descriptor 0, SegCode32Len - 1,    DA_C + DA_32; 非一致代码段
; GDT 结束
GdtLen      equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1  ; GDT界限
            dd  0       ; GDT基地址
; GDT 选择子
SelectorInitCode        equ (LABEL_DESC_INITCODE    - LABEL_GDT)

[SECTION .idt]
LABEL_IDT:
;               选择子                  偏移
.000h:  Gate    SelectorInitCode,       DEHandler,      0,  DA_386IGate + DA_DPL2
IdtLen  equ $   -   LABEL_IDT
IdtPtr  dw  IdtLen  -   1
        dd  0  
; END of [SECTION .idt]

;	DA_386IGate EQU   8Eh   ; 386 中断门类型值 TYPE = E表示中断门类型,P = 1表示段在内存中存在
;   除0异常处理例程在SelectorInitCode:DEHandler处,将代码段地址信息写入选择子,DPL_B = 0
;   除0异常的门描述符在表的第一项,对应中断向量是0,并且向量表中只有一项,其它中断没有处理例程
;   注意这里的DPL设置的是2,实验结果会分析为什么
3)进入保护模式,执行INT 0指令
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS   32]
LABEL_SEG_INIT:
    xor eax, eax
    mov es, ax
    mov ds, ax

    str ax              ; 读取TR
    cmp ax, SelectorTSS ; 检查TR是否被加载,如果加载不再重复
    je  .1

    mov ax, SelectorTSS ; 加载TR
    ltr ax
.1:
    mov ax, cs
    and ax, 10b
    cmp ax, 10b         ; 判断CPL
    je  .2              ; 如果CPL!=2,retf到ring2,否则跳转到.2执行测试代码

    mov ax, SelectorStack0
    mov ss, ax
    mov sp, TopOfStack0

    push    SelectorStack2
    push    TopOfStack2
    push    SelectorInitCodeD2
    push    0
    retf
.2:                     ; 测试代码,CPL=2
    int 0				; 模拟除0指令

    mov ax, 06h 		; 制造系统产生的除0异常(6 % 0)
    mov bl, 00h
    div bl  

    jmp $
           

2)结果分析:

int 0指令执行之前,从cs看出CPL=2,用到了stack2堆栈。

base=0x00090180,cs=0x0012,eip=0x00000037,eflags=0x00000046

IDT中的中断门

.000h: Gate SelectorInitCode, DEHandler, 0, DA_386IGate + DA_DPL2

DPL_G=2

SelectorInitCode指向的代码段

SelectorInitCode equ (LABEL_DESC_INITCODE - LABEL_GDT)

DPL_B=0

满足权限检查,CPL权限大于等于DPL_G并且小于等DPL_B,并且会发生堆栈切换。eflags,cs,eip会被压如stack0。

如果DPL_G大于等于CPL,是1或者0,检查不通过;小于等于CPL,是3或者2,检查通过.

重新认识Intel中断(一)Intel中断原理实验

int 0指令执行后,cs:0x0008 => CPL=0,ss变成了stack0的地址,查看其指向的堆栈顶端,向下依次是

eflags=0x00000046,cs=0x0012,eip=0x00000037。证明我们的结论正确。

重新认识Intel中断(一)Intel中断原理实验
  1. 制造除0异常

    把上个实验的代码中int 0去掉。制造一个除0异常

.2:                     ; 测试代码,CPL=2
	; int 0
    mov ax, 06h 		; 制造系统产生的除0异常(6 % 0)
    mov bl, 00h
    div bl  

    jmp $
           

div指令执行前,eflags 0x00000046;cs:0x0012;eip: 0x0000003d ;ss:0x002a;esp: 0x0000001f

重新认识Intel中断(一)Intel中断原理实验

div指令执行后,寄存器信息和int 0几乎一样

重新认识Intel中断(一)Intel中断原理实验
  • 用户定义的中断

    使用任务门实现中断处理例程

  1. 实验关键代码
1)中断服务例程代码,打印一个U字符到屏幕
_UsrHandler:
UsrHandler  equ _UsrHandler -   $$
    mov ax, SelectorVideo
    mov gs, ax          ; 视频段选择子(目的)
    mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
    mov ah, 0Ch         ; 0000: 黑底    1100: 红字
    mov al, 'U'
    mov [gs:edi], ax

    iretd

2)将服务例程的地址放到TSS数据结构中
; TSS
[SECTION .tss]
ALIGN   32
[BITS   32]
LABEL_TSS1:
        DD  0           ; Back
        DD  TopOfStack0 ; 0 级堆栈
        DD  SelectorStack0  ; 
        DD  0           ; 1 级堆栈
        DD  0           ; 
        DD  TopOfStack2 ; 2 级堆栈
        DD  SelectorStack2 ; 
        DD  0           ; CR3
        DD  UsrHandler  ; EIP	;中断服务例程段内偏移
        DD  0           ; EFLAGS
        DD  0           ; EAX
        DD  0           ; ECX
        DD  0           ; EDX
        DD  0           ; EBX
        DD  TopOfTask   ; ESP
        DD  0           ; EBP
        DD  0           ; ESI
        DD  0           ; EDI
        DD  0           ; ES
        DD  SelectorInitCode; CS;中断服务例程代码段
        DD  SelectorTask; SS
        DD  0           ; DS
        DD  0           ; FS
        DD  0           ; GS
        DD  0           ; LDT
        DW  0           ; 调试陷阱标志
        DW  $ - LABEL_TSS1 + 2   ; I/O位图基址
        DB  0ffh            ; I/O位图结束标志
TSS1Len      equ $ - LABEL_TSS1

3)往IDT的80h项写入任务门描述符
[SECTION .idt]
LABEL_IDT:
;               选择子                  偏移
.000h:  Gate    SelectorInitCode,       DEHandler,      0,  DA_386IGate + DA_DPL2
%rep 127
        Gate    SelectorInitCode,       DummyHandler,   0,  DA_386IGate
%endrep
.080h:  Gate    SelectorTSS1,           0,              0,  DA_TaskGate + DA_DPL3;任务门    

IdtLen  equ $   -   LABEL_IDT
IdtPtr  dw  IdtLen  -   1
        dd  0
; END of [SECTION .idt]

4)GDT表准备
[SECTION .gdt]
; GDT
;                              段基址,       段界限     , 属性
LABEL_GDT:     Descriptor       0,              0,      0   ; 空描述符
LABEL_DESC_INITCODE: Descriptor 0, SegCode32Len - 1,    DA_C + DA_32; 非一致代码段
LABEL_DESC_INITCODED2: Descriptor   0, SegCode32Len - 1,    DA_C + DA_32 + DA_DPL2; 非一致代码段
LABEL_DESC_VIDEO:  Descriptor 0B8000h,       0ffffh,    DA_DRW + DA_DPL3; 显存首地址
LABEL_DESC_STACK0:  Descriptor  0,      TopOfStack0,    DA_DRW; 堆栈段 
LABEL_DESC_STACK2:  Descriptor  0,      TopOfStack2,    DA_DRW + DA_DPL2 ; 堆栈段 
LABEL_DESC_TASK:  Descriptor    0,      TopOfTask,      DA_DRW ; 堆栈段 
LABEL_DESC_TSS:  Descriptor     0,      TSSLen -1,    DA_386TSS; TSS段 
LABEL_DESC_TSS1:  Descriptor        0,  TSS1Len -1,    DA_386TSS; TSS段 

; GDT 结束
GdtLen      equ $ - LABEL_GDT   ; GDT长度
GdtPtr      dw  GdtLen - 1  ; GDT界限
            dd  0       ; GDT基地址

; GDT 选择子
SelectorInitCode        equ (LABEL_DESC_INITCODE    - LABEL_GDT)
SelectorInitCodeD2      equ (LABEL_DESC_INITCODED2  - LABEL_GDT) + SA_RPL2
SelectorVideo       equ (LABEL_DESC_VIDEO   - LABEL_GDT) + SA_RPL3
SelectorStack0      equ (LABEL_DESC_STACK0  - LABEL_GDT)
SelectorStack2      equ (LABEL_DESC_STACK2  - LABEL_GDT) + SA_RPL2
SelectorTask        equ (LABEL_DESC_TASK    - LABEL_GDT)
SelectorTSS         equ (LABEL_DESC_TSS     - LABEL_GDT)
SelectorTSS1        equ (LABEL_DESC_TSS1    - LABEL_GDT)
; END of [SECTION .gdt]

5)主程序发起中断
.2:                     ; 测试代码,CPL=2
    ;int 0				; 模拟除0指令

    ;mov ax, 06h 		; 制造系统产生的除0异常(6 % 0)
    ;mov bl, 00h
    ;div bl  

	int 80h
	
    jmp $
           
  1. 结果分析

    执行int 80h指令前;CPL=2,堆栈用的GDT[0x0028],当前任务是GDT[0x0038]。

    重新认识Intel中断(一)Intel中断原理实验
    执行指令后,CPL变成0,当前堆栈切换成GDT[0x0030],GDT[0x0040]=32-Bit TSS (Busy) 变成Busy,由于不是显式的调用CALL指令,所以前一个任务的Busy标记不会被清空,表示嵌套的任务。查看堆栈信息,没有CS,EIP,EFLAGS信息,为什么?由于时任务切换,所以返回信息都放在GDT[0x0038]=32-Bit TSS 中了。
    重新认识Intel中断(一)Intel中断原理实验
    附: 实验完整源码见my github intel中断