16 保护模式中的特权级(中)
参考
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)
引入了们描述符,但是仍然没有解答的问题:
posted on 2020-12-12 13:16 lh03061238 阅读(172) 评论(0) 编辑 收藏 举报