Protection 1

保护模式下CPU会对其资源和运行的程序进行保护,当发现违规行为的时候产生异常,这篇文章的目的是为后续学习保护机制搭建一个代码的框架。CPU的保护机制很复杂,保护的内容也很多,我们通过研究每种保护机制来学习它,这篇文章中的代码提供了这样的一个代码框架,在后续的保护机制学习的时候可以修改相应的代码来验证不同的保护机制。


中断和异常处理程序

为了实现保护机制,CPU在执行的过程中会对各种指令,状态,权限进行检查,如果违背的CPU的规定,就会产生异常(Exception)。异常是CPU通知程序它检测到了错误,有些异常是能够恢复的有些是不能够恢复的。CPU提供的保护机制也是建立在异常基础之上的,当CPU检测到程序代码违背了保护机制的规定,就会产生异常,通常会产生#GP异常(General Protection)。程序员负责提供异常处理程序来处理异常,CPU负责检查违规行为,产生异常并且调用相应的异常处理程序。

还有一个概念就是中断,中断和异常的概念差不多,但是还是有区别的,后续会详细的学习中断和异常的机制。

如何将CPU检查到的错误与程序员提供的特定的异常处理程序关联呢?在《Real-Address Mode》这篇文章中提到了IVT,但是那是real-mode下的中断和异常与处理程序关联的方式,在protected-mode下不能够使用,而且protected-mode下的映射机制和数据结构和real-mode下的也是不同的,protected-mode下涉及到了前面讲的GDT以及IDT(中断描述符表)。


这是Intel官方文档中关于如何从中断向量映射到相应的处理程序的描述,根据中断或者异常向量号,从IDT中查找到相应的门描述符,门描述符中包含中断或者异常处理程序所在的段的段选择子以及偏移量,通过选择子在GDT或者LDT中获得段基址,然后计算出处理程序的线性地址,这样就可以跳转到处理程序去处理中断或者异常了。


IDT和IDTR

GDT前面介绍过了,这里就不再赘述了。IDT是我们没有见过的新的数据结构,它是一个向量表或者叫做数组,中断号就是这个数组的索引。这个数组中保存的元素是一种叫做门描述符的数据结构


这是Intel官方文档中关于中断门(Interrupt-Gate)的描述,此外还有两种门描述符可以保存在IDT中,它们是任务门(Task-Gate)和陷阱门(Trap-Gate),这里我们只用到了中断门,其他两种门描述符等到学习中断和异常的时候再详细的学习。

中断门是8个字节,包括了16位的段选择子,32位的偏移,还有一些属性位和保留位。通过16位的段选择子在GDT或者LDT中选择相应的段描述符,然后通过段描述符中的段基址和中断门中的偏移计算出中断处理程序的线性地址。

与GDT类似,也需要一个寄存器来保存IDT的基址以及表限,CPU中使用IDTR寄存器来保存这些内容。IDTR和GDTR结构一样,都有一个32/64位的线性基址,以及一个16位的表限。


这是Intel官方文档中关于如何从IDTR索引IDT的描述。

加载IDTR的过程也和加载GDTR的一样,CPU通过lidt指令来加载IDTR。


示例

我们给出一个例子,这个例子中仅仅安装了一个#GP的异常处理并且通过加载无效的段选择子触发#GP异常。

.equ null_selector, 0x00
.equ code_selector, 0x08
.equ vedio_selector, 0x10
.equ stack_selector, 0x18
.equ data_selector, 0x20
.equ int_selector, 0x28

.equ invalid_selector, 0x0100

.equ init_esp, 0x6000
.equ int_base, 0x1000

# REAL-ADDRESS-MODE CODE
.code16
.section .text

.globl _start
_start:
    cli
    xorw %ax, %ax
    movw %ax, %ds
    movw %ax, %es
    movw %ax, %ss

    #move interrupt code to 0x1000
    movw $_interrupt_and_exception_length, %cx
    movw $_interrupt_and_exception, %si
    movw $int_base, %di
    rep movsb

    # setup idt in 0x00
    movl $0x00, %edi
    movl $256, %ecx
_setup_idt:
    movl $0x00, (%edi)
    movl $0x00, 4(%edi)
    addl $8, %edi
    loop _setup_idt
    # setup #GP handler
    movl $13, %edi
    shll $3, %edi
    movl $0x00280000, (%edi)
    movl $0x00008e00, 4(%edi)

    lidt idt_pointer

    lgdt gdt_pointer

    movl %cr0, %eax
    orl $0x01, %eax
    movl %eax, %cr0

    ljmp $code_selector, $_protect

# GDT
gdt_start:
null_descriptor:
    .quad 0x0000000000000000
code_descriptor:
    .quad 0x00cf9a000000ffff
vedio_descriptor:
    .quad 0x00cf920b8000ffff
stack_descriptor:
    .quad 0x00cf92007000ffff
data_descriptor:
    .quad 0x00cf92000000ffff
int_descriptor:
    .quad 0x00cf9a001000ffff
gdt_end:
.equ gdt_len, gdt_end - gdt_start

gdt_pointer:
    .short gdt_len
    .int   gdt_start
idt_pointer:
    .short 0x7ff
    .int   0x00

# DATA
_data:
_gp_msg:
    .ascii "In exception #GP handler."
_gp_msg_end:
    .equ _gp_msg_length, _gp_msg_end - _gp_msg

# PROTECTED-MODE CODE
.code32
.type _protect, @function
_protect:
    movw $data_selector, %ax
    movw %ax, %ds
    movw $vedio_selector, %ax
    movw %ax, %es
    movw $stack_selector, %ax
    movw %ax, %ss
    movw $null_selector, %ax
    movw %ax, %fs
    movw %ax, %gs
    movl $init_esp, %esp

    # trigger #GP exception
    movw $invalid_selector, %ax
    movw %ax, %ss

    jmp .

# INTERRUPT AND EXCEPTION HANDLER
_interrupt_and_exception:
.type _gp, @function
_gp:
    movl $_gp_msg, %esi
    movl $_gp_msg_length, %ecx
    call _int_echo

    jmp .
    iret

# %edi, %ax, %ecx, %esi
.type _int_echo, @function
_int_echo:
    xorl %ebx, %ebx
    movl %ebx, %edi
    shll %edi
    movb $0x0c, %ah
_int_start_echo:
    movb %ds:(%esi), %al
    movw %ax, %es:(%edi)

    inc %ebx
    movl %ebx, %edi
    shll %edi
    inc %esi
    loop _int_start_echo 

    ret

_interrupt_and_exception_end:
.equ _interrupt_and_exception_length, _interrupt_and_exception_end - _interrupt_and_exception

dummy:
    .space 510-(.-_start), 0

magic_number:
    .byte 0x55, 0xaa

这段代码是很简单的,在real-mode环境中安装了#GP处理函数,然后转换到protected-mode,通过给%ss加载无效的段选择子触发#GP,从而进入#GP处理程序。

这样运行结果如下,左上角的红字表示我们的程序进入了#GP处理函数:



问题

#GP的处理函数_gp有些特殊的地方,在iret之前有一个jmp .指令,这使得代码运行到_gp之后就不能够返回了,原因是#GP是Fault类型的异常,这种类型的异常退出之后,触发异常的指令要再次执行,由于_gp并没有修正无效段选择子的错误,所以在此运行movw %ax, %ss是又会引起#GP,所以我们选择了在_gp中无限循环。


参考

《Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3 (3A, 3B & 3C): System Programming Guide》
《自己动手写操作系统》
《x86/x64体系探索与编程》


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值