16 保护模式中的特权级(中)

参考

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

 

问题:

  如何在不同特权级的代码段之间跳转执行?

 

一种新的描述符:门描述符(Gate Descriptor)

  通过门描述符在不同的特权级的代码间进行跳转

  根据应用场景的不同,门描述符分为:

    调用门(Call Gate)

    中断门(Interrupt  Gate)

    陷阱门(Trap Gate)

    任务门(Task Gate)

门描述符的一个功能就是可以在不同特权级的代码间进行跳转。

 

门描述符的内存结构:

  每一个门描述符占用8字节内存

  不同类型门描述符的内存含义不同

 

 

 

汇编小贴士:

  汇编语言中的跳转方式

    段内跳转(近跳转):call , jmp,call调用还可以返回回来,jmp是无条件跳转,无法返回来

      参数为相对地址,函数调用时只需要保存当前偏移地址

      近跳转就是段内的跳转,例如在段内定义了一个函数,就可以直接使用call来调用这个函数了,只保存当前偏移地址,例如,跳转到离当前10个字节的地址处执行,就只需将10保存下来,然后跳转执行,执行完之后将10拿出来,向回退10个字节就可以了。

    段间跳转(远跳转):call far, jmp  far

      参数为选择子和偏移地址

      函数调用时需要同时保存段基地址和偏移地址

          远跳转就是从当前段跳转到另一个段

      例如:段间远跳转的call far,将当前段的基地址即选择子保存下来,将当前段的偏移地址也保存下来。就是保存CS和IP的值。

 

调用门实验:

inc.asm

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
; Segment Attribute
DA_32    equ    0x4000
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    ;
DA_386CGate        equ        0x8C    ;
DA_386IGate        equ        0x8E    ;
DA_386tgATE        equ        0x8F    ;
 
; Selector Attribute
SA_RPL0    equ    0
SA_RPL1    equ    1
SA_RPL2    equ    2
SA_RPL3    equ    3
 
SA_TIG    equ    0
SA_TIL    equ    4
 
; 描述符
; 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 字节
 
;Gate
; usage : Gate Selector, Offset, DCount, Attr
;         Selector : dw
;          Offset   : dd
;          DCount   : db
;          Attr       : db
%macro Gate 4
    dw        (%2 & 0xFFFF)                        ; pianyidizhi1
    dw        %1                                    ; xuanzezi
    dw        (%3 & 0x1F) | ((%4 << 8) & 0xFF00)    ; shu xing
    dw        ((%2 >> 16) & 0xFFFF)                ; pianyidizhi2
%endmacro

17-23行和53-58行是我们新添加的门相关的定义。

loader.asm如下:

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
%include "inc.asm"
 
org 0x9000
 
jmp ENTRY_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
STACK32_DESC    :     Descriptor    0,      TopOfStack32,      DA_DRW + DA_32
FUNCTION_DESC    :     Descriptor    0,      FunctionSegLen - 1, DA_C + DA_32
; Gate Descriptor
; Call Gate                       xuanzezi            pianyi    canshugeshu      shuxing
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
 
 
; GDT Selector
 
Code32Selector        equ (0x0001 << 3) + SA_TIG + SA_RPL0
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]
[bits 16]
ENTRY_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, STACK32_SEGMENT
    mov edi, STACK32_DESC
 
    call InitDescItem
 
    mov esi, FUNCTION_SEGMENT
    mov edi, FUNCTION_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, 2
    mov bx, 1
 
    call FuncCGAddSelector : 0
 
    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:
    times 1024 * 4 db 0
 
Stack32SegLen    equ $ - STACK32_SEGMENT
TopOfStack32    equ Stack32SegLen - 1

我们新定义了一个func段,在里面定义了AddFunc和SubFunc函数。第14行将func段的描述符加入了全局段描述符表。

17、18行将两个门描述符加入了全局段描述符表。

33-35行分别是这几个新添加的段描述符的选择子。

下面我们单步执行:

首先使用ndisasm -o 0x9000  loader > loader.txt将可执行程序进行反汇编,结果如下:

我们重点关注跳转第58行的跳转这里。

打断点执行:

 

将ax,bx赋值准备跳转,这时的cx值如下:

 

执行函数,cx的值有变化:

因为只是加的最低位,所以高位的那个9我们不用关心。

 

通过以下的调用方式我们可以得到同样的结果:

126、127行直接使用段基址加段内偏移的方式调用。

将使用调用门的方式截图如下:

126、127现在是使用调用门的方式,冒号后面的0在这里没有什么意义,只是为了语法需要,如果去掉冒号和0,编译器会认为这是一个段内的函数调用。

使用调用门选择子时,程序会根据选择子找到描述符,然后根据描述符中的选择子和偏移再去调用函数。这个调用门选择子在这里相当于一个函数指针。

 

实验结论:

  门描述符是一种特殊的描述符,需要注册于段描述符表

  调用门可以看做一个函数指针(保存具体函数的入口地址)

  通过调用门选择子对相应的函数进行远调用(call far)

  可以直接使用  选择子:偏移地址  的方式调用其他段的函数

  使用调用门时偏移地址无意义,仅仅是语法需要

历史遗留问题:

  保护模式下的不同段之间如何进行代码复用(如:调用同一个函数)?

解决方案:

  将不同代码段中需要复用的函数定义到独立的段中(retf, f是far的意思)

  计算每一个可复用函数的偏移量(FuncName - $$)

  通过   段选择子:偏移地址   的方式对目标函数进行远调用

 

对程序进行重构:

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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
%include "inc.asm"
 
org 0x9000
 
jmp ENTRY_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
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
 
 
; 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
Code16Selector    equ (0x0005 << 3) + SA_TIG + SA_RPL0
UpdateSelector    equ (0x0006 << 3) + SA_TIG + SA_RPL0
TaskALdtSelector  equ (0x0007 << 3) + SA_TIG + SA_RPL0
FunctionSelector  equ (0x0008 << 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]
ENTRY_SEGMENT:
    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
    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
 
    mov esi, CODE16_SEGMENT
    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
    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
 
BACK_ENTRY_SEGMENT:
        mov ax, cs
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov sp, TopOfStack16
 
        in al, 0x92
        and al, 11111101b
        out 0x92, al
 
        sti
 
        mov bp, HELLO_WORLD
        mov cx, 12
        mov dx, 0
        mov ax, 0x1301
        mov bx, 0x0007
        int 0x10
 
        jmp $
 
; 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 .16]
[bits 16]
CODE16_SEGMENT:
    mov ax, UpdateSelector
    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]
    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
 
    retf
 
PrintString        equ        PrintStringFunc - $$
 
FunctionSegLen        equ        $ - FUNCTION_SEGMENT
 
[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 FunctionSelector : PrintString
 
    mov ebp, HELLO_WORLD_OFFSET
    mov bx, 0x0C
    mov dh, 13
    mov dl, 30
 
    call FunctionSelector : PrintString
 
    mov ax, TaskALdtSelector
 
    lldt ax
 
    jmp TaskACode32Selector : 0
 
    ;jmp Code16Selector : 0
 
 
Code32SegLen    equ    $ - CODE32_SEGMENT
 
[section .gs]
[bits 32]
STACK32_SEGMENT:
    times 1014 * 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
 
TaskACode32SegLen     equ  $ - TASK_A_CODE32_SEGMENT

运行结果如下:

 

 

小结:

  门描述符是一种特殊的描述符,需要注册于段描述符表

  门描述符分为:调用门、中断门、陷阱门、任务门

  调用门可以看做一个函数指针(保存具体函数的入口地址)

  调用门选择子对应的函数调用方式为远调用(call  far)

  

 引入了们描述符,但是仍然没有解答的问题:

 

 

 

  

posted on   lh03061238  阅读(177)  评论(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
点击右上角即可分享
微信分享提示