这几个概念刚开始是有点难理解的,因为各种情况都有,有点复杂。我觉得最简单的办法就是,把程序的各种段,理解为国家的各种政府机构,例如省级机构、市级机构、县级机构等,DPL就是用来标识这个的,这是个静态的概念,省级的机构办省级的事,市级的机构办市级的事,设置好了各自的职权范围。而执行程序,可以理解为去这些机构办事,CPL就是你当前的身份。而RPL呢,可以理解为,你去办事的时候,希望以什么样的身份去办事。
这里还涉及一个概念:一致代码段和非一致代码段。这两个概念有点拗口难理解,“一致”与“非一致”有点类似政府的办事机构的不同性质,“非一致”的机构,就是那种吃饭不干事的,老百姓想来办事,直接撵出去,鸟都不鸟你。“一致”的机构,把老百姓当做自己家人,踏实为老百姓办事的机构。
·
下面说下办事的潜规则:
1.“非一致”的办事机构,整天吃饭不干事,除非你就是这机构里的人员,其它人这个机构鸟都不鸟。
2.“一致”的办事机构,你能进去办事,但是进去后,你的老百姓身份并不会改变,只能办老百姓的事,并不能说我一个普通老百姓,进去后要去管理这机构里面的人。
CPL是当前执行的代码的特权级,存储在CS和SS的第0、1位上。也就是说,特权级这里只有四种,0,1,2,3。
DPL是段的特权级,可以是数据段、堆栈段或者代码段,这个很好理解,是个静态的概念,这个DPL是在GDT创建段的时候,可以作为段的属性进 行设置。
一致代码段和非一致代码段,这个属性也是在GDT创建段的时候设置的。假如从A代码段要进入B代码段,1.如果B是非一致代码段,则要求执行A时的CPL=B的DPL。(同时要求RPL<= DPL)2.如果B是一致代码段,则执行A时的CPL>=B的DPL(RPL不检查),进入B时,A的CPL会延续下来。
从上面总结下:使用jmp、call进行普通跳转时,
1. 如果目标是非一致代码段,只能在相同特权级的代码间转移;
2. 如果目标是一致代码段,则最多只能从特权级低的代码转移到高的代码,但是CPL不会改变。
从上面的第二点看,是不是有点失望,虽然能从低特权级转到高特权级上去,但是你的身份还是没有改变,就好比你去政府机构求人家办事,你依然是个屌丝身份并没有改变,咋办呢,下面要提到的“调用门”就提供了这样的机会,使用“调用门”,就好比考公务员,你有机会进去政府机构办事,而且跟他们平起平坐。
1.从低特权级转移到高特权级
如果只是普通的jmp或者call,从低特权级转移到高特权级,跳转的目标代码必须是一致代码段,跳转后CPL并不改变。
调用门,能突破这层限制。
首先,理解什么是调用门
。这里一开始我觉得很别扭,“调用”两个字很容易让人想起什么函数调用啊,实际上完全不是这么回事。调用门
好比考公务员,普通百姓考上公务员,也能到政府办事机构里面去办事。
下面是如何从代码设置一个调用门:
GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 ...... LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32
...... ; 门 目标选择子,偏移,DCount, 属性 LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 选择子 ......
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT
......
很简单吧,想要给一个段增加一个调用门,有三步:1.在GDT里增加一个表项,这个表项使用某个段的选择子来初始化自己;2.新增一个选择子,指向这个新增的调用门;3.新增一个TSS段
下面是call、jmp调用门的使用规则:
加入我们从代码A,使用调用门Gate跳转到代码B。
除了jmp到非一致代码段B,为 DPL_B = CPL <= DPL_GATE
其它为:DPL_B <= CPL <= DPL_GATE
2.从高特权级转到低特权级
这种跳转比从低特权级转到高特权级麻烦点,因为涉及堆栈的变化,详细请参考《Orange’s 一个操作系统的实现》里面的第三章,作者讲的挺清晰的。这里我遇到几个问题:
a.从高特权级转到低特权级,可以使用简单的几行代码就完了:
push 低特权级ss
push 低特权级sp
push 低特权级cs
push 低特权级ip
retf
为什么使用retf后就能跳转到低特权级呢?原来是执行retf的时候,cpu会做检查,检查堆栈里面的cs,检测出是否有特权级变化,有的话,cpu内部就会有一个堆栈转移的处理过程。
b. 使用调用门的时候,调用门可以设置param_cout属性,也就是说可以设置这个调用门传多少参数,那如果我用上面a的方式返回低特权级,但是有参数,怎么办,其实这个可以在最后的ret指令加上参数,如retf n
,cpu就处理好参数的问题了。
下面是代码:
bf.asm
org 07c00h
[BITS 16]
START:
mov ax,cs
mov ds,ax
mov es,ax
;拷贝软盘中的代码到内存区
COPY:
mov bx, COPY_CODE_START ;07c00h + 512(0100h) == 07e00h
mov dl,0 ;驱动器号,软驱从0开始:0:软驱A,1:软驱B
;磁盘从80h开始,80h:C盘,81h:D盘
mov dh,0 ;磁头号,对于软盘即面号,一个面用一个磁头来读写
mov ch,0 ;磁道号
mov cl,2 ;扇区号
mov al,0x0a ;读取的扇区数
mov ah,2 ;13h的功能号(2表示读扇区),es:bx指向
;接收从扇区读入数据的内存区
int 13h
jc COPY ;读取失败,CF表示为1,重试读取
jmp LABEL_BEGIN ;把程序读到内存区后,跳转到新的执行点
;补全512字节
times 510-($-$$) db 0
dw 0xaa55
COPY_CODE_START:
%include "inc.asm"
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW+DA_DPL1 ; Data
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW+DA_DPL3 ; 显存首地址
LABEL_DESC_CODE_RING3: Descriptor 0, SegCodeRing3Len-1, DA_C+DA_32+DA_DPL3
LABEL_DESC_STACK3: Descriptor 0, TopOfStack3, DA_DRWA+DA_32+DA_DPL3
LABEL_DESC_TSS: Descriptor 0, TSSLen-1, DA_386TSS ;TSS
; 门 目标选择子,偏移,DCount, 属性
;LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate+DA_DPL0
LABEL_CALL_GATE_TEST: Gate SelectorCodeDest, 0, 0, DA_386CGate + DA_DPL3
; GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCodeDest equ LABEL_DESC_CODE_DEST - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorCallGateTest equ LABEL_CALL_GATE_TEST - LABEL_GDT
SelectorCodeRing3 equ LABEL_DESC_CODE_RING3 - LABEL_GDT + SA_RPL3
SelectorStack3 equ LABEL_DESC_STACK3 - LABEL_GDT + SA_RPL3
SelectorTSS equ LABEL_DESC_TSS - LABEL_GDT
; END of [SECTION .gdt]
[SECTION .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0 ; 在保护模式中显示
OffsetPMMessage equ PMMessage - $$
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest equ StrTest - $$
DataLen equ $ - LABEL_DATA
; END of [SECTION .data1]
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
; END of [SECTION .gs]
; 堆栈段ring3
[SECTION .s3]
ALIGN 32
[BITS 32]
LABEL_STACK3:
times 512 db 0
TopOfStack3 equ $ - LABEL_STACK3 - 1
; END of [SECTION .s3]
; TSS ---------------------------------------------------------------------------------------------
[SECTION .tss]
ALIGN 32
[BITS 32]
LABEL_TSS:
DD 0 ; Back
DD TopOfStack ; 0 级堆栈
DD SelectorStack ;
DD 0 ; 1 级堆栈
DD 0 ;
DD 0 ; 2 级堆栈
DD 0 ;
DD 0 ; CR3
DD 0 ; EIP
DD 0 ; EFLAGS
DD 0 ; EAX
DD 0 ; ECX
DD 0 ; EDX
DD 0 ; EBX
DD 0 ; ESP
DD 0 ; EBP
DD 0 ; ESI
DD 0 ; EDI
DD 0 ; ES
DD 0 ; CS
DD 0 ; SS
DD 0 ; DS
DD 0 ; FS
DD 0 ; GS
DD 0 ; LDT
DW 0 ; 调试陷阱标志
DW $ - LABEL_TSS + 2 ; I/O位图基址
DB 0ffh ; I/O位图结束标志
TSSLen equ $ - LABEL_TSS
; TSS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 初始化数据段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
; 初始化堆栈段描述符(ring3)
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_STACK3
mov word [LABEL_DESC_STACK3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK3 + 4], al
mov byte [LABEL_DESC_STACK3 + 7], ah
; 初始化Ring3描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_CODE_RING3
mov word [LABEL_DESC_CODE_RING3 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_RING3 + 4], al
mov byte [LABEL_DESC_CODE_RING3 + 7], ah
; 初始化 TSS 描述符
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_TSS
mov word [LABEL_DESC_TSS + 2], ax
shr eax, 16
mov byte [LABEL_DESC_TSS + 4], al
mov byte [LABEL_DESC_TSS + 7], ah
; 初始化测试调用门的代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE_DEST
mov word [LABEL_DESC_CODE_DEST + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE_DEST + 4], al
mov byte [LABEL_DESC_CODE_DEST + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
mov esi, OffsetPMMessage ; 源数据偏移
mov edi, (80 * 10 + 0) * 2 ; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
.1:
lodsb
test al, al
jz .2
mov [gs:edi], ax
add edi, 2
jmp .1
.2: ; 显示完毕
call DispReturn
; Load TSS
mov ax, SelectorTSS
ltr ax ; 在任务内发生特权级变换时要切换堆栈,而内层堆栈的指针存放在当前任务的TSS中,所以要设置任务状态段寄存器 TR。
push SelectorStack3
push TopOfStack3
push SelectorCodeRing3
push 0
retf ; Ring0 -> Ring3,历史性转移!将打印数字 '3'。
; 测试调用门(无特权级变换),将打印字母 'C'
;call SelectorCallGateTest:0
;call SelectorCodeDest:0
;jmp $
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
mov eax, edi
mov bl, 160
div bl
and eax, 0FFh
inc eax
mov bl, 160
mul bl
mov edi, eax
pop ebx
pop eax
ret
; DispReturn 结束---------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
[SECTION .sdest]; 调用门目标段
[BITS 32]
LABEL_SEG_CODE_DEST:
;jmp $
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 12 + 0) * 2 ; 屏幕第 12 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'C'
mov [gs:edi], ax
retf
SegCodeDestLen equ $ - LABEL_SEG_CODE_DEST
; END of [SECTION .sdest]
; CodeRing3
[SECTION .ring3]
ALIGN 32
[BITS 32]
LABEL_CODE_RING3:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 14 + 0) * 2 ; 屏幕第 14 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, '3'
mov [gs:edi], ax
call SelectorCallGateTest:0 ; 测试调用门(有特权级变换),将打印字母 'C'。
jmp $
SegCodeRing3Len equ $ - LABEL_CODE_RING3
; END of [SECTION .ring3]