第16课 - 保护模式中的特权级(中)
Posted dua677
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第16课 - 保护模式中的特权级(中)相关的知识,希望对你有一定的参考价值。
一种新的描述符:门描述符(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课 - 保护模式中的特权级(中)的主要内容,如果未能解决你的问题,请参考以下文章