我应该在哪里使用 "swapgs" 指令

Where I should use "swapgs" instruction

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

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

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


现在我的第一个问题是:

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

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


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

所以,特别是我应该使用 swapgs 指令的地方。

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

引用的数据结构中

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

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

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

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

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


最初设计 x86-64 时(早在 2000 年,比第一个硅还早几年),this mailing list message explained the intended purpose of swapgs. It was revised a day later 在 OS 之后,开发人员注意到 AMD 对其进行规范的方式存在问题,但最初电子邮件包含一个仍然适用的简单示例:

Example usage
At a kernel entry point the OS can use SwapGS to obtain a pointer to kernel data structures and simultaneously save the user's GS base. Upon exit it can use SwapGS to restore the user's GS base:

  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 内核源代码中的一些评论指出,确保 swapgs 在内核外的每条执行路径上只运行一次可能很不方便。如果有两个操作码,一个用于“swap to user gs”,一个用于“swap to kernel gs”,则更容易确保您不会不小心交换额外的时间。该错误会使下一个内核条目在错误的位置查找。 (并给 user-space 错误的 gs,但在 GNU/Linux fs 中用于线程本地存储。)