保护模式小结:
-使用选择子访问段描述符表时,索引值的合法性检测,这个检测是处理器做的
当索引值越界时,引发异常
判断规则:索引值*8 + 7 <= 段描述表界限值
段描述表界限值就是段描述表中的地址最大值
-内存段类型合法性检测,使用选择子给相应的段寄存器赋值的时候会进行这个合法性检测
具备可执行属性的段(代码段)才能加载到CS寄存器
具备可写属性的段(数据段)才能加载到SS寄存器,加载到SS寄存器的段是栈段
具备可读属性的段才能加载到DS,ES,FS,GS寄存器
这里列出来的都是最低的条件
-代码段和数据段的保护
处理器每访问一个地址都要确认该地址不超过界限值
判断规则:
代码段:IP + 指令长度 <= 代码段界限
数据段:访问起始地址 + 访问数据长度 <= 数据段界限
段界限是相对于段基址而言的,而不是绝对物理地址
注意:
保护模式中代码中定义的界限值通常为:
最大偏移地址值(相对于段基址)。
实验分析:
正常程序如下:
1 %include "inc.asm" 2 3 org 0x9000 4 5 jmp CODE16_SEGMENT 6 7 [section .gdt] 8 ; GDT definition 9 ; 段基址, 段界限, 段属性 10 GDT_ENTRY : Descriptor 0, 0, 0 11 CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 12 VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 13 DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32 14 STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32 15 ; GDT end 16 17 GdtLen equ $ - GDT_ENTRY 18 19 GdtPtr: 20 dw GdtLen - 1 21 dd 0 22 23 24 ; GDT Selector 25 26 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 27 VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0 28 Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0 29 Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0 30 31 ; end of [section .gdt] 32 33 TopOfStack16 equ 0x7c00 34 35 [section .dat] 36 [bits 32] 37 DATA32_SEGMENT: 38 DTOS db "D.T.OS!", 0 39 DTOS_OFFSET equ DTOS - $$ 40 HELLO_WORLD db "Hello World!", 0 41 HELLO_WORLD_OFFSET equ HELLO_WORLD - $$ 42 43 Data32SegLen equ $ - DATA32_SEGMENT 44 45 [section .s16] 46 [bits 16] 47 CODE16_SEGMENT: 48 mov ax, cs 49 mov ds, ax 50 mov es, ax 51 mov ss, ax 52 mov sp, TopOfStack16 53 54 ; initialize GDT for 32 bits code segment 55 mov esi, CODE32_SEGMENT 56 mov edi, CODE32_DESC 57 58 call InitDescItem 59 60 mov esi, DATA32_SEGMENT 61 mov edi, DATA32_DESC 62 63 call InitDescItem 64 65 mov esi, DATA32_SEGMENT 66 mov edi, STACK32_DESC 67 68 call InitDescItem 69 70 ; initialize GDT pointer struct 71 mov eax, 0 72 mov ax, ds 73 shl eax, 4 74 add eax, GDT_ENTRY 75 mov dword [GdtPtr + 2], eax 76 77 ; 1. load GDT 78 lgdt [GdtPtr] 79 80 ; 2. close interrupt 81 cli 82 83 ; 3. open A20 84 in al, 0x92 85 or al, 00000010b 86 out 0x92, al 87 88 ; 4. enter protect mode 89 mov eax, cr0 90 or eax, 0x01 91 mov cr0, eax 92 93 ; 5. jump to 32 bits code 94 jmp dword Code32Selector : 0 95 96 97 ; esi --> code segment label 98 ; edi --> descriptor label 99 InitDescItem: 100 push eax 101 102 mov eax, 0 103 mov ax, cs 104 shl eax, 4 105 add eax, esi 106 mov word [edi + 2], ax 107 shr eax, 16 108 mov byte [edi + 4], al 109 mov byte [edi + 7], ah 110 111 pop eax 112 113 ret 114 115 116 [section .s32] 117 [bits 32] 118 CODE32_SEGMENT: 119 mov ax, VideoSelector 120 mov gs, ax 121 122 mov ax, Stack32Selector 123 mov ss, ax 124 125 mov eax, TopOfStack32 126 mov esp, eax 127 128 mov ax, Data32Selector 129 mov ds, ax 130 131 mov ebp, DTOS_OFFSET 132 mov bx, 0x0C 133 mov dh, 12 134 mov dl, 33 135 136 call PrintString 137 138 mov ebp, HELLO_WORLD_OFFSET 139 mov bx, 0x0C 140 mov dh, 13 141 mov dl, 30 142 143 call PrintString 144 145 jmp $ 146 147 ; ds:ebp --> string address 148 ; bx --> attribute 149 ; dx --> dh : row, dl : col 150 PrintString: 151 push ebp 152 push eax 153 push edi 154 push cx 155 push dx 156 157 print: 158 mov cl, [ds:ebp] 159 cmp cl, 0 160 je end 161 mov eax, 80 162 mul dh 163 add al, dl 164 shl eax, 1 165 mov edi, eax 166 mov ah, bl 167 mov al, cl 168 mov [gs:edi], ax 169 inc ebp 170 inc dl 171 jmp print 172 173 end: 174 pop dx 175 pop cx 176 pop edi 177 pop eax 178 pop ebp 179 180 ret 181 182 Code32SegLen equ $ - CODE32_SEGMENT 183 184 [section .gs] 185 [bits 32] 186 STACK32_SEGMENT: 187 times 1024 * 4 db 0 188 189 Stack32SegLen equ $ - STACK32_SEGMENT 190 TopOfStack32 equ Stack32SegLen - 1
运行结果如下:
修改程序如下:
1 %include "inc.asm" 2 3 org 0x9000 4 5 jmp CODE16_SEGMENT 6 7 [section .gdt] 8 ; GDT definition 9 ; 段基址, 段界限, 段属性 10 GDT_ENTRY : Descriptor 0, 0, 0 11 CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 12 VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 13 DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32 14 STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32 15 ; GDT end 16 17 GdtLen equ $ - GDT_ENTRY 18 19 GdtPtr: 20 dw GdtLen - 1 21 dd 0 22 23 24 ; GDT Selector 25 26 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 27 VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0 28 Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0 29 Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0 30 ExceptionSelector equ (0x0005 << 3) + SA_TIG + SA_RPL0 31 32 ; end of [section .gdt] 33 34 TopOfStack16 equ 0x7c00 35 36 [section .dat] 37 [bits 32] 38 DATA32_SEGMENT: 39 DTOS db "D.T.OS!", 0 40 DTOS_OFFSET equ DTOS - $$ 41 HELLO_WORLD db "Hello World!", 0 42 HELLO_WORLD_OFFSET equ HELLO_WORLD - $$ 43 44 Data32SegLen equ $ - DATA32_SEGMENT 45 46 [section .s16] 47 [bits 16] 48 CODE16_SEGMENT: 49 mov ax, cs 50 mov ds, ax 51 mov es, ax 52 mov ss, ax 53 mov sp, TopOfStack16 54 55 ; initialize GDT for 32 bits code segment 56 mov esi, CODE32_SEGMENT 57 mov edi, CODE32_DESC 58 59 call InitDescItem 60 61 mov esi, DATA32_SEGMENT 62 mov edi, DATA32_DESC 63 64 call InitDescItem 65 66 mov esi, DATA32_SEGMENT 67 mov edi, STACK32_DESC 68 69 call InitDescItem 70 71 ; initialize GDT pointer struct 72 mov eax, 0 73 mov ax, ds 74 shl eax, 4 75 add eax, GDT_ENTRY 76 mov dword [GdtPtr + 2], eax 77 78 ; 1. load GDT 79 lgdt [GdtPtr] 80 81 ; 2. close interrupt 82 cli 83 84 ; 3. open A20 85 in al, 0x92 86 or al, 00000010b 87 out 0x92, al 88 89 ; 4. enter protect mode 90 mov eax, cr0 91 or eax, 0x01 92 mov cr0, eax 93 94 ; 5. jump to 32 bits code 95 jmp dword ExceptionSelector : 0 96 97 98 ; esi --> code segment label 99 ; edi --> descriptor label 100 InitDescItem: 101 push eax 102 103 mov eax, 0 104 mov ax, cs 105 shl eax, 4 106 add eax, esi 107 mov word [edi + 2], ax 108 shr eax, 16 109 mov byte [edi + 4], al 110 mov byte [edi + 7], ah 111 112 pop eax 113 114 ret 115 116 117 [section .s32] 118 [bits 32] 119 CODE32_SEGMENT: 120 mov ax, VideoSelector 121 mov gs, ax 122 123 mov ax, Stack32Selector 124 mov ss, ax 125 126 mov eax, TopOfStack32 127 mov esp, eax 128 129 mov ax, Data32Selector 130 mov ds, ax 131 132 mov ebp, DTOS_OFFSET 133 mov bx, 0x0C 134 mov dh, 12 135 mov dl, 33 136 137 call PrintString 138 139 mov ebp, HELLO_WORLD_OFFSET 140 mov bx, 0x0C 141 mov dh, 13 142 mov dl, 30 143 144 call PrintString 145 146 jmp $ 147 148 ; ds:ebp --> string address 149 ; bx --> attribute 150 ; dx --> dh : row, dl : col 151 PrintString: 152 push ebp 153 push eax 154 push edi 155 push cx 156 push dx 157 158 print: 159 mov cl, [ds:ebp] 160 cmp cl, 0 161 je end 162 mov eax, 80 163 mul dh 164 add al, dl 165 shl eax, 1 166 mov edi, eax 167 mov ah, bl 168 mov al, cl 169 mov [gs:edi], ax 170 inc ebp 171 inc dl 172 jmp print 173 174 end: 175 pop dx 176 pop cx 177 pop edi 178 pop eax 179 pop ebp 180 181 ret 182 183 Code32SegLen equ $ - CODE32_SEGMENT 184 185 [section .gs] 186 [bits 32] 187 STACK32_SEGMENT: 188 times 1024 * 4 db 0 189 190 Stack32SegLen equ $ - STACK32_SEGMENT 191 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才能成功访问数据