内核要和用户程序分开,内核一定要安全,不能被用户程序干涉,但是有时候用户程序也需要读取内核的某些数据,怎么办呢?x86就引入了访问特权等级(0-3)的机制,x86 cpu共有4个特权级 level0 到 level3 其中level0特权级最高,level3特权级最高。处理器通过识别CPL、DPL、RPL这3中种特权级进行特权级检验。
CPL(Current Privilege Level)是当前执行的程序或任务的特权级。它被存储在CS和SS的第0位和第1位上。
DPL(Descriptor Privilege Level)表示段或者门的特权级。它被存储在段描述符或者门描述符的DPL字段中。
RPL(Requessted Privilege Level)是通过段选择子的第0位和第1位表现出来的。
jmp和call所能进行的代码间转移是非常有限的,对于非一致代码段,只能在相同特权级代码间转移;对于一致代码段,可以从低到高,且CPL不会改变。
我们一般用调用门和lcall指令实现特权级由低到高的转移,通过lret指令实现特权级由高到低的转移。
其实我们一直在特权级0下工作,下面我们就用lret实现特权级由高到低的转移,ring0--->ring3
#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_C = 0x98
DA_32 = 0x4000
DA_DRW = 0x92
DA_DRWA=0x93
SA_TIL = 0x4
DA_LDT = 0x82
SA_RPL3 = 3
DA_DPL0=0x00
DA_DPL1=0x20
DA_DPL2=0x40
DA_DPL3=0x60
.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 + DA_DPL3)
Descriptor_CODE_GATE:Descriptor(0x0,CodeGLen,DA_C+DA_32)
Descriptor_LDT: Descriptor(0x0,LDTLen - 1, DA_LDT)
Descriptor_CODE3:Descriptor(0x0,0xffffffff,DA_C+DA_32+DA_DPL3)
Descriptor_STACKR3:Descriptor(0,TopOfStackR3, (DA_DRWA + DA_32 + DA_DPL3))
/*门描述符*/
Descriptor_Gate_Call:Gate(Selector_Code_Gate,0,0,(DA_386CGate+DA_DPL0))
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_CodeR3,(Descriptor_CODE3-GDT_START+SA_RPL3)
.set SelectorStackR3,(Descriptor_STACKR3- GDT_START + SA_RPL3)
/*调用门选择子*/
.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)
/* 32-bit ring 3 stack segment. */
.align 4
LABEL_STACKR3:
.space 10, 0
.set TopOfStackR3, (. - LABEL_STACKR3)
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);
/*初始全局描述符LABEL_STACKR3 */
InitDescrptor(LABEL_STACKR3,Descriptor_STACKR3);
/*初始LDT 中的描述符Descriptor_LDT_CODEA*/
InitDescrptor(Descriptor_LDT_CODEA,LABEL_CODE_A);
/*初始全局描述符Descriptor_CODE3*/
InitDescrptor(Descriptor_CODE3,LABEL_CODE_3);
/*加载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_CodeR3,$0
pushl $SelectorStackR3
pushl $TopOfStackR3
pushl $selector_CodeR3
pushl $0
lret
lcall $Selector_Gate_Call,$0
#ljmp $Selector_Code_Gate,$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)
jmp .
.set CodeALen,(. - LABEL_CODE_A)
LABEL_CODE_3:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+10)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'3',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set Code3Len,(. - LABEL_CODE_3)
.org 0x1fe, 0x90
.word 0xaa55
具体改动点如下:
DA_DRWA=0x93
SA_RPL3 = 3
DA_DPL3=0x60
我们将的描述符Descriptor_VIDEO设为DA_DPL3,添加描述符Descriptor_CODE3和Descriptor_STACKR3
Descriptor_VIDEO: Descriptor(0xb8000,0x0ffff,DA_DRW + DA_DPL3)
Descriptor_CODE3:Descriptor(0x0,0xffffffff,DA_C+DA_32+DA_DPL3)
Descriptor_STACKR3:Descriptor(0,TopOfStackR3, (DA_DRWA + DA_32 + DA_DPL3))
添加选择子selector_CodeR3和SelectorStackR3
.set selector_CodeR3,(Descriptor_CODE3-GDT_START+SA_RPL3)
.set SelectorStackR3,(Descriptor_STACKR3- GDT_START + SA_RPL3)
ring3下的堆栈
.align 4
LABEL_STACKR3:
.space 10, 0
.set TopOfStackR3, (. - LABEL_STACKR3)
/*初始全局描述符LABEL_STACKR3 */
InitDescrptor(LABEL_STACKR3,Descriptor_STACKR3);
/*初始全局描述符Descriptor_CODE3*/
InitDescrptor(Descriptor_CODE3,LABEL_CODE_3);
在ring3下打印'3'
LABEL_CODE_3:
movw $0x10,%ax
movw %ax,%gs/* 视频段选择子(目的)*/
movl $((80*12+10)*2),%edi/*第10行,0列*/
movb $0x0c,%ah/*高四位0000表示黑底,低四位1100表示红字*/
movb $'3',%al/*要显示的字符*/
movw %ax,%gs:(%edi)
jmp .
.set Code3Len,(. - LABEL_CODE_3)
实现从ring0到ring3的跳转
pushl $SelectorStackR3 //ss入栈
pushl $TopOfStackR3 //esp入栈
pushl $selector_CodeR3//cs入栈
pushl $0 //ip入栈
lret //将栈中的内容弹出