上一篇为了简单我们只涉及到GDT,其实LDT跟它差不多,跳转的时候选择子的第3位也就是TI位为0我们就用GDT,如果TI=1我们就用LDT。
总结如下:
TI=0时:CS:IP=全局描述符表中第1(0x8>>3)项描述符给出的段基址+0的偏移地址
TI=1时:CS:IP=局部描述符表中第1(0x8>>3)项描述符给出的段基址+0的偏移地址
局部描述符表在哪里?这要问ldtr寄存器了,ldtr里面存了一个选择子对应的就是用来描述LDT的那个描述符。
原来局部描述符表的基地址存在全局描述符表的LDT描述符里,这个描述符的选择子放到了ldtr寄存器中。
使用步骤如下:
1.定义一个局部描述符表LDT
2.在GDT中定义一个描述符Descriptor_LDT其基地址用LDT的起始地址填充,描述符Descriptor_LDT的选择子为SelectorLDT。
3.用lldt命令加载lgtr
4.jmp时的选择子TI位置1就可以了
很简单看一下代码全明白了:
#define Descriptor(base,lim,type)\
.word lim&0xffff;\
.word base&0xffff;\
.byte (base>>16)&0xff;\
.word ((lim>>8)&0xf00)|(type&0x0f0ff);\
.byte ((base>>24)&0xff)
/*
*InitDescrptor(Descriptor,SegBase)初始化描述符函数
*Descriptor:要初始化的描述符
*SegBase:段基址
*/
#define InitDescrptor(Descriptor,SegBase)\
xor %eax,%eax; \
mov %cs,%ax ; \
shl $4,%eax ; \
addl $(SegBase), %eax ;\
movw %ax, (Descriptor + 2);\
shr $16, %eax;\
movb %al, (Descriptor + 4);\
movb %ah, (Descriptor + 7)
DA_C = 0x98
DA_32 = 0x4000
DA_DRW = 0x92
SA_TIL = 0x4
DA_LDT = 0x82
.text
.globl start
.code16
start:
jmpl $0x0, $code
/**-----------------------------------------------------------------
* 全局描述符表: GDT
*-------------------------------*/
GDT_START:
Descriptor_DUMMY: Descriptor(0x0,0x0,0x0)
Descriptor_CODE32:Descriptor(0x0,0xffffffff,DA_C+DA_32)
Descriptor_VIDEO: Descriptor(0xb8000,0x0ffff,DA_DRW)
Descriptor_LDT: Descriptor(0x0,LDTLen - 1, DA_LDT) # LDT
GDT_END:
GdtPtr:
.word (GDT_END-GDT_START)-1 # so does gdt
.long GDT_START # This will be rewrite by code.
.set SelectorLDT,0x18
msg:
.string "Hello world!"
code:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $0x8000,%sp
/*显示HelloWorld字符串*/
mov $msg ,%ax
mov %ax ,%bp
mov $12 ,%cx
mov $0x1301,%ax
mov $0x000c,%bx
mov $0 ,%dl
int $0x10
/*初始全局描述符Descriptor_CODE32*/
InitDescrptor(Descriptor_CODE32,LABEL_SEG_CODE32);
/*初始全局描述符Descriptor_CODE32*/
InitDescrptor(Descriptor_LDT,LDT_START);
/*初始LDT 中的描述符Descriptor_LDT_CODEA*/
InitDescrptor(Descriptor_LDT_CODEA,LABEL_CODE_A);
/*加载gdtr即将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器*/
lgdt GdtPtr
/*关中断*/
cli
/*打开地址线A20*/
inb $0x92,%al
or $0x02,%al
outb %al,$0x92
/*设置cr0寄存器,切换到保护模式*/
movl %cr0,%eax
or $1,%eax
movl %eax,%cr0
/*真正进入保护模式,执行此命令后CS=0x8,IP=0*/
ljmp $0x8,$0
.align 32
.code32
/**-----------------------------------------------------------------
* 局部描述符表: LDT
*-------------------------------*/
LDT_START:
/* 段基址 段界限 属性*/
Descriptor_LDT_CODEA: Descriptor(0, CodeALen - 1, DA_C + DA_32)# Code, 32 位
.set LDTLen,(. - LDT_START)
#LDT 选择子
.set SelectorLDTCodeA,(0x0 + SA_TIL)
LABEL_SEG_CODE32:
.align 32
.code32
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*11+79)*2),%edi/*第11行,79列*/
movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
movb $'P',%al/*显示的字符*/
movw %ax,%gs:(%edi)
movw $SelectorLDT,%ax
lldt %ax
ljmp $SelectorLDTCodeA,$0 /* 跳入局部任务 LABEL_CODE_A*/
loop2:
jmp loop2
LABEL_CODE_A:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+0)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'A',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
loop3:
jmp loop3
.set CodeALen,(. - LABEL_CODE_A)
.org 0x1fe, 0x90
.word 0xaa55
注意LABEL_SEG_CODE32中的
movw $SelectorLDT,%ax
lldt %ax/*加载lgtr*/
ljmp $SelectorLDTCodeA,$0 /* 跳入局部任务 LABEL_CODE_A*/
我们在LABEL_CODE_A中打印了一个字符‘A’
运行结果如下:
为了更直观一些我将代码改成了这样:
#define Descriptor(base,lim,type)\
.word lim&0xffff;\
.word base&0xffff;\
.byte (base>>16)&0xff;\
.word ((lim>>8)&0xf00)|(type&0x0f0ff);\
.byte ((base>>24)&0xff)
/*
*InitDescrptor(Descriptor,SegBase)初始化描述符函数
*Descriptor:要初始化的描述符
*SegBase:段基址
*/
#define InitDescrptor(Descriptor,SegBase)\
xor %eax,%eax; \
mov %cs,%ax ; \
shl $4,%eax ; \
addl $(SegBase), %eax ;\
movw %ax, (Descriptor + 2);\
shr $16, %eax;\
movb %al, (Descriptor + 4);\
movb %ah, (Descriptor + 7)
DA_C = 0x98
DA_32 = 0x4000
DA_DRW = 0x92
SA_TIL = 0x4
DA_LDT = 0x82
.text
.globl start
.code16
start:
jmpl $0x0, $code
/**-----------------------------------------------------------------
* 全局描述符表: GDT
*-------------------------------*/
GDT_START:
Descriptor_DUMMY: Descriptor(0x0,0x0,0x0)
Descriptor_CODE32:Descriptor(0x0,0xffffffff,DA_C+DA_32)
Descriptor_VIDEO: Descriptor(0xb8000,0x0ffff,DA_DRW)
Descriptor_LDT: Descriptor(0x0,LDTLen - 1, DA_LDT) # LDT
GDT_END:
GdtPtr:
.word (GDT_END-GDT_START)-1 # so does gdt
.long GDT_START # This will be rewrite by code.
.set SelectorLDT,0x18
msg:
.string "Hello world!"
code:
mov %cs,%ax
mov %ax,%ds
mov %ax,%es
mov %ax,%ss
mov $0x8000,%sp
/*显示HelloWorld字符串*/
mov $msg ,%ax
mov %ax ,%bp
mov $12 ,%cx
mov $0x1301,%ax
mov $0x000c,%bx
mov $0 ,%dl
int $0x10
/*初始全局描述符Descriptor_CODE32*/
InitDescrptor(Descriptor_CODE32,LABEL_SEG_CODE32);
/*初始全局描述符Descriptor_CODE32*/
InitDescrptor(Descriptor_LDT,LDT_START);
/*初始LDT 中的描述符Descriptor_LDT_CODEA*/
InitDescrptor(Descriptor_LDT_CODEA,LABEL_CODE_A);
InitDescrptor(Descriptor_LDT_CODEB,LABEL_CODE_B);
/*加载gdtr即将全局描述符表gdt的首地址和gdt的界限赋给gdtr寄存器*/
lgdt GdtPtr
/*关中断*/
cli
/*打开地址线A20*/
inb $0x92,%al
or $0x02,%al
outb %al,$0x92
/*设置cr0寄存器,切换到保护模式*/
movl %cr0,%eax
or $1,%eax
movl %eax,%cr0
movw $SelectorLDT,%ax
lldt %ax/*加载lgtr*/
/*真正进入保护模式,执行此命令后CS=0x8,IP=0*/
#ljmp $0x8,$0 /*选择子的TI位为0 跳入LABEL_SEG_CODE32 打印字符'P'*/
ljmp $(0x8+0x4),$0 /* 0x4将选择子的TI位置1 跳入局部任务 LABEL_CODE_B 打印字符'B'*/
/*
*TI=0时:CS:IP=全局描述符表中第1(0x8>>3)项描述符给出的段基址+0的偏移地址
*TI=1时:CS:IP=局部描述符表中第1(0x8>>3)项描述符给出的段基址+0的偏移地址
*局部描述符表在哪里?这要问ldtr寄存器了,ldtr里面存了一个选择子对应的就是用来描述LDT的那个描述符。
*原来局部描述符表的基地址存在全局描述符表的LDT描述符里,这个描述符的选择子放到了ldtr寄存器中。
*/
/**-----------------------------------------------------------------
* 局部描述符表: LDT
*-------------------------------*/
LDT_START:
/* 段基址 段界限 属性*/
Descriptor_LDT_CODEA: Descriptor(0, CodeALen - 1, DA_C + DA_32)# Code, 32 位
Descriptor_LDT_CODEB: Descriptor(0, CodeBLen - 1, DA_C + DA_32)# Code, 32 位
.set LDTLen,(. - LDT_START)
/**-----------------------------------------------------------------
* #LDT 选择子
*----------------*/
.set SelectorLDTCodeA,(0x0 + SA_TIL)
.set SelectorLDTCodeB,(0x8 + SA_TIL)
/**-----------------------------------------------------------------
* 32位代码,显示字符P
*----------------*/
LABEL_SEG_CODE32:
.align 32
.code32
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*11+79)*2),%edi/*第11行,79列*/
movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
movb $'P',%al/*显示的字符*/
movw %ax,%gs:(%edi)
jmp .
/**-----------------------------------------------------------------
* 32位代码,显示字符A
*----------------*/
LABEL_CODE_A:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+0)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'A',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set CodeALen,(. - LABEL_CODE_A)
/**-----------------------------------------------------------------
* 32位代码,显示字符B
*----------------*/
LABEL_CODE_B:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+3)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'B',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set CodeBLen,(. - LABEL_CODE_B)
.org 0x1fe, 0x90
.word 0xaa55
注意99到104行代码
当我们用ljmp $0x8,$0 时选择子的TI位为0 , 跳入LABEL_SEG_CODE32, 打印字符'P'
当我们用ljmp $(0x8+0x4),$0 时选择子的TI位为0, 跳入LABEL_CODE_B , 打印字符'B'
LDT和GDT的关系:
LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同LDTR的内容是一个段选择子。由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。LDTR可以在程序中随时改变,通过使用lldt指令。如图,如果装载的是Selector 2则LDTR指向的是表LDT2。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。