Linux AMD64中如何使用fs/gs寄存器?

53

在x86-64架构中,有两个寄存器具有特殊用途:FS和GS。在linux 2.6.*中,FS寄存器似乎被用来存储线程本地信息。

  • 这是正确的吗?
  • fs:0存储什么?是否有任何C结构描述这个内容?
  • 那么GS有什么用途?
3个回答

52
在x86-64中,有3个TLS条目,其中两个可以通过FS和GS访问,FS在glibc内部使用(在IA32中,显然Wine使用FS,glibc使用GS)。
Glibc将其TLS条目指向包含某些线程内部结构的struct pthread。 Glibc通常将struct pthread变量称为pd,可能是因为pthread描述符
在x86-64上,struct pthreadtcbhead_t开头(这取决于架构,请参见宏TLS_DTV_AT_TPTLS_TCB_AT_TP)。据我所知,此线程控制块头包含一些即使只有一个线程也需要的字段。DTV是动态线程向量,包含通过dlopen()加载的DSO的TLS块的指针。在TCB之前或之后,有一个静态TLS块,用于可执行文件和在(程序的)加载时间链接的DSO。TCB和DTV在Ulrich Drepper的TLS文档中得到了很好的解释(请查看第3章中的图表)。

2
FS 用于 win32 在 x86 上指向 Windows 线程信息 -- Wine 只是在匹配它。 - Chris Dodd
TLS_DTV_AT_TP 是 Drepper 文档(第 5-6、8 页)中所称的“变体 I”,他指出这是为 IA-64(Itanium)发明的;TLS_TCB_AT_TP 是他所称的“变体 II”。在“变体 I”中,DTV 指针始终位于 TCB 偏移量 0 处,并且编译器可以假定它在那里;在“变体 II”中,它可以位于任意偏移量,编译器无法知道,因此编译器必须依赖于运行时库 __tls_get_addr 函数。在 Linux 上,所有新架构都使用 Variant I,旧架构(x86_64、i386、s390、s390x 和 SPARC)使用 Variant II,以实现向后兼容性。 - Simon Kissane

25

实际回答您的fs:0问题:x86_64 ABI要求fs:0包含fs本身所“指向”的地址。也就是说,fs:-4会加载在fs:0-4处存储的值。这个特性很必要,因为您不能轻易地通过内核代码获取fs所指向的地址。因此,在fs:0中存储地址可以使使用线程本地存储更加高效。

当您获取线程本地变量的地址时,可以看到这一点:

static __thread int test = 0;

int *f(void) {
    return &test;
}

int g(void) {
    return test;
}

编译成

f:
    movq    %fs:0, %rax
    leaq    -4(%rax), %rax
    retq

g:
    movl    %fs:-4, %eax
    retq

i686使用%gs执行相同的操作。在aarch64上,这是不必要的,因为地址可以直接从tls寄存器中读取。


1
请问您能提供“x86_64 ABI要求fs:0包含由fs本身指向的地址”的来源吗? - Zhani Baramidze
1
显然,这在文档《ELF处理线程局部存储》中被ABI引用。%fs:0在第4.3.6节和第4.4.6节中描述了两种不同的模型。 - Nate Eldredge

7

GS的作用是什么?

x86_64 Linux内核使用GS寄存器来高效地获取系统调用的内核空间堆栈。

GS寄存器存储每个CPU区域的基地址。为了获取内核空间堆栈,需要在entry_SYSCALL_64中进行操作。

movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp

扩展PER_CPU_VAR后,我们得到以下结果:
movq    %gs:cpu_current_top_of_stack, %rsp

9
当然,在执行swapgs之后,它会将gs段基址设置为内核的GS值,而不是用户空间的值。从用户空间进入时,%gs仍然是用户空间设定的值!(AMD设计了swapgssyscall以便像这样一起使用。) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接