我应该在哪里使用“swapgs”指令

Sho*_*ore 3 assembly operating-system kernel x86-64 thread-local-storage

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

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

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


现在我的第一个问题是:

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

那么我应该在哪里存储“基础”和“比例”?


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

所以,特别是,我应该在哪里使用swapgs指令。

Pet*_*des 8

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

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

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

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


在最初设计 x86-64 时(早在 2000 年,第一个硅片诞生之前的几年),此邮件列表消息解释了swapgs. 一天后,操作系统开发人员注意到 AMD 对其指定方式存在问题后对其进行了修订,但原始电子邮件包含一个仍然适用的简单示例:

示例用法
在内核入口点,操作系统可以使用 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
Run Code Online (Sandbox Code Playgroud)

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

(另请参阅为什么 Windows64 使用与 x86-64 上的所有其他操作系统不同的调用约定?有关其他 Linux 内核入口点中发生的情况的一些解释,来自int 0x80)。

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