Linux内核系列—5.操作系统开发之特权级CPL、DPL、RPL

CPL——当前执行的程序或任务的特权级,它被存储在cs和ss的第0位和第1位上。

DPL——段或者门的特权级,如果是数据段DPL则规定了可以访问此段的最低特权级

RPL——通过段选择子的第0位和第1位表现出来的。处理器通过检查RPL和CPL来确认一个访问请求是否合法。RPL保证了操作系统不会越俎代庖地代表一个程序去访问一个段。

我们先来展示一下特权级错误访问版本。

先把LABEL_DESC_DATA对应的段描述符的DPL修改为1:

LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW+DA_DPL1    ; Data

继续修改,把选择子的RPL改为3:

SelectorData		equ	LABEL_DESC_DATA		- LABEL_GDT + SA_RPL3

运行结果如下:

虚拟机崩溃。因为RPL & CPL 必须 <= DPL

下面就演示一下从低特权转到高特权的方法:

通过jmp和call所能进行的代码段间转移是非常有限的,对于非一致代码段,只能在相同特权级代码段之间转移。遇到一致代码段也最多能从低到高,而且CPL不会改变。如果想自由地进行不同特权级之间的转移,显然需要其他几种方式,即运用门描述符或者TSS。调用门描述符格式如下:

一个门描述了由一个选择子和一个偏移所指定的线性地址,程序正是通过这个地址进行转移的。

以下是通过调用门转移的目标段:

[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]

下面是代码段描述符,选择子及初始化描述符的代码:

LABEL_DESC_CODE_DEST: Descriptor 0,SegCodeDestLen-1, DA_C+DA_32; 非一致代码段,32

SelectorCodeDest	equ	LABEL_DESC_CODE_DEST	- LABEL_GDT

; 初始化测试调用门的代码段描述符
	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

现在添加调用门:

LABEL_CALL_GATE_TEST: Gate SelectorCodeDest,   0,     0, DA_386CGate+DA_DPL0

宏Gate的定义在pm.inc中

描述符的属性是DA_386CGate表明是一个调用门。里面指定的选择子是SelectorCodeDest,表明目标代码段是刚刚新添加的代码段。偏移地址是0,表示将跳转到目标代码段的开头处。另外,我们把其DPL指定为0.

现在调用门准备就绪,它指向的位置是SelectorCodeDest:0,即标号LABEL_SEG_CODE_DEST处的代码。

现在添加一个低特权的代码段ring3和堆栈:

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

; 堆栈段ring3
[SECTION .s3]
ALIGN	32
[BITS	32]
LABEL_STACK3:
	times 512 db 0
TopOfStack3	equ	$ - LABEL_STACK3 - 1
; END of [SECTION .s3]

; CodeRing3
[SECTION .ring3]
ALIGN	32
[BITS	32]
LABEL_CODE_RING3:
	mov	ax, SelectorVideo
	mov	gs, ax

	mov	edi, (80 * 14 + 0) * 2
	mov	ah, 0Ch
	mov	al, ‘3‘
	mov	[gs:edi], ax

	jmp	$
SegCodeRing3Len	equ	$ - LABEL_CODE_RING3
; END of [SECTION .ring3]

执行如下:

打印了红色的3,表明我们由ring0到ring3的转移成功。接下来试验一下调用门的使用。

把调用门的描述符和选择子改成特权等级为3.还有从低特权级到高特权级转移的时候,需要用到TSS,我们来准备一个TSS。

LABEL_DESC_TSS:        Descriptor 0,          TSSLen-1, DA_386TSS

; 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

我们需要在特权级变换之前加载它

	mov	ax, SelectorTSS
	ltr	ax

运行结果如下:

看到字母C表明从低特权级到高特权级的转移。

源码

时间: 2024-08-25 10:17:57

Linux内核系列—5.操作系统开发之特权级CPL、DPL、RPL的相关文章

Linux内核系列—10.操作系统开发之内核HelloWorld

a.我们先来体验一下在Linux下用汇编编程的感觉,见代码 [section .data] ; 数据在此 strHello db "Hello, world!", 0Ah STRLEN equ $ - strHello [section .text] ; 代码在此 global _start ; 我们必须导出 _start 这个入口,以便让链接器识别 _start: mov edx, STRLEN mov ecx, strHello mov ebx, 1 mov eax, 4 ; sy

Linux内核系列—11.操作系统开发之ELF格式

ELF文件的结构如下图所示: ELF文件由4部分组成,分别是ELF头(ELF header).程序头表(Program header table).节(Sections)和节头表(Section header table). 实际上,一个文件中不一定包含全部这些内容,而且它们的位置也未必如上图所示这样安排,只有ELF头的位置是固定的,其余各部分的位置.大小等信息由ELF头中的各项值来决定. ELF header的格式如下代码所示: #define EI_NIDENT 16 typedef str

Linux内核系列—8.操作系统开发之时钟中断

外部中断的情况复杂一些,因为需要建立硬件中断与向量号之间的对应关系.外部中断分为不可屏蔽中断(NMI)和可屏蔽中断两种,分别由CPU的两根引脚NMI和INTR来接收.如下图所示: 可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的.8259A可以认为它是中断机制中所有外围设备的一个代理.在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,在保护模式下向量号08h~0Fh已经被占用了,所以我们不得不重新设置主从8259A. 对8259A的设置并不复杂,通

linux内核系列(一)编译安装Linux内核 2.6.18

1.配置环境 操作系统:CentOS 5.2 下载linux-2.6.18版本的内核,网址:http://www.kernel.org 说明:该编译文档适合2.6.18以上的Linux内核版本,只需所编译的 Linux内核版本不能低于Linux操作系统自身的内核版本,不然会遇到很多问题:   2.开始编译 cp  ./ linux-2.6.18.tar.gz  /usr/src/ tar –zxvf ./linux-2.6.18.tar.gz cd /usr/src/linux-2.6.18 /

linux内核系列(一)内核数据结构之链表

双向链表 传统链表与linu内核链表的区别图: 图一 图二 从上图中看出在传统链表中各种不同链表间没有通用性,因为各个数据域不同,而在linux内核中巧妙将链表结构内嵌到数据域结构中使得不同结构之间能连接起来: 链表的常用操作 内核中链表实现文件路径:include/linux/list.h 链表结构定义 struct list_head {     struct list_head *next, *prev; }; 获取结构入口地址(list_entry) #define list_entry

Linux内核系列—12.d.操作系统开发之扩充内核 ●

现在把esp.GDT等内容放进内核中,我们现在可以用C语言了,只要能用C,我们就避免用汇编. 下面看切换堆栈和GDT的关键代码: ; 导入函数 extern cstart ; 导入全局变量 extern gdt_ptr [SECTION .bss] StackSpace resb 2 * 1024 StackTop: ; 栈顶 ; 把 esp 从 LOADER 挪到 KERNEL mov esp, StackTop ; 堆栈在 bss 段中 sgdt [gdt_ptr] ; cstart() 中

Linux内核系列—12.a.操作系统开发之从Loader到内核

Loader要做两项工作,我们先来做第一项,把内核加载到内存: 1.加载内核到内存. 2.跳入保护模式. 首先编译无内核时: nasm boot.asm -o boot.bin nasm loader.asm -o loader.bin dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc sudo mount -o loop a.img /mnt/hgfs/ sudo cp loader.bin /mnt/hgfs/ -v sudo umoun

Linux内核系列—12.b.操作系统开发之从Loader跳入保护模式

现在,内核已经被我们加载进内存了,该是跳入保护模式的时候了. 首先是GDT以及对应的选择子,我们只定义三个描述符,分别是一个0~4GB的可执行段.一个0~4GB的可读写段和一个指向显存开始地址的段: ; GDT ; 段基址 段界限, 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR|DA_32|DA_LIMIT_4K ;0-4G LABEL_DESC_FLAT_RW: D

Linux内核系列—12.e.操作系统开发之Makefile

先来看一个简单的Makefile,我们把它放在目录/boot下,可以用来编译boot.bin和loader.bin. # Makefile for boot # Programs, flags, etc. ASM = nasm ASMFLAGS = -I include/ # This Program TARGET = boot.bin loader.bin # All Phony Targets .PHONY : everything clean all # Default starting