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   lh03061238  阅读(132)  评论(0编辑  收藏  举报

编辑推荐:
· 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)

导航

< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5
点击右上角即可分享
微信分享提示