使用调用门如何实现不同特权级代码之间的跳转(如:从高特权级到低特权级)?
调用门只支持从低特权级跳转到高特权级执行。
无法利用调用门从高特权级跳转到低特权级执行。
调用门的特权级跳转分析
1. 通过远调用(call far) : 低特权级 --> 高特权级
2. 通过远返回(retf) : 高特权级 --> 低特权级
函数的调用过程
需要提前知道的事实
1. x86处理器对于不同的特权级需要使用不同的栈。
2. 每一个特权级对应一个私有的栈(最多4个栈)。
3. 特权级跳转之前需要指定好相应的栈
解决方案(高特权级 --> 低特权级)
1. 指定目标栈段选择子(push)
2. 指定栈顶指针位置(push)
3. 指定目标代码段选择子(push)
4. 指定目标代码段偏移地址(push)
5. 跳转(retf)
%include "inc.asm"
org 0x9000
jmp ENTRY_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C + DA_DPL3
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA + DA_DPL3
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DR + DA_DPL3
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW + DA_DPL3
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL3
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL3
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL3
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL3
; end of [section .gdt]
TopOfStack16 equ 0x7c00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_Offset equ DTOS - $$
HELLOWORLD db "Hello, World!", 0
HELLOWORLD_Offset equ HELLOWORLD - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; initalize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt
cli
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
; jmp dword Code32Selector : 0
push Stack32Selector ; 目标栈段选择子
push TopOfStack32 ; 目标栈顶指针
push Code32Selector ; 目标代码段选择子
push 0 ; 目标代码段偏移
retf
; esi --> code segment labelBACK_ENTRY_SEGMENT
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Stack32Selector
mov ss, ax
mov eax, TopOfStack32
mov esp, eax
mov ax, Data32Selector
mov ds, ax
mov ebp, DTOS_Offset
mov bx, 0x0c
mov dh, 13
mov dl, 33
call PrintString
mov ebp, HELLOWORLD_Offset
mov bx, 0x0c
mov dh, 14
mov dl, 30
call PrintString
jmp $
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push cx
push eax
push dx
push edi
print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print
end:
pop edi
pop dx
pop eax
pop cx
pop ebp
ret
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
10-14行,23-26行我们分别把段描述符的DPL改为3,选择子的RPL改为3.
90-94行来实现从高特权级到低特权级的跳转。
我们通过打断点的方式来查看cs段寄存器的值,看看我们是否从高特权级跳转到低特权级。
在retf跳转之前,cs段寄存器的值为0,cs段寄存器的后两位为CPL(当前可执行代码段的特权级),后2位为0则说明当前位于0特权级,也说明了处理器默认的CPL为0。
retf执行完以后,cs段寄存器的值为0x00b,转换为2进制就是1011b,最后两位为11,则说明了此时处理器的CPL为3(最低特权级),这和我们设置的特权级时一样的。
其他结论
第一个实验
我们把90行和91行注释掉,运行后发现处理器出现了异常,这是因为从高特权级跳转到低特权级时,没有给它指定相应特权级的栈段。
第二个实验
我们将所有段描述符和选择子的特权级都改为0,然后注释掉90行和91行,程序正常的打印出了字符串,则说明在相同特权级代码段间执行跳转时,不需要指定目标栈的选择子和栈顶指针位置。此时92行-94行的作用和88行的作用是一样的,都能正常的跳转过去。