特权级转移之 高,低特权级互相转移,模拟操作系统内核态与用户态切换以及应用程序调用系统资源

在这里插入图片描述

TSS中保存的栈信息,是在特权级转移的时候被用到,具体就是在从低特权级转移到高特权级的时候会用。注意 从低到高

问题:为什么要保存 段寄存器值和通用寄存器的值?
既然是为了实现多任务,那么任务之间显然要进行切换,切换任务的时候需要保存当前任务上下文,那么什么是任务上下文? 具体就是任务执行时的关键寄存器的值,包括段寄存器和通用寄存器的值。把这些寄存器值保存之后,等任务切换回来的时候,直接把这些寄存器值恢复 任务的执行状态就恢复了。

TSS这个数据结构中很大一部分就是保存了寄存器的值的。

在X86处理器中,如果说特权级进行了转移,那么所使用的栈也会发生变化,每一个特权级使用自己一个独立的栈,不同特权级的栈是相互独立的。所以TSS中也保存了 任务所需要的不同特权级的栈信息,这些不同特权级的栈信息是怎么表示的呢? 其实就是保存 ss寄存器的值 和 esp寄存器的值。众所周知 栈需要两个寄存器来表示,第一个是ss寄存器 保存栈基地址,第二个是esp寄存器 保存栈顶地址。

这样保存之后的好处是什么?
很明显,假如我们现在要从 3特权级 跳转到 0特权级执行,发生特权级的转移了,要切换栈,此时所需要的 0特权级的栈信息 就可以直接到 TSS结构体中找就可以了。

注意 TSS结构体中 只保存了3个栈的信息 分别是特权级 0 1 2 的栈信息

在这里插入图片描述
在这里插入图片描述
调用门可以做 特权级的转移,从低特权级转移到高特权级,转移的时候栈的变化是这样的:

1 首先从 TSS中获取 高特权级栈的信息(包括栈基地址 ss寄存器值,栈顶指针位置 esp寄存器值)

2 获取之后,将 低特权级的栈信息(包括栈基地址 ss寄存器值,栈顶指针位置 esp寄存器值) 压入到 高特权级栈中,此操作的意义就是为了返回,函数调用完需要返回,返回的时候就从 高特权级转移到低特权级了,也就发生了特权级变化,那么栈就会发生变化。

回忆上一节知识,高特权级跳转到低特权级,我们在跳转的时候,手工的将低特权级栈的信息压入了栈中,并且将低特权级代码段入口压入栈中,然后使用 retf指令 。

如果是 调用门的话 在调用的瞬间,低特权级的栈信息以及低特权级的代码段信息 都会被压入栈中,这样 当遇到 retf指令的时候,就会将低特权级的栈信息 从高特权级的栈中取出来 恢复到 ss寄存器和 esp寄存器。

在这里插入图片描述
TSS中保存的栈信息,是在特权级转移的时候被用到,具体就是在从低特权级转移到高特权级的时候会用。注意 从低到高,而一共只有0 1 2 3,四个特权级,没有比3特权级更低的特权级,所以不可能有特权级从比3更低的特权级 转移到 3特权级。

在这里插入图片描述
注意:
1, 32位核心代码段和数据段 特权级为0 模拟内核态

2, 32位任务代码段和数据段 特权级为3 模拟用户态

3, 在系统启动后会首先执行内核态核心代码 之后就会跳转到 任务代码去执行,此时就是 高特权级 到 低特权级的转移(retf 远返回)。这里模拟的就是操作系统启动后去执行某个应用程序。模拟操作系统内核加载执行 应用程序

4, 在用户态的应用程序中 调用 内核高特权级代码,即系统函数。这个时候就涉及到了特权级转移,必然要陷入内核态,对应实验当中 就是使用调用门来做特权级的转移,完成某个任务,然后返回。

在这里插入图片描述
1 当系统开始执行了之后,显然是在实模式的,我们需要转换到保护模式执行,转换到保护模式之后,特权级为0,对应的就是核心代码段的执行。

2 核心代码段做好工作后,通过远返回执行指令 从高特权级0的内核态 跳转到 低特权级为3的用户态执行任务,模拟操作系统内核加载执行 应用程序

3 低特权级为3的用户态 执行任务中 需要调用一个系统函数,那么就要陷入内核态,这个过程的本质就是特权级转移了,通过调用门从3特权级的用户态转移到特权级为0的内核态,然后执行系统函数,执行完之后 又通过远返回执行 做特权级的转移,从特权级为0的内核态系统函数代码段 转移到 特权级为3的用户态任务代码段。

在这里插入图片描述
特权级转移时会发生栈变换,栈信息到TSS结构体中查找,TSS结构体存在于内存当中,既然TSS 要存在于内存当中,那么他就应该是保护模式下的一个段,所以必然要有相应的段描述符和选择子,但凡内存中的一个段 就会有相应的段描述符 和 选择子,TSS结构体也不例外。

在TSS结构体定义好之后,如何使用呢?
通过 ltr指令加载使用。

实验 :

实验说明:

32位保护模式下的 
CODE32_DESC   代码段
DATA32_DESC  数据段 
STACK32_DESC 栈段
特权级都是0,用来模拟 内核态,CODE32_DESC 模拟内核 核心代码段


32位保护模式下的 FUNCTION_DESC 特权级为0 用来模拟 内核态系统函数


32位保护模式下的 
TASK_A_CODE32_DESC
TASK_A_DATA32_DESC
TASK_A_STACK32_DESC
特权级都是3 用来模拟用户态 任务代码段

makefile

; Segment Attribute
DA_32    equ    0x4000
DA_DR    equ    0x90
DA_DRW   equ    0x92
DA_DRWA  equ    0x93
DA_C     equ    0x98
DA_CR    equ    0x9A
DA_CCO   equ    0x9C
DA_CCOR  equ    0x9E

; Segment Privilege
DA_DPL0		equ	  0x00    ; DPL = 0
DA_DPL1		equ	  0x20    ; DPL = 1
DA_DPL2		equ	  0x40    ; DPL = 2
DA_DPL3		equ	  0x60    ; DPL = 3

; Special Attribute
DA_LDT       equ    0x82
DA_TaskGate  equ    0x85	; 任务门类型值
DA_386TSS    equ	0x89	; 可用 386 任务状态段类型值
DA_386CGate  equ	0x8C	; 386 调用门类型值
DA_386IGate  equ	0x8E	; 386 中断门类型值
DA_386TGate  equ	0x8F	; 386 陷阱门类型值

; Selector Attribute
SA_RPL0    equ    0
SA_RPL1    equ    1
SA_RPL2    equ    2
SA_RPL3    equ    3

SA_TIG    equ    0
SA_TIL    equ    4

; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3	                          ; 段基址, 段界限, 段属性
    dw    %2 & 0xFFFF                         ; 段界限1
    dw    %1 & 0xFFFF                         ; 段基址1
    db    (%1 >> 16) & 0xFF                   ; 段基址2
    dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
    db    (%1 >> 24) & 0xFF                   ; 段基址3
%endmacro                                     ; 共 8 字节

; 门
; usage: Gate Selector, Offset, DCount, Attr
;        Selector:  dw
;        Offset:    dd
;        DCount:    db
;        Attr:      db
%macro Gate 4
    dw    (%2 & 0xFFFF)                      ; 偏移地址1
    dw    %1                                 ; 选择子
    dw    (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; 属性
    dw    ((%2 >> 16) & 0xFFFF)              ; 偏移地址2
%endmacro 

loader.asm

%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]
; GDT definition
;                                 段基址,       段界限,       段属性

GDT_ENTRY       :     Descriptor    0,            0,           0
CODE32_DESC     :     Descriptor    0,    Code32SegLen - 1,    DA_C + DA_32 + DA_DPL0
;注意显存段特权级为3
VIDEO_DESC      :     Descriptor 0xB8000,     0x07FFF,         DA_DRWA + DA_32 + DA_DPL3
DATA32_DESC     :     Descriptor    0,    Data32SegLen - 1,    DA_DR + DA_32 + DA_DPL0
STACK32_DESC    :     Descriptor    0,     TopOfStack32,       DA_DRW + DA_32 + DA_DPL0
;32位保护模式下的代码段,特权级0,模拟系统函数
FUNCTION_DESC   :     Descriptor    0,   FunctionSegLen - 1,   DA_C + DA_32 + DA_DPL0
;局部段描述附表,该段中定义 特权级为3的用户态 任务代码,模拟务代码段
TASK_A_LDT_DESC :     Descriptor    0,     TaskALdtLen - 1,    DA_LDT + DA_DPL0
;TSS任务状态段 段描述符
TSS_DESC        :     Descriptor    0,       TSSLen - 1,       DA_386TSS + DA_DPL0

; Gate Descriptor 调用门描述符 
;定义调用门描述符,对应 FUNCTION_DESC 代码段中两个函数的入口地址
;该调用门描述符中保存  FUNCTION_DESC段(函数段)的选择子,以及段内偏移地址(PrintString)(指的是该代码段中某个函数的入口地址)
;参数个数为0 表明我们没有使用栈来传递参数,而是使用寄存器来传递参数
;属性 是 调用门类型值 DA_386CGate  equ	0x8C	; 386 调用门类型值,而特权级只能定义为3,因为他是给应用程序调用的,太高了应用程序就没权限使用它了
;                                           选择子,            偏移,          参数个数,         属性
FUNC_PRINTSTRING_DESC    :    Gate      FunctionSelector,   PrintString,       0,         DA_386CGate + DA_DPL3

; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:
          dw   GdtLen - 1
          dd   0

; GDT Selector
Code32Selector     equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector      equ (0x0002 << 3) + SA_TIG + SA_RPL3
Data32Selector     equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector    equ (0x0004 << 3) + SA_TIG + SA_RPL0
FunctionSelector   equ (0x0005 << 3) + SA_TIG + SA_RPL0
TaskALdtSelector   equ (0x0006 << 3) + SA_TIG + SA_RPL0
;TSS任务状态段 段描述符选择子
TSSSelector        equ (0x0007 << 3) + SA_TIG + SA_RPL0
; Gate Selector
FuncPrintStringSelector    equ   (0x0008 << 3) + SA_TIG + SA_RPL3
; end of [section .gdt]

;任务状态段  本质上也是一块内存,所以也需要遵循32位保护模式的编程规则,需要定义对应的段描述符+选择子
[section .tss]
[bits 32]
TSS_SEGMENT:
        dd    0
        dd    TopOfStack32            ; 0 特权级对应的栈信息 栈顶指针
        dd    Stack32Selector         ; 0 特权级对应的栈信息 栈段基指针
        dd    0                       ; 1 特权级对应的栈信息 暂时为空
        dd    0                       ;
        dd    0                       ; 2 特权级对应的栈信息 暂时为空
        dd    0                       ;
        times 4 * 18 dd 0			  ; 往下全部定义为0,因为次实验不涉及多任务切换,所以不需要使用那些保存寄存器值的字段
        dw    0
        dw    $ - TSS_SEGMENT + 2
        db    0xFF					  ; 结束符
        
TSSLen    equ    $ - TSS_SEGMENT

TopOfStack16    equ 0x7c00



[section .s16]
[bits 16]
ENTRY_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, TopOfStack16

   
    ; initialize GDT for 32 bits code segment
    mov esi, CODE32_SEGMENT
    mov edi, CODE32_DESC
    call InitDescItem

   
    mov esi, DATA32_SEGMENT
    mov edi, DATA32_DESC
    call InitDescItem

    mov esi, STACK32_SEGMENT
    mov edi, STACK32_DESC
    call InitDescItem
    
    mov esi, FUNCTION_SEGMENT
    mov edi, FUNCTION_DESC
    call InitDescItem
    
	;初始化 任务A的局部段描述符表 的段描述符的段基址信息
    mov esi, TASK_A_LDT_ENTRY
    mov edi, TASK_A_LDT_DESC
    call InitDescItem
    
	;初始化 局部段描述符表中的 数据段 的 段描述符的段基址信息
    mov esi, TASK_A_DATA32_SEGMENT
    mov edi, TASK_A_DATA32_DESC
    call InitDescItem
    
	;初始化 局部段描述符表中的  可执行代码段  的 段描述符的段基址信息
    mov esi, TASK_A_CODE32_SEGMENT
    mov edi, TASK_A_CODE32_DESC
    call InitDescItem
    
	;初始化 局部段描述符表中的  栈段 的 段描述符的段基址信息
    mov esi, TASK_A_STACK32_SEGMENT
    mov edi, TASK_A_STACK32_DESC
    call InitDescItem
    
	;初始化 TSS 段描述符段基址信息
    mov esi, TSS_SEGMENT
    mov edi, TSS_DESC
    call InitDescItem

    
    ; initialize GDT pointer struct
    mov eax, 0
    mov ax, ds
    shl eax, 4
    add eax, GDT_ENTRY
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT
    lgdt [GdtPtr]

    ; 2. close interrupt
    cli 

    ; 3. open A20
    in al, 0x92
    or al, 00000010b
    out 0x92, al

    ; 4. enter protect mode
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax

    
    ; 5. jump to 32 bits code
    jmp dword Code32Selector : 0

; esi    --> code segment label
; edi    --> descriptor label

InitDescItem:

    push eax
    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah
    pop eax
    ret



[section .dat]
[bits 32]
DATA32_SEGMENT:
    DTOS               db  "D.T.OS!", 0
    DTOS_OFFSET        equ DTOS - $$

Data32SegLen equ $ - DATA32_SEGMENT


[section .s32]
[bits 32]
CODE32_SEGMENT:
    mov ax, VideoSelector
    mov gs, ax
    
    mov ax, Data32Selector
    mov ds, ax

    mov ax, Stack32Selector
    mov ss, ax

    mov eax, TopOfStack32
    mov esp, eax
    
	;打印内核数据段当中的字符串,用于说明已经执行核心代码段
    mov ebp, DTOS_OFFSET
    mov bx, 0x0c
    mov dh, 12
    mov dl, 33
	;通过 段选择子:段内偏移地址 的方式 调用打印函数
	;不发生特权级转移,因为本段内存段特权级 与 FunctionSelector段特权级 都是0
    call FunctionSelector : PrintString
	
	;加载 TSS 结构体
    mov ax, TSSSelector
    ltr ax
	
	;加载局部段描述符表
    mov ax, TaskALdtSelector
    lldt ax
	
;从高特权级内核态核心代码段 跳转到 低特权级用户态任务代码段
;模拟操作系统内核加载执行 应用程序
	
	;将关键寄存器压入栈
	;将低特权级用户态任务代码段的栈信息 压入到 当前内核态核心代码段的高特权级栈中
    push TaskAStack32Selector ;栈选择子
    push TaskATopOfStack32 	  ;栈顶地址
	;将低特权级用户态任务代码段的选择子,即入口地址 压入到 当前内核态核心代码段的高特权级栈中
    push TaskACode32Selector
	;将低特权级用户态任务代码段中目标代码的 偏移地址 0 压入 当前内核态核心代码段的高特权级栈中
    push 0
	;跳转到 低特权级用户态任务代码段,此时就会发生特权级转移
    retf

Code32SegLen    equ    $ - CODE32_SEGMENT

[section .gs]
[bits 32]
STACK32_SEGMENT:

    times 1024 * 4 db 0

Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32  equ Stack32SegLen - 1


; ==========================================
;
;          Global Function Segment  
; 模拟内核系统函数,从操作系统的角度看 这就是内核中的函数
; ==========================================

[section .func]
[bits 32]
FUNCTION_SEGMENT:

; ds:ebp    --> string address
; bx        --> attribute
; dx        --> dh : row, dl : col
PrintStringFunc:
    push ebp
    push eax
    push edi
    push cx
    push dx

print:
    mov cl, [ds:ebp]
    cmp cl, 0
    je end
    mov eax, 80
    mul dh
    add al, dl
    shl eax, 1
    mov edi, eax
    mov ah, bl
    mov al, cl
    mov [gs:edi], ax
    inc ebp
    inc dl
    jmp print

end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp

    retf
    
PrintString    equ   PrintStringFunc - $$



FunctionSegLen    equ   $ - FUNCTION_SEGMENT



; ==========================================
;
;            Task A Code Segment 
;
; ==========================================

[section .task-a-ldt]

; Task A LDT definition
;                                             段基址,                段界限,                段属性

TASK_A_LDT_ENTRY:
TASK_A_CODE32_DESC    :    Descriptor          0,           TaskACode32SegLen - 1,        DA_C + DA_32 + DA_DPL3
TASK_A_DATA32_DESC    :    Descriptor          0,           TaskAData32SegLen - 1,        DA_DR + DA_32 + DA_DPL3
TASK_A_STACK32_DESC   :    Descriptor          0,           TaskAStack32SegLen - 1,       DA_DRW + DA_32 + DA_DPL3
TaskALdtLen  equ   $ - TASK_A_LDT_ENTRY
; Task A LDT Selector

TaskACode32Selector  equ   (0x0000 << 3) + SA_TIL + SA_RPL3
TaskAData32Selector  equ   (0x0001 << 3) + SA_TIL + SA_RPL3
TaskAStack32Selector equ   (0x0002 << 3) + SA_TIL + SA_RPL3

[section .task-a-dat]
[bits 32]
TASK_A_DATA32_SEGMENT:
    TASK_A_STRING        db   "This is Task A!", 0
    TASK_A_STRING_OFFSET equ  TASK_A_STRING - $$

TaskAData32SegLen  equ  $ - TASK_A_DATA32_SEGMENT


;用户态低特权级任务代码段的 栈 大小1024Byte
[section .task-a-gs]
[bits 32]
TASK_A_STACK32_SEGMENT:
    times 1024 db 0

TaskAStack32SegLen  equ  $ - TASK_A_STACK32_SEGMENT
;栈顶
TaskATopOfStack32   equ  TaskAStack32SegLen - 1


;模拟用户态低特权级 任务代码段
[section .task-a-s32]
[bits 32]
TASK_A_CODE32_SEGMENT:  

	;设置 用户态任务数据段寄存器
    mov ax, TaskAData32Selector
    mov ds, ax
	
	;打印函数参数,通过寄存器传参,没有用栈传参
    mov ebp, TASK_A_STRING_OFFSET
    mov bx, 0x0c
    mov dh, 14
    mov dl, 29
    ;通过调用门描述符的选择子 调用高特权级的目标函数
	;说明 这里的0是语法需要 表示是段间跳转 0本身无意义,但是不能删除
	;如果删除后 就变成了 call FuncPrintStringSelector,成了段内跳转,意义本身发生变化,所以必须留着
	;通过调用门描述符的选择子 调用高特权级的目标函数 那么此时就会发生特权级转移,而特权级转移的时候栈
	;也会变化,所以我们就要找到高特权级的栈信息,到TSS中去找高特权级的栈信息
    call FuncPrintStringSelector : 0
    jmp $
TaskACode32SegLen   equ  $ - TASK_A_CODE32_SEGMENT

在这里插入图片描述

关于显存段特权级设置为3的合理性分析:
将显存段特权级设置为3,即便应用程序向显存段中写入了乱七八糟的数据,后果最多就是屏幕上显示乱七八糟的数据,不会有更严重的后果,可以接受

将机器码 反编译成 32位的汇编代码
ndisasm -b 32 -o 0x9000 loader > loader.txt

可以定位到 特权级切换前后,查看关键寄存器值 CS寄存器值,CS寄存器最低2位 为CPL 即特权级。可证明跳转成功。

在这里插入图片描述
如果说 当前所执行的代码段的 CPL 为 0,那是不是就意味着 他可以访问任何资源了呢?

答案是否定的,因为还要看它的请求特权级 RPL,如果太低 也是不能够访问对应资源的。

在这里插入图片描述

在这里插入图片描述
当前代码想要访问某个放在数据段中的资源,于是就要去做请求,通过选择子进行请求,处理器通过 CPL RPL DPL 共同确定该请求是否合法,如果合法就放行,可以访问数据段中的资源。

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux老A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值