LDT部段描述符表(Local Descriptor Table)

  • 本质段描述符表 , 用于定义段描述符
  • 与 GDT 类似 , 可以看作 “段描述符的数组”
  • 通过定义选择子访问局部段描述符表中的元素

局部段描述符选择子

与全局段描述符差距在于第二位是不同的

image-20220514210200638

局部段描述符表

特殊寄存器存储ldt_ptr

image-20220514210436606

注意事项:

  • 局部段描述符表(它也是一段内存)需要在全局段描述符表中注册( 增加描述项 )
  • 通过对应的选择子加载局部段描述符( lldt load local descriptor table)
  • 局部段描述符表从第0项(GDT默认第一项占位不使用)开始使用( different from GDT )

LDT的意义:

  • 代码层面的意义:分级管理功能相同意义不同的段,减少GDT描述项的负担( 如 : 多个代码段 )
  • 系统层面的意义:实现多任务的基础要素( 每个任务对应一系列不同的段 )

LDT的定义与使用

    1. 定义独立功能相关的段( 代码段,数据段,栈段 )
    1. 上述目标段描述符组成局部段描述符表( LDT )
    1. 为各个段描述符定义选择子( SA_TIL )
    1. 在 GDT 中定义 LDT 的段描述符 , 并定义选择子
1
2
3
4
5
6
7
8
9
10
; GDT definition
;1.GDT 中定义 LDT 的段描述符 段基址, 段界限, 段属性
TASK_A_LDT_DESC : Descriptor 0, TaskALdtLen - 1, DA_LDT
;2.LDT选择子
TaskALdtSelector equ (0x0007 << 3) + SA_TIG + SA_RPL0 ;这里依然是全局段描述符表因此是SA_TIG

;3.
mov ax, TaskALdtSelector ;选择子赋值
lldt ax ;加载局部段描述符表LDT
jmp TaskACode32Selector : 0 ;使用局部段描述表的代码段

局部段的具体实现

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
;======================================
;
; Task A Code Segment
;======================================
[section .tast-a-ldt]
;Task A局部段描述符表
; 段基址, 段界限, 段属性
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 TaskAPrintString
jmp Code16Selector : 0

; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
TaskAPrintString:
push ebp
push eax
push edi
push cx
push dx

task_print:
mov cl, [ds:ebp]
cmp cl, 0
je task_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 task_print

task_end:
pop dx
pop cx
pop edi
pop eax
pop ebp

ret

TaskACode32SegLen equ $ - TASK_A_CODE32_SEGMENT

注意这里的打印函数是TaskAPrintString,而不是另一个隶属于.32代码段的printstring函数,
这里选择子已经不同了,即使偏移地址相同,强行调用printstring肯定是要出异常的,因此在此代码段复制粘贴了printstring函数,稍微改个名字就可以使用了。

多任务程序设计思路

image-20220514221129539

待解决的问题:

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

小结

  • 局部段描述表用于组织功能相关的段( section )
  • 局部段描述符表需要加载后才能正常使用( lldt )
  • 局部段描述符表必须在全局段描述符表中注册( Descriptor )
  • 通过局部段描述符表的选择子对其进行访问
  • 局部段描述符表是实现多任务的基础