【问题标题】:Where I should use "swapgs" instruction我应该在哪里使用“交换”指令
【发布时间】:2020-10-14 04:32:52
【问题描述】:

您好,我是一名内核学习者,对 swapgs 有一些疑问。

根据 AMD 的文档,它交换了 gs.base 隐藏寄存器和 KernelGSBase MSR。

此外,“gs:XXXX”的寻址计算为“gs.base + base + (scale*index) + 位移”


现在我的第一个问题是:

  1. gs.base 是段寄存器的隐藏部分
  2. 位移是“gs:XXXX”的“XXXX”部分
  3. index 可能是 gs 中的选择器索引

那我应该把“base”和“scale”存放在哪里?


另外,我应该在哪里使用它,我当前的项目将虚拟内存空间的上半部分作为内核,编译器通常不会添加“gs:XXXX”作为寻址参考。

那么,我应该特别在哪里使用swapgs 指令。

【问题讨论】:

  • base 和 scale 是内存访问指令中内存寻址方式的一部分。 MSR(和 gsbase)仅包含 gs 段的基数。

标签: assembly operating-system kernel x86-64 thread-local-storage


【解决方案1】:

没有隐藏的“基础和规模”,只有一个隐藏的gs.base,您可以在正常寻址模式下使用。 (以及 GS 寄存器本身的隐藏值。这是一个选择器值,如果您确实执行了 mov gs, eax 而不是仅通过 MSR 修改 GS 基数,它将充当 GDT 的索引,或通过wrgsbase。但这与完整gs:[base + index*scale]寻址模式中偏移量的索引部分无关。

您在内核的syscall 入口点处理程序中使用swapgs,然后在某些加载和存储上使用GS 段覆盖,就像线程本地存储一样,因此使用以前隐藏的gs.base 使用您在每个加载或存储指令中使用的[base + idx*scale] 寻址模式。例如类似mov [gs:0x10], rsp 来保存用户空间堆栈指针和mov rsp, [gs:0x18] 来加载内核堆栈指针。

swapgs 存在是因为syscall 不会将 RSP 更改为指向内核堆栈(并且不会将用户空间 RSP 保存在任何地方)。因此,您需要某种线程本地(或实际上是内核本地)存储,以便每个内核都可以为在该内核上运行的任务获取正确的内核堆栈指针。隐藏的 GS 基址是该隐藏指针的存储空间,也是一种在不破坏任何架构寄存器值的情况下使用它的方法。

您不能只使用常规全局变量(绝对地址),因为它只能有一个所有内核都可以读取的值。您也没有任何备用寄存器(它们都包含宝贵的用户空间状态,您需要稍后恢复),并且您没有内核堆栈来推送它们。而且您不能使用用户空间 RSP;在内核模式下使用用户空间 RSP 运行 push 会使用户空间崩溃内核,因为在运行 syscall 之前 RSP 指向无效。


最初设计 x86-64 时(早在 2000 年,比第一个芯片还早几年),this mailing list message 解释了swapgs 的预期用途。在操作系统开发人员注意到 AMD 指定它的方式存在问题之后,它是 revised a day later,但原始电子邮件包含一个仍然适用的简单示例:

使用示例
在内核入口点,操作系统可以使用 SwapGS 获取指向内核数据结构的指针,同时保存 用户的 GS 基地。退出后可以使用 SwapGS 恢复用户的 GS 基地:

  SystemCallEntryPoint:
    SwapGS                        ; set up kernel pointer, save user's GS base    
    mov gs:[SavedUserRSP], rsp    ; save user's stack pointer
    mov rsp, gs:[KernelStackPtr]  ; set up kernel stack
    push rax                      ; now that we have a stack, save user's GPRs    
    mov rax, gs:[CPUnumber]       ; get CPU number     < or whatever >
    .                             ; perform system service
    .
    SwapGS                        ; restore user's GS, save kernel pointer

您可能还想查看 Linux 内核如何在其 syscall 入口点中使用它,最好是在 Spectre / Meltdown 缓解使一切变得复杂之前的旧内核中。例如Linux 4.12's entry_64.SENTRY(entry_SYSCALL_64)swapgs 开头,非常像 AMD 的例子。

(另请参阅Why does Windows64 use a different calling convention from all other OSes on x86-64?,了解其他 Linux 内核入口点发生的情况,来自int 0x80)。

Linux 内核源代码中的一些 cmets 指出,确保 swapgs 在内核外的每条执行路径上只运行一次可能很不方便。如果有两个操作码,一个用于“交换到用户 gs”,一个用于“交换到内核 gs”,则更容易确保您不会意外交换额外的时间。该错误会使下一个内核条目出现在错误的位置。 (并且给用户空间错误的 gs,但在 GNU/Linux 中 fs 用于线程本地存储。)

【讨论】:

    【解决方案2】:

    如果您的内核不使用 gs 来访问内核私有数据,那么您无需使用此指令。它旨在供内核在从用户模式进入内核时使用,以允许内核保存应用程序的 gsbase 并加载它自己的,而无需访问内存。然后内核可以将应用程序状态保存在通过 gs 引用的数据结构中。

    由于您显然已经构建了内核以能够在不使用 gs 的情况下保存来宾状态,因此您不需要使用 swapgs。您仍然可能希望在每次上下文切换时保存和恢复 fsbase 和 gsbase 以及应用程序的其余状态,以便每个用户进程都可以拥有自己的值。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-25
      • 2020-03-12
      • 2021-02-27
      相关资源
      最近更新 更多