使用 TSS selector 进行任务切换

转自:点击打开链接

指令:

  call 0x20:00000000
  jmp 0x20:00000000
-----------------------------------
  selector 0x20 是个 TSS descriptor 的 selector。


  TSS descriptor 与 segment descriptor 的格式一致,不同的是 type,在 x86 和 long mode 的 compatibility 模式下有: available/busy 16-TSS 和 available/busy 32-TSS。
  当成功将 TSS descriptor 加载到 TR 寄存器后,processor 将 available TSS 置为 busy TSS,在返回时将 busy TSS 置为 available TSS。


1、索引查找 TSS descriptor
  TSS selector 在 GDT 里索引 TSS descriptor,其方法和 segment / gate dscriptor 的查找一致。



2、TSS 的 limit & type check

  TSS segment 大小必须能容纳所有的 processor state,整个 TSS segment 的大小是 104 个字节,在加载 TSS descriptor 进入 TR 之前,processor 检查 TSS descriptor 中的 limit 必须大于或等于 67h(104 字节)。若否,则产生 #TS 异常。
  processor 也检查 TSS descriptor 的 type 是否为 available 类型。若为 busy 类型,则产生 #GP 异常。


if (temp_descriptor.type == BUSY)
{
  goto do_#GP_exception;
}

if (temp_descriptor.limit < 0x67)
{
  goto do_#TS_exception;
}




3、权限的 check

  所需的权限是:CPL <= DPL && RPL <= DPL

if (RPL <= DPL && CPL <= DPL) {
   /* 通过,允许访问 */
} else {
   /* 失败,#GP 异常 */
}





4、保存 old-task 的 processor  state

  在执行加载 TSS descriptor 到 TR 之前,processor 将执行一项很重要的工作:保存 old-task 的执行环境(processor 的状态)在当前的 TSS segment 中。
  当前的 TSS segment 就是 old-task 的 TSS segment,在没加载 new-task 的 TSS 进入 TR 之前。

执行环境包括:
(1)GPRs
(2)segment registers
(3)Eflags
(4)LDTR 和 CR3
(5)EIP



5、加载 TSS descriptor

  通过一系列的 processor 检查后,processor 加载 TSS descriptor 进入 TR 寄存器。

TR.selector = TSS_selector;
TR.limit = temp_descriptor.limit;
TR.base = temp_descriptor.base;
TR.attribute = temp_descriptor;

TSS_descriptor.type = BUSY_32;

CR0.TS = 1;


TSS descriptor 加载到 TR 寄存器相应的域。
并且,processor 将 TSS descriptor 的 type 置为 BUSY 状态,防止再调用该 TSS descriptor
CR0.TS 也被置为 1,防止在 task 切换时执行 x87 or SSE(media)指令。



6、加载 processor state 信息

  TSS descriptor 加载进 TR,processor 将加载新任务保存在 TSS 的 processor 状态信息。
包括:
(1)GPRs 加载
(2)segment registers 加载
(3)Eflags 加载
(4)LDTR 和 CR3 加载



7、设置 EFLAGS.NT 和 TSS.link 域

  使用 call 指令调用 TSS selector,processor 将置 Eflags.NT 为 1,表示当前的 task 被嵌套调用。而使用 jmp 指令则不会置 Eflags.NT 为 1。

if (opcode == CALL) {           /* 使用 call 指令 */
  Eflags.NT = 1;                 /* 置 NT 位为 1 */
  TSS.link = old_TSS;         /* link 域为 old task 的 TSS selector */
}


  jmp 指令不支持 Task-Nesting 因此不改变 Eflags.NT 域,使用 call 指令还将当前 TSS 的 link 域设为 old-task 的 TSS selector,这样,当前使用 iret 指令返回时,将通过 old-task 的 TSS selector 返回到 old-task。





7、执行新 task

  同样,processor 加载新的 task EIP 进入 EIP 寄存器,执行新的 task 代码。新的 CS 被加载时,CPL 就是新的 CS.RPL。




8、返回 old-task

  new-task 执行完毕通过 ret 或 iret 指令返回到 old-task。

在 caller 中:

... ...
  call 0x20:0x0000000
next:
... ...

使用 call TSS_selector 来切换到 new-task。

(1)使用 ret 指令返回
  在 new-task 中使用 ret 指令返回和一般的 caller / callee 之间返回并无两样。程序将返回到 next 处。

(2)使用 iret 指令返回
  使用 Eflags.NT 的目的是让 iret 来返回,当使用 iret 指令返回时,processor 将检查 Eflags.NT 是否为 1 ,若 Eflags.NT = 1 则表示当前 task 被嵌套。
  程序将返回到 TSS.link 的 TSS selector 指向的 TSS descriptor,这个 TSS descriptor 就是 old-task 的 TSS descriptor,这里又进行另一次的任务切换到 old-task。
----------------------------------------------------------
  使用 ret 与 iret 的不同就是:用 ret 返回时,当前的 processor 还是当前的 state,使用 iret 返回时,将加载保存在 TSS 的 processor state。
  也就是说,ret 返回 old-task 还是沿用 new-task 的 processor 状态。iret 返回到 old-task 使用的是原来保存在 old-task 的 TSS 的 processor 状态。


使用的 iret 的另一个功能:
  当 Eflags.NT = 1 时:由于 iret 将返回到 TSS.link 的 TSS selector,进行一次任务切换。若这个 TSS selector 是刻意构造的别有用途的 TSS 时,可以进行一次别有用途的 task 切换。






7.1.3.4.1、 long mode 下的 TSS 切换情形

  在 long mode 下已经不支持使用 TSS 来切换 task ,包括 TSS selector 和 task gate。使用 call / jmp TSS_selector 将产生 #GP 异常。
<think>嗯,用户遇到了保护模式下的错误提示:“selector 0x0008 points to a system descriptor and is not supported”。我需要先理解这个错误的含义。保护模式是x86架构中的一种运行模式,允许系统使用更多的内存和高级功能。段选择子0x0008可能指向全局描述符表(GDT)中的一个条目,但这里提示说这是一个系统描述符,而当前环境不支持。 首先,段选择子的结构是怎样的呢?高13位是索引,接着是TI(表指示符,0表示GDT,1表示LDT),最后两位是请求特权级(RPL)。0x0008转换成二进制是0000 0000 0000 1000,所以索引是1(因为前13位是0000000000001),TI=0,RPL=00。这意味着它指向GDT中的第二个条目(索引从0开始)。GDT中的第一个条目通常是空描述符,第二个通常是代码段描述符。 错误提示说这个描述符是系统描述符,而当前不支持。系统描述符可能指的是任务状态段(TSS)、局部描述符表(LDT)或者调用门等。如果原本期望的是代码段或数据段描述符,但GDT中的第二个条目被错误地配置成了系统描述符类型,就会导致这个问题。 可能的原因有几个:1. GDT设置错误,错误地将代码段描述符的类型设置为系统类型。2. 在切换到保护模式时,加载了错误的段选择子。3. 在进入保护模式后,访问了需要系统描述符的操作,但当前模式不支持。 解决方案方面,首先需要检查GDT的配置。确保代码段和数据段描述符的类型是正确的。代码段描述符的类型应为0x9A(存在,DPL 0,代码段,可读),数据段为0x92(存在,DPL 0,数据段,可写)。如果GDT条目类型错误,比如设置成了系统描述符类型(如TSS的类型是0x89),就会触发错误。 其次,检查切换到保护模式时的代码流程。正确步骤是:创建GDT,禁用中断,加载GDT,设置CR0的PE位,执行远跳转以刷新流水线,然后加载数据段寄存器,最后重新启用中断。如果在设置CR0后没有正确跳转,可能导致段选择子未被正确加载。 另外,检查是否在进入保护模式后尝试执行特权指令或访问需要更高权限的资源,这可能导致错误。例如,如果当前特权级(CPL)与描述符的DPL不匹配,也会引发异常。 用户可能需要查看他们的GDT初始化代码,确认每个描述符的类型字段是否正确。同时,检查进入保护模式后的第一条指令是否正确使用了远跳转来加载CS寄存器。此外,使用调试工具如Bochs或QEMU的调试功能来单步执行代码,观察在加载段选择子时寄存器的状态和GDT的内容,有助于定位问题。 如果用户是在开发操作系统或低级系统软件,可能还需要参考Intel或AMD的架构手册,确保所有描述符的格式和类型符合处理器的要求。有时候,描述符中的某些保留位被错误设置也会导致不可预知的问题。</think>### 保护模式错误解决方案:段选择子0x0008指向系统描述符 #### 错误分析 该错误通常发生在操作系统内核或引导程序尝试进入x86保护模式时,全局描述符表(GDT)配置存在问题。具体原因为: 1. **段选择子0x0008**指向的GDT条目被错误标记为系统描述符(如TSS/LDT描述符) 2. 当前CPU运行级别(如长模式或特定特权级)不支持系统描述符类型 3. GDT表项的类型字段(Type Field)设置错误[^1] #### 分步解决方案 1. **检查GDT结构** 确保代码段描述符类型为标准的非系统描述符: ```asm ; 正确示例:32位代码段描述符 (Type=0x9A) gdt_entry: dw 0xFFFF ; Limit 0-15 dw 0x0000 ; Base 0-15 db 0x00 ; Base 16-23 db 0x9A ; P=1, DPL=0, Type=1010 (非系统描述符) db 0xCF ; G=1, D/B=1, Limit 16-19=0xF db 0x00 ; Base 24-31 ``` 2. **验证保护模式切换流程** 使用标准初始化序列: ```asm cli lgdt [gdt_descriptor] mov eax, cr0 or eax, 0x1 mov cr0, eax jmp 0x08:protected_mode ; 关键:远跳转加载CS寄存器 ``` 3. **使用调试工具验证** 通过Bochs/QEMU查看GDT内容: ```bash # QEMU调试命令 qemu-system-x86_64 -d int,cpu_reset -no-reboot ``` #### 常见错误模式 | 错误位置 | 错误示例 | 修正方法 | |----------------|------------------|--------------------------| | 描述符类型字段 | Type=0x89 (TSS) | 改为0x9A(代码段) | | 段限长设置 | Limit=0x0000 | 设为完整4GB空间0xFFFFF | | 特权级设置 | DPL=3 | 内核代码段应设为DPL=0 | #### 扩展验证 若问题仍未解决,建议: 1. 检查内存地址对齐是否符合GDT要求 2. 确认未在实模式下意外修改GDT内容 3. 使用`sgdt`指令验证GDTR寄存器值 4. 测试最小化GDT配置(仅包含空描述符+代码段+数据段)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值