上一篇为了简单我们只涉及到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为二级描述符表。