引言
- 在 【局部段描述符表的使用】 的最后,我们留下了一个问题,为了解决这个问题,于是引出了调用门
- 当然,关于实现不同代码段之间的代码复用功能(调用同一个函数),不光只有调用门这一种方案
- 这里讲调用门不光是因为其可以实现不同代码段之间的代码复用,而且其能在不同特权级的代码段之间跳转执行。关于特权级跳转,下一章节再详细说明
- 在这一章节,我们只需要认识调用门,不要过多的深入到其它地方
门描述符
- 前面章节我们学习了多种段描述符,今天我们来学习一种新的描述符:门描述符
- 根据应用场景的不同,门描述符可分为
- 调用门
- 中断门
- 陷阱门
- 任务门
调用门描述符格式
- 门描述符同段描述符类似,都是 8 字节大小的数据结构,现在,我们只看调用门描述符格式
- 调用门中的段选择符字段指定要访问的代码段
- 偏移值字段指定段中入口点。这个入口点通常是指定过程的第一条指令
- DPL 字段指定调用门的特权级,从而指定通过调用门访问特定过程所要求的特权级
- 标志 P 指明调用门描述符是否有效
- 参数个数字段(Param Count)指明在发生堆栈切换时从调用者堆栈复 制到新堆栈中的参数个数
调用门执行过程
- 调用门执行过程:当处理器访问调用门时,它会使用调用门中的段选择符来定位目的代码段的段描述符。然后 CPU 会把代码段描述符的基地址与调用门中的偏移值进行组合,形成代码段中指定程序入口点的线性地址。
代码实现
- 接下来我们使用调用门来实现不同代码段之间打印函数复用功能
- 先上完整代码:loader.asm
- 参照段描述符宏定义的方式,给门描述符也做个宏定义
; 门描述符定义 %macro Gate 4 ; 有四个参数:选择子、偏移地址、参数个数、属性 dw (%2 & 0xFFFF) ; 偏移地址1 dw %1 ; 选择子 dw (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; 属性 dw ((%2 >> 16) & 0xFFFF) ; 偏移地址2 %endmacro
- 首先我们把打印函数单独放在一个专门的代码段中,注意,不同段之间的函数返回必须用 retf ,不能用 ret
FUNCTION_SEGMENT: print_str_32: ... 函数主体 ... retf printOffset equ print_str_32 - FUNCTION_SEGMENT FUNC_SEG_LEN equ $-FUNCTION_SEGMENT
- 那么对应的就要有一个段描述符和选择符
; 段描述符 FUNC_DESC : Descriptor 0, FUNC_SEG_LEN-1, DA_C + DA_32 ; 段选择符 FUNC_SELECTOR equ (0x0006 << 3) + SA_RPL0 + SA_TIG
; 段选择符
FUNC_SELECTOR equ (0x0006 << 3) + SA_RPL0 + SA_TIG
- 填充段基址
mov esi, FUNCTION_SEGMENT
mov edi, FUNC_DESC
call InitDescItem
- 以上操作与之前完全相同
- 接下来还需要定义门描述符和对应的选择符
; 门描述符 FUNC_PRINT_DESC : Gate FUNC_SELECTOR, printOffset, 0, DA_CALL_GATE ; 选择符 FUNC_PRINT_SELECTOR equ (0x0007 << 3) + SA_RPL0 + SA_TIG
- 最后,函数调用也变为:
; call print_str_32 call FUNC_PRINT_SELECTOR : 0 ; call TaskAPrintString call FUNC_PRINT_SELECTOR : 0
- 也可以不使用调用门实现不同代码段的函数复用,其格式为:call 选择子 : 偏移地址
; call print_str_32 ; call FUNC_PRINT_SELECTOR : 0 call FUNC_SELECTOR : printOffset ; call TaskAPrintString ; call FUNC_PRINT_SELECTOR : 0 call FUNC_SELECTOR : printOffset
- 运行一下,打印信息跟上一章节一模一样