问题:如何在不同特权级的代码之间跳转执行?
调用门描述符 用于从 低特权级代码段跳转到高特权级代码段 并返回
门描述符也是描述符,结构上同 段描述符一样,也是占用8个字节。
调用门描述符: 调用门中保存了 某个段的选择子以及相应的偏移地址。 地址位一共32位 4个字节,正好是一个偏移地址所需要存储空间。
调用门描述符的工作原理:
只要是描述符,那么必然会对应有一个选择子,既然调用门描述符是一个描述符,那么就会对应一个选择子,即调用门选择子。可以使用 调用门选择子 来访问对应的门描述符, 门描述符中有两个很重要的信息,一个是选择子,一个是偏移地址。选择子指的是段描述符的选择子,所以我们可以通过这个段选择子 访问到一个对应的段描述符,拿到对应的段基地址和段界限,再使用门描述符中的记录的偏移地址,就可以得到一个确定的内存地址,就可以跳转到该地址执行。所以 通过调用门选择子 最终得到一个确定的内存地址,然后跳转到该地址,所以通过调用门选择子可以跳转到某个固定的内存地址上来执行。有点类似于 C语言中 函数指针。
全局段描述附表
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
...
;定义一个专门定义函数的 代码段
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32
调用门描述符
; Gate Descriptor
; Call Gate 选择子, 偏移, 参数个数, 属性
;定义两个门描述符,分别对应 FUNCTION_DESC代码段中两个函数的入口地址
;该调用门描述符中保存 FUNCTION_DESC段的选择子,以及段内偏移地址(指的是该代码段中某个函数的入口地址
FUNC_CG_ADD_DESC Gate FunctionSelector, CG_Add, 0, DA_386CGate
FUNC_CG_SUB_DESC Gate FunctionSelector, CG_Sub, 0, DA_386CGate
; GDT end
;代码段 段描述符选择子
FunctionSelector equ (0x0004 << 3) + SA_TIG + SA_RPL0
;定义两个调用门描述符选择子 分别指向对应的函数地址,类似于C语言中的函数指针,所以通过选择子调用相应的函数
FuncCGAddSelector equ (0x0005 << 3) + SA_TIG + SA_RPL0
FuncCGSubSelector equ (0x0006 << 3) + SA_TIG + SA_RPL0
两个调用门描述符选择子 分别指向对应的函数地址,类似于C语言中的函数指针,所以通过调用门选择子 调用相应的函数
;call Add, Sub function
call FuncCGAddSelector : 0
call FuncCGSubSelector : 0
段内跳转 call jmp 近跳转,参数是相对地址(如 跳转到距离当前位置10个字节的位置执行)
call跳转指令 就是 函数调用时的跳转,他最大的特点是 跳转过去执行之后,还能返回回来
jmp 是无条件跳转,跳转过去之后 不会返回,有去无回
段间跳转 call far, jmp far 远跳转,参数是选择子和偏移地址(即目标段基地址和段内偏移地址)
实验:
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 ; 可用 386 任务状态段类型值
DA_386CGate equ 0x8C ; 386 调用门类型值
DA_386IGate equ 0x8E ; 386 中断门类型值
DA_386TGate equ 0x8F ; 386 陷阱门类型值
; 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 字节
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0xFFFF) ; 偏移地址1
dw %1 ; 选择子
dw (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; 属性
dw ((%2 >> 16) & 0xFFFF) ; 偏移地址2
%endmacro
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
;定义 函数代码段 段描述符,该代码段中都是函数
;可执行的代码段,属性 DA_C + DA_32
FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32
; Gate Descriptor 门描述符
; Call Gate调用门描述符 选择子, 偏移, 参数个数, 属性
;定义两个调用门描述符,分别对应 FUNCTION_DESC 代码段中两个函数的入口地址
;该调用门描述符中保存 FUNCTION_DESC段的选择子,以及段内偏移地址(指的是该代码段中某个函数的入口地址
;属性 是 调用门类型值 DA_386CGate equ 0x8C ; 386 调用门类型值
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
;FUNCTION_DESC函数代码段 段描述符选择子
FunctionSelector equ (0x0004 << 3) + SA_TIG + SA_RPL0
;定义两个调用门描述符选择子 分别指向对应的函数地址,类似于C语言中的函数指针,所以通过选择子调用相应的函数
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
;初始化 段描述符表中的 FUNCTION_DESC函数代码段 的 段描述符的段基址信息
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
;通过选择子调用相应的函数
;说明 这里的0是语法需要 表示是段间跳转 0本身无意义,但是不能删除
;如果删除后 就变成了 call FuncCGAddSelector,成了段内跳转,意义本身发生变化,所以必须留着
call FuncCGAddSelector : 0
call FuncCGSubSelector : 0
;函数指针的本质就是函数入口地址,所以也可以这样调用函数:
;原理就是直接使用函数地址(段基址+段内偏移地址)
;call FunctionSelector : CG_Add
;call FunctionSelector : CG_Sub
jmp $
Code32SegLen equ $ - CODE32_SEGMENT
;定义一个32位代码段,里面全部是函数
[section .func]
[bits 32]
FUNCTION_SEGMENT:
; ax --> a
; bx --> b
;
; return:
; cx --> a + b
AddFunc:
mov cx, ax
add cx, bx
;返回指令 retf (ret far 远调用 远返回)
retf
;函数 AddFunc 段内偏移
CG_Add equ AddFunc - $$
; ax --> a
; bx --> b
;
; return:
; cx --> a - b
SubFunc:
mov cx, ax
sub cx, bx
;返回指令 retf (ret far 远调用 远返回)
retf
;函数SubFunc 段内偏移
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
调用门 在使用上 就是函数指针,本质就是函数指针,但是调用门 不仅仅只是为了 实现段间跳转,实现函数调用,另一个关键的作用是实现不同特权级代码之间的跳转所以 使用前面 直接使用选择子+段内偏移地址 调用函数的方式 并不是完善的 因为这样不能实现不同特权级代码之间的跳转
;通过选择子调用相应的函数
;说明 这里的0是语法需要 表示是段间跳转 0本身无意义,但是不能删除
;如果删除后 就变成了 call FuncCGAddSelector,成了段内跳转,意义法身变化,所以必须留着
call FuncCGAddSelector : 0
call FuncCGSubSelector : 0
;函数指针的本质就是函数入口地址,所以也可以这样调用函数:
;原理就是直接使用函数地址(段基址+段内偏移地址),但是不能实现不同特权级代码之间的跳转
;call FunctionSelector : CG_Add
;call FunctionSelector : CG_Sub