1. 程式人生 > >第16課 - 保護模式中的特權級(中)

第16課 - 保護模式中的特權級(中)

pro 啟用 add 應用場景 ima 不同 http == sas

一種新的描述符:門描述符(Gate Descriptor)

通過門描述符在不同特權級的代碼間進行跳轉 根據應用場景的不同,門描述符分為: 調用門(Call Gates) 中斷門(Interrupt Gates) 陷阱門(Trap Gate) 任務門(Task Gate)

門描述符的內存結構

每一個門描述符占用8字節內存 不同類項門描述的內存含義不同 技術分享圖片技術分享圖片

調用門描述符(Call Gates)的定義

技術分享圖片技術分享圖片

調用門描述符的工作原理

技術分享圖片技術分享圖片

調用門描述符的使用

技術分享圖片技術分享圖片 技術分享圖片技術分享圖片

匯編小貼士

匯編語言中的跳轉方式 段內跳轉:call,jmp
參數為響度地址,函數調用時只需要保存當前偏移地址 段間跳轉:call far, jmp far 參數為選擇子和偏移地址 函數調用時需要同時保存段基地址和偏移地址

實驗結論

門描述符是一種特殊的描述符需要註冊於段描述符表 調用門可以看作一個函數指針保存具體函數的入口地址通過調用門選擇子對相應的函數進行遠調用call far) 可以直接使用選擇子:偏移地址的方式調用其它段的函數 使用調用門時偏移地址無意義,僅僅是語法需要

如何復用14課的PrintString函數?

將不同代碼段需要復用的函數定義到獨立的段中(retf) 計算每一個可復用函數的偏移量(FuncName - $$) 通過段選擇子:偏移地址的方式對目標函數進行遠調用

小結

門描述符是一種特殊的描述符,需要註冊於段描述符表 門描述符分為:調用門,中斷門,陷阱門,任務門 調用門可以看作一個函數指針保存具體函數的入口地址) 調用門選擇子對應的函數調用方式為遠調用(call far)

代碼

// inc.asm
; Segment Attribute     ; 一致性是指高特權不能訪問低特權,但是低特權能訪問高特權,不過特權級別不變
DA_32    equ    0x4000  ;
保護模式32位段 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 ; GDT SA_TIL equ 4 ; LDT,本質是1,因為後面還有2bit的RPL標誌位 ; 描述符 ; 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 ; 全局段描述符表第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 選擇子 偏移 參數個數 屬性 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 ; 全局描述符起始地址,先初始化為0 ; GDT Selector ; TI:全局、局部 RPL:請求權限級別 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 ; 0x0001==第二個選擇子 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] ; 實模式代碼段(16bit) [bits 16] ; 使用16位編譯 ENTRY_SEGMENT: ; 16位保護模式入口段 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 ; 初始化32位保護模式32位代碼段描述符 mov edi, CODE32_DESC call InitDescItem mov esi, STACK32_SEGMENT ; 初始化32位保護模式32位棧段描述符 mov edi, STACK32_DESC call InitDescItem mov esi, FUNCTION_SEGMENT ; 初始化函數段描述符 mov edi, FUNCTION_DESC call InitDescItem ; initialize GDT pointer struct mov eax, 0 ; 代碼段地址左移4位 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 ; 通過0x92端口開啟A20地址線開關 or al, 00000010b out 0x92, al ; 4. enter protect mode mov eax, cr0 ; 設置cr0寄存器,進入保護模式 or eax, 0x01 mov cr0, eax ; 5. jump to 32 bits code jmp dword Code32Selector : 0 ; 使用jmp跳轉到32位代碼段選擇子的0偏移處 ; esi --> code segment label ; edi --> descriptor label InitDescItem: ; 初始化描述符項目 push eax mov eax, 0 ; 代碼段地址左移4位 mov ax, cs shl eax, 4 ; 實地址=段寄存器地址左移4位+偏移地址 add eax, esi mov word [edi + 2], ax ; 將段基址寫入描述符2個字節(16位寄存器),低32位的16-31bit(偏移2字節) shr eax, 16 ; 移除eax實地址中已經寫入段基址的2字節數據 mov byte [edi + 4], al ; 將段基址寫入描述符1個字節(8位寄存器),高32位的0-7bit(偏移4+0=4字節) mov byte [edi + 7], ah ; 將段基址寫入描述符1個字節(8位寄存器),高32位的24-31bit(偏移4+3=7字節) pop eax ret [section .s32] ; 32位代碼段 [bits 32] ; 使用32位編譯 CODE32_SEGMENT: ; 32位代碼段數據 mov ax, VideoSelector ; 把視頻段選擇子放到gs全局段寄存器 mov gs, ax mov ax, Stack32Selector mov ss, ax mov eax, TopOfStack32 mov esp, eax mov ax, 2 mov bx, 1 call FuncCGAddSelector : 0 ; call FunctionSelector : CG_Add ;選擇子:偏移量 call FuncCGSubSelector : 0 ; call FunctionSelector : CG_Sub 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: ; 32位棧段定義 times 1024 * 4 db 0 Stack32SegLen equ $ - STACK32_SEGMENT TopOfStack32 equ Stack32SegLen - 1 // loader.asm 修改復用的PrintString(loader.asm - Lesson 14) %include "inc.asm" org 0x9000 jmp ENTRY_SEGMENT [section .gdt] ; 全局描述符表,部分段基址暫未知地址需使用時再調節 ; GDT definition ; "函數名" 段基址 段界限 段屬性 GDT_ENTRY : Descriptor 0, 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 ; 全局描述符起始地址,先初始化為0 ; GDT Selector ; TI:全局、局部 RPL:請求權限級別 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 ; 0x0001==第二個選擇子 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 ; 局部描述符表選擇子A FunctionSelector equ (0x0008 << 3) + SA_TIG + SA_RPL0 ; 選擇門 ; end of [section .gdt] TopOfStack16 equ 0x7c00 [section .dat] ; 32位數據段 [bits 32] DATA32_SEGMENT: DTOS db "D.T.OS!", 0 ; 註意添加字符串結束標記0 DTOS_OFFSET equ DTOS - $$ HELLO_WORLD db "Hello World!", 0 HELLO_WORLD_OFFSET equ HELLO_WORLD - $$ Data32SegLen equ $ - DATA32_SEGMENT [section .s16] ; 實模式代碼段(16bit) [bits 16] ; 使用16位編譯 ENTRY_SEGMENT: ; 16位保護模式入口段 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 ; 初始化32位保護模式32位代碼段描述符 mov edi, CODE32_DESC call InitDescItem mov esi, DATA32_SEGMENT ; 初始化32位保護模式32位數據段描述符 mov edi, DATA32_DESC call InitDescItem mov esi, STACK32_SEGMENT ; 初始化32位保護模式32位棧段描述符 mov edi, STACK32_DESC call InitDescItem mov esi, CODE16_SEGMENT ; 初始化32位保護模式16位代碼段描述符 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 ; 代碼段地址左移4位 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 ; 通過0x92端口開啟A20地址線開關 or al, 00000010b out 0x92, al ; 4. enter protect mode mov eax, cr0 ; 設置cr0寄存器,進入保護模式 or eax, 0x01 mov cr0, eax ; 5. jump to 32 bits code jmp dword Code32Selector : 0 ; 使用jmp跳轉到32位代碼段選擇子的0偏移處 BACK_ENTRY_SEGMENT: ; 返回入口段(保護模式返回實模式) mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, TopOfStack16 ; 設置16位棧頂指針 in al, 0x92 ; 關閉A20地址線 and al, 11111101b out 0x92, al sti ; 開啟中斷 mov bp, HELLO_WORLD ; 使用實模式打印提示語句,註意bp指向的是字符串而不是段起始地址 mov cx, 12 ; 字符串長度 mov dx, 0 ; 打印於第0行 mov ax, 0x1301 ; 在電傳打字機模式輸出,字符串只含字符,啟用BL屬性 mov bx, 0x0007 ; 打印第0頁,輸出白色前景色 int 0x10 jmp $ ; esi --> code segment label ; edi --> descriptor label InitDescItem: ; 初始化描述符項目 push eax mov eax, 0 ; 代碼段地址左移4位 mov ax, cs shl eax, 4 ; 實地址=段寄存器地址左移4位+偏移地址 add eax, esi mov word [edi + 2], ax ; 將段基址寫入描述符2個字節(16位寄存器),低32位的16-31bit(偏移2字節) shr eax, 16 ; 移除eax實地址中已經寫入段基址的2字節數據 mov byte [edi + 4], al ; 將段基址寫入描述符1個字節(8位寄存器),高32位的0-7bit(偏移4+0=4字節) mov byte [edi + 7], ah ; 將段基址寫入描述符1個字節(8位寄存器),高32位的24-31bit(偏移4+3=7字節) pop eax ret [section .s16] [bits 16] CODE16_SEGMENT: ; 保護模式返回實模式 mov ax, UpdateSelector ; 註意不要操作CS寄存器,因為當前還是保護模式 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] ; cl記錄要打印的字符 cmp cl, 0 ; 對比字符串結束符號 je end mov eax, 80 ; 每行字符數 mul dh ; 乘以行數 add al, dl ; 加上列數,最終計算出要顯示的位置 shl eax, 1 ; 左移乘以2,計算字節偏移 mov edi, eax ; 寫入顯示的偏移地址到edi 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] ; 32位代碼段 [bits 32] ; 使用32位編譯 CODE32_SEGMENT: ; 32位代碼段數據 mov ax, VideoSelector ; 把視頻段選擇子放到gs全局段寄存器 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 ; 指定行地址,註意行列都是從0開始 mov dl, 33 ; 指定列地址 call FunctionSelector : PrintString mov ebp, HELLO_WORLD_OFFSET ; 打印另一個字符串 mov bx, 0x0C mov dh, 13 mov dl, 31 call FunctionSelector : PrintString mov ax, TaskALdtSelector ; 使用局部描述符表 lldt ax jmp TaskACode32Selector : 0 ; jmp Code16Selector : 0 ; 返回到16位實模式 Code32SegLen equ $ - CODE32_SEGMENT [section .gs] [bits 32] STACK32_SEGMENT: ; 32位棧段定義 times 1024 * 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 ; 返回16位保護模式 ; 刪除原有TaskAPrintString函數 TaskACode32SegLen equ $ - TASK_A_CODE32_SEGMENT

輸出

ndisasm -o 0x9000 loader > loader.txt反匯編查看兩個函數的調用情況,可從16位實模式跳轉到32位保護模式的地方開始(包含jmp指令),下圖是采用調用門調用兩個函數的結果 技術分享圖片 采用段選擇子:偏移地址的方式調用PrintString函數 技術分享圖片技術分享圖片

第16課 - 保護模式中的特權級(中)