目标:LDT(Local Descriptor Table)学习和使用;
代码:pmtest3.asm
原理:构造LDT,然后通过LDT描述符的信息跳入新的代码段执行
先来复习一下如果通过LDT寻址:
如何通过GDT、LDT寻址
实模式下,以XXXX:YYYY(16位:16位)格式表示虚拟地址,它对应的物理地址为 XXXX*10h+YYYYYYYY。
保护模式下,以XXXX:YYYYYYYY(16位:32位)格式表示一个虚拟地址。XXXX现在存放的是段选择子,显然无法反映出段的基址在那。对于这个地址,需要采取如下步骤进行寻找:
1)看XXXX中的T1位是否为0,如果是,表示段描述符在GDT中,跳转到步骤2;如果为1,表示段描述符在LDT中,跳转到步骤5;
2)从GDTR中获得GDT的基址;
3)以XXXX中高13位当做位置索引得到段描述符;
4)从段描述符中就可以得到段的起始地址,这样就能得到XXXX:YYYYYYYY对应的线性地址了;
5)从GDTR中获得GDT的基址,从LDTR中获取LDT在GDT中的索引值;
6)根据索引值得到LDT在内存中的位置,即LDT段的位置;
7)以XXXX中高13位当做索引得到段描述符;
8)从段描述符中就可以得到段的起始地址。
下面来分析代码
1.GDT中如何定义LDT的表项
LABEL_DESC_LDT: Descriptor 0, LDTLen - 1, DA_LDT ; LDT
定义了表项,接下来就需要定义它的选择子了
SelectorLDT equ LABEL_DESC_LDT - LABEL_GDT
2.构造内存中的LDT
GDT中的LDT表项会告诉你LDT在内存中的位置,下面是构造LDT的代码
[SECTION .ldt]
ALIGN 32
LABEL_LDT:
; 段基址 段界限 属性
LABEL_LDT_DESC_CODEA: Descriptor 0, CodeALen - 1, DA_C + DA_32 ; Code, 32 位
代码中只给LDT表中定义了一个表项,即LABEL_LDT_DESC_CODEA。
LDTLen equ $ - LABEL_LDT ;计算LDT的长度
同GDT一样,定义了表项,接下来就需要定义它的选择子了
SelectorLDTCodeA equ LABEL_LDT_DESC_CODEA - LABEL_LDT + SA_TIL
其实equ后面的内容用加括号的形式更好,即(LABEL_LDT_DESC_CODEA - LABEL_LDT)+ SA_TIL,前半部分表示相对于LDT起始位置的偏移,后半部分表示选择子的TI位为1。TI位是区分GDT和LDT的关键。
3.最后是LABEL_LDT_DESC_CODEA表项对应的代码的定义
; CodeA (LDT, 32 位代码段)
[SECTION .la]
ALIGN 32
[BITS 32]
LABEL_CODE_A:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 12 + 0) * 2 ; 屏幕第 10 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'L'
mov [gs:edi], ax
; 准备经由16位代码段跳回实模式
jmp SelectorCode16:0
CodeALen equ $ - LABEL_CODE_A
功能很简单打印字符“L”。
4.所有有关LDT的定义都完成了,现在需要将代码和表项关联起来,其实也就是为表项中的段基址赋值
; 初始化 LDT 在 GDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_LDT ;内存中LDT的地址
mov word [LABEL_DESC_LDT + 2], ax ;写入GDT中的LDT表项中
shr eax, 16
mov byte [LABEL_DESC_LDT + 4], al
mov byte [LABEL_DESC_LDT + 7], ah
; 初始化 LDT 中的描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_A ;内存中代码CODE_A地址
mov word [LABEL_LDT_DESC_CODEA + 2], ax ;写入LDT中LABEL_LDT_DESC_CODEA 表项中
shr eax, 16
mov byte [LABEL_LDT_DESC_CODEA + 4], al
mov byte [LABEL_LDT_DESC_CODEA + 7], ah
有关LDT的代码调试:
1.lldt命令加载ldtr
(0) [0x0000000000032712] 0010:0000003e (unk. ctxt): mov ax, 0x0030 ; 66b83000 0x30是LDT表项在GDT中偏移
(0) [0x0000000000032716] 0010:00000042 (unk. ctxt): lldt ax ; 0f00d0
执行前
cs:0x0010, dh=0x00409903, dl=0x26d40062, valid=1
Code segment, base=0x000326d4, limit=0x00000062, Execute-Only, Accessed, 32-bit
ldtr:0x0000, dh=0x00008200, dl=0x0000ffff, valid=1
gdtr:base=0x00032348, limit=0x3f
执行后
ldtr:0x0030, dh=0x00008203, dl=0x27540007, valid=1
此处是保护模式的代码,顺便看看0010:00000042的寻址过程:
(1)0x10表示TI位为0,所以找GDT中的表项,应该是第3个,即LABEL_DESC_CODE32;
(2)GDT的地址由gdtr:base=0x00032348得到;
(3)表项LABEL_DESC_CODE32中存储的段基址值为0x000326d4;
(4)0010:00000042对应的线性地址为0x000326d4:0042;
(5)0x000326d4+0x42=0x0000000000032716
2.jmp SelectorLDTCodeA:0 ; 跳入局部任务
(0) [0x0000000000032719] 0010:00000045 (unk. ctxt): jmp far 0004:00000000 ; ea000000000400
执行前
cs:0x0010, dh=0x00409903, dl=0x26d40062, valid=1
Code segment, base=0x000326d4, limit=0x00000062, Execute-Only, Accessed, 32-bit
eip: 0x00000045
执行后
cs:0x0004, dh=0x00409903, dl=0x275c0019, valid=1
Code segment, base=0x0003275c, limit=0x00000019, Execute-Only, Accessed, 32-bit
eip: 0x00000000
3.通过LDT寻址
选择LDT代码断中的语句mov [gs:edi], ax
(0) [0x000000000003276b] 0004:0000000f (unk. ctxt): mov word ptr gs:[edi], ax ; 65668907
cs:0x0004, dh=0x00409903, dl=0x275c0019, valid=1
Code segment, base=0x0003275c, limit=0x00000019, Execute-Only, Accessed, 32-bit
gs:0x0038, dh=0x0000930b, dl=0x8000ffff, valid=1
Data segment, base=0x000b8000, limit=0x0000ffff, Read/Write, Accessed
edi: 0x00000780 1920
现在来看0004:0000000f的寻址过程,也就是通过LDT寻址的过程:
(1)0x04表示TI位为1,表示段描述符在LDT中;
(2)在GDT中查找表示LDT的表项,索引值来自于ldtr:0x0030,GDT的地址由gdtr:base=0x00032348得到;
(3)根据(2)中的值,检索出表项为第7个LABEL_DESC_LDT,其中存储的段基址值为0x00032754,也就是LDT内存地址;
(4)根据选择子0x04的高13位为0,也就是说该段在LDT中的偏移为0,所以要找的表项是LDT中的第1个;
(5)从LDT的第1个表项中读出的段基址为0x0003275c;
(5)0004:0000000f对应的线性地址为0x0003275c:0f;
(5)0x0003275c+0x0f=0x000000000003276b
LDT的用法我们基本了解了,但是为什么要引入LDT呢?
可以把一个单独的任务所用到的所有东西封装在一个LDT中,这种思想就是多任务处理的一个雏形。