门,顾名思义它是一扇门通向令一个地方,看一下门的结构里面有一个描述符和一个偏移值,门中定义了这扇门通向的目的地,当我们调用一个门的时候会到达这个地方:
门中的选择子:门中的偏移值,也即下图的selector:offset
这是我们代码中国定义的门的数据结构:
#define Gate(Selector,Offset,PCount,Attr)\
.2byte (Offset&0xffff);\
.2byte (Selector);\
.2byte (PCount&0x1f)|((Attr<<8)&0xff00);\
.2byte ((Offset>>16)&0xffff)
假设Selector_Gate_Call是调用门的描述符,执行lcall $Selector_Gate_Call,$0语句时偏移值($0)无意义,执行此语句后
CS=调用门中的选择子
IP=调用门中偏移值
CS:IP=全局描述符表中第(调用门中的选择子>>3)项描述符给出的段基址+调用门中偏移值
调用门的使用
定义调用门的描述符和选择子,如下面代码
/*门描述符*/
Descriptor_Gate_Call:Gate(Selector_Code_Gate,0,0,(DA_386CGate+DA_PL0))
/*调用门选择子*/
.set Selector_Gate_Call,(Descriptor_Gate_Call-GDT_START)
其中调用门中的选择子和描述符如下:
Descriptor_CODE_GATE:Descriptor(0x0,CodeGLen,DA_C+DA_32)
.set Selector_Code_Gate,(Descriptor_CODE_GATE-GDT_START)
Descriptor_CODE_GATE的初始化和代码段如下:
InitDescrptor(Descriptor_CODE_GATE,LABEL_CODE_G);
LABEL_CODE_G:
movl $((80*12+0)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'G',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set CodeGLen,(. - LABEL_CODE_G)
这时lcall $Selector_Gate_Call,$0就会看到红色的'G’打印出来,和ljmp Selector_Code_Gate,$0的效果是一样的,但是这并不是多此一举的,这涉及的特权级转换的问题,这里我们先不关心这些。
/*初始全局描述符Descriptor_CODE_GATE*/
InitDescrptor(Descriptor_CODE_GATE,LABEL_CODE_G);
具体代码如下:
#define Descriptor(base,lim,attr)\
.word lim&0xffff;\
.word base&0xffff;\
.byte (base>>16)&0xff;\
.word ((lim>>8)&0xf00)|(attr&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)
#define Gate(Selector,Offset,PCount,Attr)\
.2byte (Offset&0xffff);\
.2byte (Selector);\
.2byte (PCount&0x1f)|((Attr<<8)&0xff00);\
.2byte ((Offset>>16)&0xffff)
DA_386CGate = 0x8c
DA_PL0 = 0x00
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_CODE_GATE:Descriptor(0x0,CodeGLen,DA_C+DA_32)
/*门描述符*/
Descriptor_Gate_Call:Gate(Selector_Code_Gate,0,0,(DA_386CGate+DA_PL0))
GDT_END:
GdtPtr:
.word (GDT_END-GDT_START)-1 # so does gdt
.long GDT_START # This will be rewrite by code.
.set selector_Code32,(Descriptor_CODE32-GDT_START)
.set Selector_Video,(Descriptor_VIDEO-GDT_START)
.set Selector_Code_Gate,(Descriptor_CODE_GATE-GDT_START)
/*调用门选择子*/
.set Selector_Gate_Call,(Descriptor_Gate_Call-GDT_START)
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_CODE_GATE*/
InitDescrptor(Descriptor_CODE_GATE,LABEL_CODE_G);
/*加载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 $selector_Code32,$0
LABEL_SEG_CODE32:
.align 32
.code32
movw $Selector_Video,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*11+79)*2),%edi/*第11行,79列*/
movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
movb $'P',%al/*显示的字符*/
movw %ax,%gs:(%edi)
#ljmp $Selector_Gate_Call,$0
lcall $Selector_Gate_Call,$0
jmp .
LABEL_CODE_G:
movl $((80*12+0)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'G',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set CodeGLen,(. - LABEL_CODE_G)
.org 0x1fe, 0x90
.word 0xaa55
执行结果如下图,可以看到红色的‘G’打印出来
在LABEL_CODE_G中我们打印完字符就让程序挺在了这里,这时候如下两段代码的效果是一样的。
ljmp $Selector_Gate_Call,$0
lcall $Selector_Gate_Call,$0
但ljmp和lcall是不一样的,它们都会改变寄存器CS和IP实现程序的跳转,但lcall还会影响堆栈 ,段内跳转回将ip如栈段间跳转回将cs和ip入栈,等到lret执行时这个ip(或者cs和ip)会从堆栈中弹出。
下面我们就实现一个带返回的调用门:
#define Descriptor(base,lim,attr)\
.word lim&0xffff;\
.word base&0xffff;\
.byte (base>>16)&0xff;\
.word ((lim>>8)&0xf00)|(attr&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)
#define Gate(Selector,Offset,PCount,Attr)\
.2byte (Offset&0xffff);\
.2byte (Selector);\
.2byte (PCount&0x1f)|((Attr<<8)&0xff00);\
.2byte ((Offset>>16)&0xffff)
DA_386CGate = 0x8c
DA_PL0 = 0x00
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_CODE_GATE:Descriptor(0x0,CodeGLen,DA_C+DA_32)
Descriptor_LDT: Descriptor(0x0,LDTLen - 1, DA_LDT)
/*门描述符*/
Descriptor_Gate_Call:Gate(Selector_Code_Gate,0,0,(DA_386CGate+DA_PL0))
GDT_END:
GdtPtr:
.word (GDT_END-GDT_START)-1 # so does gdt
.long GDT_START # This will be rewrite by code.
/**-----------------------------------------------------------------
* GDT中的选择子
*-------------------------------*/
.set selector_Code32,(Descriptor_CODE32-GDT_START)
.set Selector_Video,(Descriptor_VIDEO-GDT_START)
.set Selector_Code_Gate,(Descriptor_CODE_GATE-GDT_START)
.set SelectorLDT,(Descriptor_LDT-GDT_START)
/*调用门选择子*/
.set Selector_Gate_Call,(Descriptor_Gate_Call-GDT_START)
/**-----------------------------------------------------------------
* 局部描述符表: 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)
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_LDT*/
InitDescrptor(Descriptor_LDT,LDT_START);
/*初始全局描述符Descriptor_CODE_GATE*/
InitDescrptor(Descriptor_CODE_GATE,LABEL_CODE_G);
/*初始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 $selector_Code32,$0
LABEL_SEG_CODE32:
.align 32
.code32
movw $Selector_Video,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*11+79)*2),%edi/*第11行,79列*/
movb $0x0c,%ah/*高四位表示黑底,低四位表示红字*/
movb $'P',%al/*显示的字符*/
movw %ax,%gs:(%edi)
lcall $Selector_Gate_Call,$0
movw $SelectorLDT,%ax
lldt %ax/*加载lgtr*/
ljmp $SelectorLDTCodeA,$0 /* 跳入局部任务 LABEL_CODE_A*/
jmp .
LABEL_CODE_G:
movl $((80*12+0)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'G',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
lret
.set CodeGLen,(. - LABEL_CODE_G)
LABEL_CODE_A:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+10)*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
其结果是打印完'G'后返回,然后又打印出'A'如下图:
门也是一个描述符,我们放到了GDT中,我们通过门的选择子访问门,门通过它记录的选择子和偏移值又把我们传送到了目的地。