https://www.cnblogs.com/wanmeishenghuo/tag/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/
https://blog.51cto.com/13475106/category6.html
问题:
如何在不同特权级的代码段之间跳转执行?
一种新的描述符:门描述符(Gate Descriptor)
通过门描述符在不同的特权级的代码间进行跳转
根据应用场景的不同,门描述符分为:
调用门(Call Gate)
中断门(Interrupt Gate)
陷阱门(Trap Gate)
任务门(Task Gate)
门描述符的一个功能就是可以在不同特权级的代码间进行跳转。
门描述符的内存结构:
每一个门描述符占用8字节内存
不同类型门描述符的内存含义不同
汇编小贴士:
汇编语言中的跳转方式
段内跳转(近跳转):call , jmp,call调用还可以返回回来,jmp是无条件跳转,无法返回来
参数为相对地址,函数调用时只需要保存当前偏移地址
近跳转就是段内的跳转,例如在段内定义了一个函数,就可以直接使用call来调用这个函数了,只保存当前偏移地址,例如,跳转到离当前10个字节的地址处执行,就只需将10保存下来,然后跳转执行,执行完之后将10拿出来,向回退10个字节就可以了。
段间跳转(远跳转):call far, jmp far
参数为选择子和偏移地址
函数调用时需要同时保存段基地址和偏移地址
远跳转就是从当前段跳转到另一个段
例如:段间远跳转的call far,将当前段的基地址即选择子保存下来,将当前段的偏移地址也保存下来。就是保存CS和IP的值。
调用门实验:
inc.asm
; Segment Attribute
DA_32 equ 0x4000
DA_DR equ 0x90
DA_DRW equ 0x92
DA_DRWA equ 0x93
DA_C equ 0x98
DA_CR equ 0x9A
DA_CCO equ 0x9C
DA_CCOR equ 0x9E
; Segment Privilege
DA_DPL0 equ 0x00 ; DPL = 0
DA_DPL1 equ 0x20 ; DPL = 1
DA_DPL2 equ 0x40 ; DPL = 2
DA_DPL3 equ 0x60 ; DPL = 3
; Special Attribute
DA_LDT equ 0x82
DA_TaskGate equ 0x85 ;
DA_386TSS equ 0x89 ;
DA_386CGate equ 0x8C ;
DA_386IGate equ 0x8E ;
DA_386tgATE equ 0x8F ;
; Selector Attribute
SA_RPL0 equ 0
SA_RPL1 equ 1
SA_RPL2 equ 2
SA_RPL3 equ 3
SA_TIG equ 0
SA_TIL equ 4
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3 ; 段基址, 段界限, 段属性
dw %2 & 0xFFFF ; 段界限1
dw %1 & 0xFFFF ; 段基址1
db (%1 >> 16) & 0xFF ; 段基址2
dw ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0xFF ; 段基址3
%endmacro ; 共 8 字节
;Gate
; usage : Gate Selector, Offset, DCount, Attr
; Selector : dw
; Offset : dd
; DCount : db
; Attr : db
%macro Gate 4
dw (%2 & 0xFFFF) ; pianyidizhi1
dw %1 ; xuanzezi
dw (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; shu xing
dw ((%2 >> 16) & 0xFFFF) ; pianyidizhi2
%endmacro
17-23行和53-58行是我们新添加的门相关的定义。
loader.asm如下:
%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_C + DA_32
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32
STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32
; Gate Descriptor
; Call Gate xuanzezi pianyi canshugeshu shuxing
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]
TopOfStack16 equ 0x7c00
[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, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
mov esi, FUNCTION_SEGMENT
mov edi, FUNCTION_DESC
call InitDescItem
; initialize 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, 2
mov bx, 1
call FuncCGAddSelector : 0
jmp $
Code32SegLen equ $ - CODE32_SEGMENT
[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
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
我们新定义了一个func段,在里面定义了AddFunc和SubFunc函数。第14行将func段的描述符加入了全局段描述符表。
17、18行将两个门描述符加入了全局段描述符表。
33-35行分别是这几个新添加的段描述符的选择子。
下面我们单步执行:
首先使用ndisasm -o 0x9000 loader > loader.txt将可执行程序进行反汇编,结果如下:

我们重点关注跳转第58行的跳转这里。
打断点执行:

将ax,bx赋值准备跳转,这时的cx值如下:

执行函数,cx的值有变化:

因为只是加的最低位,所以高位的那个9我们不用关心。
通过以下的调用方式我们可以得到同样的结果:

126、127行直接使用段基址加段内偏移的方式调用。
将使用调用门的方式截图如下:

126、127现在是使用调用门的方式,冒号后面的0在这里没有什么意义,只是为了语法需要,如果去掉冒号和0,编译器会认为这是一个段内的函数调用。
使用调用门选择子时,程序会根据选择子找到描述符,然后根据描述符中的选择子和偏移再去调用函数。这个调用门选择子在这里相当于一个函数指针。
实验结论:
门描述符是一种特殊的描述符,需要注册于段描述符表
调用门可以看做一个函数指针(保存具体函数的入口地址)
通过调用门选择子对相应的函数进行远调用(call far)
可以直接使用 选择子:偏移地址 的方式调用其他段的函数
使用调用门时偏移地址无意义,仅仅是语法需要
历史遗留问题:
保护模式下的不同段之间如何进行代码复用(如:调用同一个函数)?
解决方案:
将不同代码段中需要复用的函数定义到独立的段中(retf, f是far的意思)
计算每一个可复用函数的偏移量(FuncName - $$)
通过 段选择子:偏移地址 的方式对目标函数进行远调用
对程序进行重构:
%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_C + DA_32
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32
STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32
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_C + DA_32
; 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 - $$
HELLO_WORLD db "Hello World!", 0
HELLO_WORLD_OFFSET equ HELLO_WORLD - $$
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, DATA32_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
; initialize 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, HELLO_WORLD
mov cx, 12
mov dx, 0
mov ax, 0x1301
mov bx, 0x0007
int 0x10
jmp $
; 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 .16]
[bits 16]
CODE16_SEGMENT:
mov ax, UpdateSelector
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
BACK_TO_REAL_MODE:
jmp 0 : BACK_ENTRY_SEGMENT
Code16SegLen equ $ - CODE16_SEGMENT
[section .func]
[bits 32]
FUNCTION_SEGMENT:
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintStringFunc:
push ebp
push eax
push edi
push cx
push dx
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 dx
pop cx
pop edi
pop eax
pop ebp
retf
PrintString equ PrintStringFunc - $$
FunctionSegLen equ $ - FUNCTION_SEGMENT
[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, 12
mov dl, 33
call FunctionSelector : PrintString
mov ebp, HELLO_WORLD_OFFSET
mov bx, 0x0C
mov dh, 13
mov dl, 30
call FunctionSelector : PrintString
mov ax, TaskALdtSelector
lldt ax
jmp TaskACode32Selector : 0
;jmp Code16Selector : 0
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1014 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
; ==================================
;
; Task A Code Segment
;
;===================================
[section .task-a-ldt]
; Task A LDT definition
; 段基址 段界限 段属性
TASK_A_LDT_ENTRY:
TASK_A_CODE32_DESC : Descriptor 0, TaskACode32SegLen - 1, DA_C + DA_32
TASK_A_DATA32_DESC : Descriptor 0, TaskAData32SegLen - 1, DA_DR + DA_32
TASK_A_STACK32_DESC : Descriptor 0, TaskAStack32SegLen - 1, DA_DRW + DA_32
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-dat]
[bits 32]
TASK_A_DATA32_SEGMENT:
TASK_A_STRING db "This is Task A", 0
TASK_A_STRING_OFFSET equ TASK_A_STRING - $$
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, TaskAStack32Selector
mov ss, ax
mov eax, TaskATopOfStack32
mov esp, eax
mov ax, TaskAData32Selector
mov ds, ax
mov ebp, TASK_A_STRING_OFFSET
mov bx, 0x0c
mov dh, 14
mov dl, 29
call FunctionSelector : PrintString
jmp Code16Selector : 0
TaskACode32SegLen equ $ - TASK_A_CODE32_SEGMENT
运行结果如下:

小结:
门描述符是一种特殊的描述符,需要注册于段描述符表
门描述符分为:调用门、中断门、陷阱门、任务门
调用门可以看做一个函数指针(保存具体函数的入口地址)
调用门选择子对应的函数调用方式为远调用(call far)
引入了们描述符,但是仍然没有解答的问题:
