特权级由低到高转移

简介: 特权级由低到高转移

引言

  • 直接以一个实验来引入本章节内容

实验目标

  • 定义 32 位核心代码段和数据段特权级为 0
  • 定义 32 为任务代码段和数据段特权级为 3
  • 由核心代码段跳转到任务段(高->低)
  • 在任务段中调用高特权级代码段打印字符串
  • 打印完成后再由高特权级代码段返回任务段(低->高)

目标分析

  • 像不像应用程序调用系统函数的情况?不是像,他就是系统调用的一种实现方法
  • 用图形的形式更直观的理解

  • 这上面这张图中,从右向左(高特权级->低特权级)的实现过程我们已经在 特权级转移(高->低) 中做过一定的讲解
  • 而从左向右(低特权级->高特权级)的实现过程我们也在 调用门 中有了一定的了解,但是当时做的实验是相同特权级之间通过调用门实现跳转
  • 现在特权级不同了,想要从低特权级跳转到高特权级代码段,还有一个知识点需要了解,那就是任务状态段 TSS

初识别任务状态段 TSS

  • 英文全称:Task State Segment
  • TSS 是处理器所提供的一种硬件数据结构,它保存了关键寄存器的值以及不同特权级使用的栈等数据
  • 它是处理器在硬件上原生支持多任务的一种实现方式,也就是说处理器原本是想让操作系统开发厂商利用此结构实现多任务的,人家处理器厂商已经提供了多任务管理的解决方案,尽管后来操作系统并不买账,这是后话
  • 不同的特权级需要使用不同的栈,每一个特权级对应一个私有栈
  • 使用 TSS 数据结构,处理器可以从一个任务切换到另一个任务,同时保存原任务的上下文,等状态切换回来的时候再恢复到切换前的状态
  • 32 位任务状态段 TSS 格式

特权级转移时 TSS 中栈的变化

  • 首先看上图中看一下 TSS 中各特权级栈的信息
  • 特权级 0 :SS0, ESP0
  • 特权级 1 :SS1, ESP1
  • 特权级 2 :SS2, ESP2
  • 低特权级->高特权级(调用门)
  • 从 TSS 中获取高特权级目标栈段
  • 将低特权级栈信息压入高特权级栈中(ss 和 esp)
  • 高特权级->低特权级(retf)
  • 将低特权级栈信息从高特权级栈中取出并恢复到 ss 和 esp 寄存器中

有趣的问题

  • 问题 1:为啥不同的特权级要使用不同的栈?
  • 要是不同的特权级可以使用相同的栈,那么应用程序(特权级 3)对栈中数据的修改是不是会影响内核(特权级 0)的运行了。要是应用程序故意搞破坏呢,还不把系统给干崩了啊,操作系统怎么可能会留这么到的漏洞呢
  • 问题 2:TSS 中为什么只保存 3 个特权级(0、1、2)的栈信息?
  • 当低特权级 3 跳转到高特权级 0 或 1 或 2 时,特权级 3 的栈信息 ss3 和 esp3 会被压入对应的高特权级栈中保存,而当从高特权级 0 或 1 或 2 跳回低特权级 3 时,原先被保存的 ss3 和 esp3 又会被从高特权级栈中取出恢复,既然 ss3 和 esp3 会压栈到高特权级的栈中保存,那么完全没必要再重复指定一个保存 ss3 和 esp3 的地方,想要的时候直接从高特权级栈中出栈即可
  • 问题 3:虽然 Intel 专门为进程切换提供了 TSS 一系列解决方案,但是各家操作系统并不买账,没人用(仅使用其中的 ss0 和 esp0),原因有两个
  • 一是操作系统厂商并不希望把自己的产品绑定在 Intel 处理器上,所有的操作系统都会考虑,如果以后有新的处理器出来,操作系统要能够运行在新的处理器上
  • 二是 Intel 提供的方案其进程切换效率比较低

准备工作

  • 在之前章节中实现的代码 loader.asm 基础上进行改动实验
  • 先来回顾一下这个基础代码的框架
boot.asm 跳转到 loader.asm,并打印 “Welcome to KOS.” 
    |
CODE16_START          ; 实模式,打印 “Loader...”
    |
CODE32_START          ; 进入保护模式,通过调用门打印 “Enter protection”
    |
TASK_A_CODE32_SEGMENT ; 跳转到局部代码段,通过调用门打印 “Task A”
  • 先上实验成功后的完整代码:loader.asm
  • 我们先把 Task A 相关特权级改为 3,用来模拟低特权级的应用程序
; 局部段描述符
TASK_A_CODE32_DESC : Descriptor 0, TASK_A_CODE32_SEG_LEN - 1, DA_C + DA_32 + DA_DPL3
TASK_A_DATA32_DESC : Descriptor 0, TASK_A_DATA32_SEG_LEN - 1, DA_DR + DA_32 + DA_DPL3
TASK_A_STACK32_DESC : Descriptor 0, TASK_A_STACK32_SEG_LEN - 1, DA_DRW + DA_32 + DA_DPL3
; 局部段选择子
TASK_A_CODE32_SELECTOR equ (0x0000 << 3) + SA_RPL3 + SA_TIL
TASK_A_DATA32_SELECTOR equ (0x0001 << 3) + SA_RPL3 + SA_TIL
TASK_A_STACK32_SELECTOR equ (0x0002 << 3) + SA_RPL3 + SA_TIL
  • 有一个地方还需要改一下,那就是调用门要被低特权级 3 所调用,那么调用门的特权级也要改为 3

; 门描述符

FUNC_PRINT_DESC : Gate FUNC_SELECTOR, printOffset, 0, DA_CALL_GATE + DA_DPL3  

; 选择子

FUNC_PRINT_SELECTOR    equ   (0x0007 << 3) + SA_RPL3 + SA_TIG

实验开始

  • 首先程序需要从核心代码段 CODE32_START (特权级 0)跳转到任务Task A (特权级 3)
; jmp TASK_A_CODE32_SELECTOR:0
push TASK_A_STACK32_SELECTOR
push TASK_A_TOP_OF_STACK32
push TASK_A_CODE32_SELECTOR
push 0
retf
  • 原理这里就不再说明了,可以参考 特权级转移(高->低)
  • 接下来实现低特权级代码段跳转到高特权级代码段(任务 Task A 调用全局函数段 FUNCTION_SEGMENT 中的打印函数 print_str_32)
  • 前面刚说过,想要从低特权级跳转到高特权级代码段,不光需要调用门,还需要任务状态段 TSS
  • 那么这个 TSS 是如何使用的呢?
  • 首先我们先定义出 TSS 数据结构(参照上面 32 位任务状态段 TSS 格式示意图)
TSS_SEGMENT:
  dd    0
  dd    TOP_OF_STACK32      ; esp0
  dd    STACK32_SELECTOR    ; ss0
  dd    0                   ; esp1
  dd    0                   ; ss1
  dd    0                   ; esp2
  dd    0                   ; ss2
  times 4 * 18 dd 0
  dw    0
  dw    $ - TSS_SEGMENT + 2
  db    0xFF
TSS_LEN    equ    $ - TSS_SEGMENT
  • 在这个实验中我们只用到了 esp0 和 ss0,所以这两个值要条充好
  • 其余用不到的暂时先填充数据 0
  • 最后 3 个字节数据暂时先不深究,先这么写,后面再做讲解
  • 定义好了这个数据结构接下来怎么办呢?
  • 老套路了,先将其加载到处理器 (ltr)
mov ax, TSS_SELECTOR
ltr ax
  • 再定义对应的描述符和选择子
; 描述符
TSDESC : Descriptor 0, TSS_LEN -1, DA_TSS + DA_DPL0 
; 选择子
TSS_SELECTOR  equ   (0x0007 << 3) + SA_RPL0 + SA_TIG
  • 别忘了初始化一下段基址
mov esi, TSS_SEGMENT
mov edi, TSDESC
call InitDescItem
  • 好了,改动完毕,运行一下试试
  • 结果,只打印了 “Welcome to KOS.” 、“Loader...”、“Enter protection”,并没有打印出 “Task A” ,程序崩溃了
  • 没有什么好办法,只能反汇编后断点调试了
  • 经过长时间摸索,发现程序每次运行到 mov [gs:edi], ax 这条指令后就会崩溃
  • 看一下这条指令是干嘛,这条指令是将 Task A 中数据拷贝到显存里去,仔细推敲一下,显存的特权级 DPL 为 0,你能把特权级 3 的数据拷贝到特权级 0 的段中吗?处理器显然不允许你那么干
  • 于是,我们把显存段特权级也改为 3,果然成功打印出了 “Task A”

; VIDEO_DESC : Descriptor 0xB8000, 0xBFFFF - 0xB8000, DA_DRWA + DA_32

VIDEO_DESC : Descriptor 0xB8000, 0xBFFFF - 0xB8000, DA_DRWA + DA_32 + DA_DPL3

  • 那么,我们把显存段特权级改为 3 合适吗?
  • 显存段唯一的作用就是显示,就算应用程序胡乱往显存里放数据,顶多画面显示异常,并不会影响处理器功能的正常执行。所以,我们把显存段特权级改为 3 也是合理的
目录
相关文章
|
2月前
|
负载均衡 网络协议 Unix
Nginx负载均衡与故障转移实践
Nginx通过ngx_http_upstream_module模块实现负载均衡与故障转移,适用于多服务器环境。利用`upstream`与`server`指令定义后端服务器组,通过`proxy_pass`将请求代理至这些服务器,实现请求分发。Nginx还提供了多种负载均衡策略,如轮询、权重分配、IP哈希等,并支持自定义故障转移逻辑,确保系统稳定性和高可用性。示例配置展示了如何定义负载均衡设备及状态,并应用到具体server配置中。
|
3月前
|
运维 监控 定位技术
故障转移和自动恢复
故障转移和自动恢复
|
3月前
|
存储 缓存 运维
无状态故障转移与有状态故障转移
【8月更文挑战第24天】
30 0
|
4月前
|
消息中间件 运维 监控
中间件故障转移主-备配置
【7月更文挑战第25天】
37 2
|
4月前
|
运维 监控 Kubernetes
中间件故障转移自动切换
【7月更文挑战第25天】
40 2
|
4月前
|
运维 监控 算法
中间件故障转移故障检测
【7月更文挑战第25天】
45 1
|
6月前
|
芯片
特权级由高到低转移
特权级由高到低转移
62 0
|
6月前
|
安全
深入特权级转移
深入特权级转移
56 0
|
存储 运维 负载均衡
RH236配置IP故障转移--CTDB
RH236配置IP故障转移--CTDB
903 0
RH236配置IP故障转移--CTDB