门描述符
通过门描述符可以在不同特权级的代码间进行跳转。根据应用场景的不同,门描述符可以分为:调用门(Call Gates)、中断门(Interrupt Gate)、陷阱门(Trap Gate)、任务门(Task Gate)。
门描述符的内存结构
1. 每一个门描述符占用8字节内存。
2. 不同类型的门描述符的内存含义不同。
通过调用门选择子可以得到门描述符里段选择子,通过段选择子我们可以得到这个段的段基地址,段基地址加上偏移地址就是函数的入口地址。
当我们需要执行该段之外的函数调用时,需要 call 段基址 : 偏移地址 这样调用。
代码如下
%include "inc.asm"
org 0x9000
jmp CODE16_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DES : Descriptor 0, Code32SegLen - 1, DA_32 + DA_C
VIDEO_DES : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_32 + DA_C
; Gate Descriptor
; Call Gate 选择子, 偏移地址, 参数个数, 属性
FUNC_CG_ADD_DESC Gate FunctionSelector, CG_Add, 0, DA_386CGate
FUNC_CG_SUB_DESC Gate FunctionSelector, CG_Sub, 0, DA_386CGate
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
FunctionSelector equ (0x0004 << 3) + SA_TIG + SA_RPL0
FuncCGAddSelector equ (0x0005 << 3) + SA_TIG + SA_RPL0
FuncCGSubSelector equ (0x0006 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
TopOfStackInit equ 0x7c00
[section .s16]
[bits 16]
CODE16_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStackInit
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DES
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
mov esi, FUNCTION_SEGMENT
mov edi, FUNCTION_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
; esi --> code segment label
; 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, 3
mov bx, 1
call FuncCGAddSelector : 0
call FuncCGSubSelector : 0
jmp $
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
[section .func]
[bits 32]
FUNCTION_SEGMENT:
; ax --> a
; bx --> b
; return
; cx = a + b
AddFunc:
mov cx, ax
add cx, bx
retf
CG_Add equ AddFunc - $$
; ax --> a
; bx --> b
; return
; cx = a - b
SubFunc:
mov cx, ax
sub cx, bx
retf
CG_Sub equ SubFunc - $$
FunctionSegLen equ $ - FUNCTION_SEGMENT
134-160行,我们新增的一个func段,这个段我们定义了2个函数。第15行,我们指定了这个段的段描述符。第30行,我们制定了这个段的选择子。
17-18行,我们定义了func段里2个函数的门描述符。31-32行定义它们的选择子。
118行我们调用了func段的加法函数,119行我们调用了func段的减法函数。
我们通过断点调试,查看相关寄存器的值来查看函数是否调用成功。
在函数调用之前eax寄存器的值为3,ebx寄存器的值为1,ecx的值为15。
我们成功跳转到了AddFunc处,在函数调用之后eax寄存器的值为3,ebx寄存器的值为1,ecx寄存器的值为4,说明了加法函数调用成功,我们继续单步调试,来查看下一个减法函数的调用情况。
我们成功跳转到了SubFunc处,函数执行完以后,ecx寄存器的值为2,则说明减法函数也执行成功了。
我们将118-119行改为以下2行代码
call FunctionSelector : CG_Add
call FunctionSelector : CG_Sub
执行结果是和上面的结果是一样的。
实验结论
1. 门描述符是一种特殊的描述符,需要注册于段描述符表
2. 调用门可以看作一个函数指针(保存具体函数的入口地址)
3. 通过调用门选择子对相应的函数进行远调用(call far)
4. 可以直接使用选择子 : 偏移地址的方式调用其他段的函数
5. 使用调用门时偏移地址无意义,仅仅是语法需要。
在14节课中,保护模式在不同的段中调用相同的函数,我们是通过复制粘贴来进行的,那么如何在不同的段中进行代码复用呢?
解决方案
1. 将不同的代码段需要复用的函数定义到独立的段中(retf)。
2. 计算每一个可复用函数的偏移量(FuncName - $$)。
3. 通过段选择子 : 偏移地址的方式对目标函数进行远调用。
下面贴出代码
%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
VIDEO_DESC : Descriptor 0xb8000, 0x7fff, DA_32 + DA_DRWA
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_32 + DA_DR
STACK32_DESC : Descriptor 0, TopOfStack32, DA_32 + DA_DRW
CODE16_DESC : Descriptor 0, 0xffff, DA_C
UPDATE_DESC : Descriptor 0, 0xffff, DA_DRW
TASK_A_LDT_DESC : Descriptor 0, TaskALdtLen - 1, DA_LDT
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_32 + DA_C
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
Code16Selector equ (0x0005 << 3) + SA_TIG + SA_RPL0
UpdateSelector equ (0x0006 << 3) + SA_TIG + SA_RPL0
TaskALdtSelector equ (0x0007 << 3) + SA_TIG + SA_RPL0
FunctionSelector equ (0x0008 << 3) + SA_TIG + SA_RPL0
; 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
mov [BACK_TO_REAL_MODE + 3], ax
; 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
mov esi, CODE16_SEGMENT
mov edi, CODE16_DESC
call InitDescItem
mov esi, TASK_A_LDT_ENTRY
mov edi, TASK_A_LDT_DESC
call InitDescItem
mov esi, TASK_A_CODE32_SEGMENT
mov edi, TASK_A_CODE32_DESC
call InitDescItem
mov esi, TASK_A_DATA32_SEGMENT
mov edi, TASK_A_DATA32_DESC
call InitDescItem
mov esi, TASK_A_STACK32_SEGMENT
mov edi, TASK_A_STACK32_DESC
call InitDescItem
mov esi, FUNCTION_SEGMENT
mov edi, FUNCTION_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
BACK_ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
in al, 0x92
and al, 11111101b
out 0x92, al
sti
mov bp, HELLOWORLD
mov cx, 13
mov dx, 0
mov ax, 0x1301
mov bx, 0007
int 0x10
jmp $
; 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 FunctionSelector : PrintStringOffset
mov ebp, HELLOWORLD_Offset
mov bx, 0x0c
mov dh, 14
mov dl, 30
call FunctionSelector : PrintStringOffset
mov ax, TaskALdtSelector
lldt ax
jmp TaskACode32Selector : 0
; jmp Code16Selector : 0
Code32SegLen equ $ - CODE32_SEGMENT
[section .s16]
[bits 16]
CODE16_SEGMENT:
mov ax, UpdateSelector
mov ds, ax
mov es, ax
mov ss, ax
mov gs, ax
mov fs, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
BACK_TO_REAL_MODE:
jmp 0 : BACK_ENTRY_SEGMENT
Code16SegLen equ $ - CODE16_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
[section .func]
[bits 32]
FUNCTION_SEGMENT:
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintStringFunc:
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
retf
PrintStringOffset equ PrintStringFunc - $$
FunctionSegLen equ $ - FUNCTION_SEGMENT
; ===================================================
;
; TASK A CODE SEGMENT
;
; ===================================================
;Task A LDT definition
; 段基址 段界限 段属性
TASK_A_LDT_ENTRY:
TASK_A_CODE32_DESC Descriptor 0, TaskACode32SegLen - 1, DA_32 + DA_C
TASK_A_DATA32_DESC Descriptor 0, TaskAData32SegLen - 1, DA_32 + DA_DR
TASK_A_STACK32_DESC Descriptor 0, TaskAStack32SegLen - 1, DA_32 + DA_DRW
TaskALdtLen equ $ - TASK_A_LDT_ENTRY
;Task A LDT Selector
TaskACode32Selector equ (0x0000 << 3) + SA_TIL + SA_RPL0
TaskAData32Selector equ (0x0001 << 3) + SA_TIL + SA_RPL0
TaskAStack32Selector equ (0x0002 << 3) + SA_TIL + SA_RPL0
[section .task-a-data]
[bits 32]
TASK_A_DATA32_SEGMENT:
TaskAString db "This is Task A!", 0
TaskAStringOffset equ TaskAString - $$
TaskAData32SegLen equ $ - TASK_A_DATA32_SEGMENT
[section .task-a-gs]
[bits 32]
TASK_A_STACK32_SEGMENT:
times 1024 db 0
TaskAStack32SegLen equ $ - TASK_A_STACK32_SEGMENT
TaskATopOfStack32 equ TaskAStack32SegLen - 1
[section .task-a-s32]
[bits 32]
TASK_A_CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, TaskAData32Selector
mov ds, ax
mov ax, TaskAStack32Selector
mov ss, ax
mov esp, TaskATopOfStack32
mov ebp, TaskAStringOffset
mov bx, 0x0c
mov dh, 15
mov dl, 30
call FunctionSelector : PrintStringOffset
jmp Code16Selector : 0
TaskACode32SegLen equ $ - TASK_A_CODE32_SEGMENT