15 保护模式中的特权级(上)
参考
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
保护模式小结:
-使用选择子访问段描述符表时,索引值的合法性检测,这个检测是处理器做的
当索引值越界时,引发异常
判断规则:索引值*8 + 7 <= 段描述表界限值
段描述表界限值就是段描述表中的地址最大值
-内存段类型合法性检测,使用选择子给相应的段寄存器赋值的时候会进行这个合法性检测
具备可执行属性的段(代码段)才能加载到CS寄存器
具备可写属性的段(数据段)才能加载到SS寄存器,加载到SS寄存器的段是栈段
具备可读属性的段才能加载到DS,ES,FS,GS寄存器
这里列出来的都是最低的条件
-代码段和数据段的保护
处理器每访问一个地址都要确认该地址不超过界限值
判断规则:
代码段:IP + 指令长度 <= 代码段界限
数据段:访问起始地址 + 访问数据长度 <= 数据段界限
段界限是相对于段基址而言的,而不是绝对物理地址
注意:
保护模式中代码中定义的界限值通常为:
最大偏移地址值(相对于段基址)。
实验分析:
正常程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | %include "inc.asm" org 0x9000 jmp CODE16_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 ; 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 ; 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] CODE16_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, DATA32_SEGMENT mov edi, DATA32_DESC call InitDescItem mov esi, DATA32_SEGMENT mov edi, STACK32_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, Data32Selector mov ds, ax mov ebp, DTOS_OFFSET mov bx, 0x0C mov dh, 12 mov dl, 33 call PrintString mov ebp, HELLO_WORLD_OFFSET mov bx, 0x0C mov dh, 13 mov dl, 30 call PrintString jmp $ ; ds:ebp --> string address ; bx --> attribute ; dx --> dh : row, dl : col PrintString: 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 ret Code32SegLen equ $ - CODE32_SEGMENT [section .gs] [bits 32] STACK32_SEGMENT: times 1024 * 4 db 0 Stack32SegLen equ $ - STACK32_SEGMENT TopOfStack32 equ Stack32SegLen - 1 |
运行结果如下:
修改程序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | %include "inc.asm" org 0x9000 jmp CODE16_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 ; 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 ExceptionSelector equ (0x0005 << 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] CODE16_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, DATA32_SEGMENT mov edi, DATA32_DESC call InitDescItem mov esi, DATA32_SEGMENT mov edi, STACK32_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 ExceptionSelector : 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, Data32Selector mov ds, ax mov ebp, DTOS_OFFSET mov bx, 0x0C mov dh, 12 mov dl, 33 call PrintString mov ebp, HELLO_WORLD_OFFSET mov bx, 0x0C mov dh, 13 mov dl, 30 call PrintString jmp $ ; ds:ebp --> string address ; bx --> attribute ; dx --> dh : row, dl : col PrintString: 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 ret Code32SegLen equ $ - CODE32_SEGMENT [section .gs] [bits 32] STACK32_SEGMENT: times 1024 * 4 db 0 Stack32SegLen equ $ - STACK32_SEGMENT TopOfStack32 equ Stack32SegLen - 1 |
我们在第30行新添加了一个段选择子,选择子号是5,而在第8-14行的段描述符表中没有为5的描述符。第95行跳转时我们使用新添加的段选择子,启动bochs,实验结果如下:
可以看出CPU复位了,因为选择子对用的描述符越界了。
下面进行属性的实验:
在正常的程序基础上进行修改:
1、修改代码段属性
将第11行的代码段的可执行属性改成可读属性,程序运行崩溃,运行结果如下:
2、修改栈段属性,将可写属性去掉,只留下可读属性,程序崩溃,如下:
可以看到程序在执行第123行的mov ss,ax是崩溃的,因为这时候栈段的属性没有可写了,所以会出错。
3、修改数据段属性,将可读写属性改为可执行属性
运行结果如下:
可以看到在第129行出错,mov ds,ax导致程序崩溃。
下面做界限值的实验:
1、代码段界限修改
将第11行的代码段界限改小一个字节,运行结果如下:
程序崩溃了,是在执行180行的ret时崩溃的,因为这时候的代码已经超出了界限。
2、修改数据段界限
将第13行的数据段界限改小1个字节,运行结果如下:
这是在执行第158行的打印时崩溃了,当我们打印字符串的最后一个0时,超出了数据段的界限。
问题:
保护模式除了利用段界限对内存访问进行保护,是否还提供其他的保护机制?
保护模式中的特权级:
- X86架构中的保护模式提供了四个特权级(0,1,2,3)
- 特权级从高到低分别是0,1,2,3(数字越大特权级越低)
linux和window只用到0和3级,即内核态和用户态。
特权级的表现形式:
CPL(Current Privilege Level)
当前可执行代码段的特权级,由CS寄存器最低2位定义,CS保存的段选择子
DPL(Descriptor Privilege Level)
内存段的特权级,在段描述符表中定义
RPL(Request Privilege Level)
选择子的特权级,由选择子最低2位定义
段描述符中的DPL用于标识内存段的特权级,可执行代码访问内存段时必须满足一定的特权级(CPL),否则,处理器将产生异常
可执行代码访问数据段的情形:
应用程序访问内核的数据,会崩溃,因为内核的数据的DPL是0,应用程序的CPL是3。CPL应该小于等于DPL才可以访问。
CPL和DPL的关系:代码段之间的跳转
保护模式中每一个代码段都定义了一个DPL
当处理器从A代码段成功跳转到B代码段执行
跳转之前:CPL = DPLa
跳转之后:CPL = DPLb
保护模式中,每一个数据段都定义了一个DPL
当处理器执行过程中需要访问数据段时:
CPL <= DPLdata
CPL和DPL实验:
首先在inc.asm文件中定义特权级
1、修改代码段的DPL
11行将代码段的DPL设置为3,执行结果如下:
程序崩溃了,提示DPL不等于CPL。
程序进入保护模式后默认的CPL为0,而我们的32位代码段DPL为3。
第88-91行的程序执行完就进入了保护模式,这时的CPL默认为0,在第94行跳转时,从CPL为0向DPL为3跳转就发生了异常。
运行结果告诉我们从CPL为0的代码段不能跳转到DPL位3的代码段。
将DPL改为0,运行结果如下:
程序正常运行了,这时候CPL等于DPL。从CPL为0的代码段可以跳转到DPL位0的代码段。
2、修改数据段的DPL
程序依然能正常运行,这说明程序在访问数据时,只要CPL小于等于相应数据段的DPL即可。
RPL实验:
1、修改代码段选择子的RPL
运行结果如下:
提示我们RPL > CPL,可见在代码段跳转时,RPL也参与了合法性判断。
将代码段RPL改回0即可正常运行。
2、修改数据段的RPL
28行将RPL改为3,运行结果:
再次修改28行的RPL:
运行结果如下:
可见访问数据段时RPL并没有参与合法性判断。
3、栈段的RPL实验
第14行的栈的DPL改为3,第29行栈的RPL改为3,运行结果如下:
将上述两个特权级都改为0,如下:
结果如下:
都改为0后程序就可以正常运行了。
可见,栈段和数据段的特权级判断使用的规则并不一样。
实验结论:
处理器进入保护模式后CPL = 0(最高特权级)
处理器不能从高特权级转换到低特权级执行
选择子RPL大于对应段描述符的DPL时,产生异常
处理器进入保护模式后默认特权级为0,我们必须将它降下来,这样才能从高特权级跳转到低特权级代码执行。从低到高跳转也需要改变特权级。
小结:
保护模式对内存的访问范围有严格定义
保护模式定义了内存段的特权级(0,1,2,3)
每个内存段都有固定的特权级(DPL)
不同代码段之间成功跳转后CPL可能发生改变
CPL小于或等于数据段DPL才能成功访问数据
后续需要解决的疑问:
posted on 2020-12-10 10:54 lh03061238 阅读(132) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)