在x86-64架构中,两个寄存器具有特殊用途:FS和GS。在linux 2.6。*中,FS寄存器似乎用于存储线程本地信息。
在x86-64中有qazxsw poi,其中两个可通过qazxsw poi访问,FS由glibc内部使用(在IA32中显然是3 TLS entries)。
Glibc将其TLS入口指向包含一些内部结构的FS and GS。 Glibc通常将FS is used by Wine and GS by glibc变量称为struct pthread
,大概是pthread描述符。
在x86-64上,struct pthread
以pd
开头(这取决于架构,请参阅宏struct pthread
和tcbhead_t
)。该线程控制块头(AFAIU)包含一些即使存在单个线程也需要的字段。 DTV是动态线程向量,包含指向通过TLS_DTV_AT_TP
加载的DSO的TLS块的指针。在TCB之前或之后,存在用于在(程序)加载时链接的可执行文件和DSO的静态TLS块。在TLS_TCB_AT_TP
中很好地解释了TCB和DTV(在第3章中查找图表)。
要真正回答你的dlopen()
问题:x86_64 ABI要求Ulrich Drepper's TLS document包含fs:0
本身“指向”的地址。也就是说,fs:0
加载存储在fs
的值。此功能是必要的,因为您无法轻松获取fs:-4
指向的地址而无需通过内核代码。将地址存储在fs:0 - 4
可以更有效地处理线程本地存储。
获取线程局部变量的地址时,您可以看到此操作:
fs
编译成
fs:0
i686使用static __thread int test = 0;
int *f(void) {
return &test;
}
int g(void) {
return test;
}
做同样的事情。在aarch64上,这不是必需的,因为可以从tls寄存器本身读取地址。
那么GS的用途是什么?
x86_64 Linux内核使用GS寄存器作为获取系统调用的内核空间堆栈的有效方法。
GS寄存器存储每个cpu区域的基址。要获取内核空间堆栈,请在entry_SYSCALL_64中
f:
movq %fs:0, %rax
leaq -4(%rax), %rax
retq
g:
movl %fs:-4, %eax
retq
扩展PER_CPU_VAR后,我们得到以下结果:
%gs