¿Cuál es entonces el uso de GS?
x86_64 El kernel de Linux usa el registro GS como una forma eficiente de adquirir la pila de espacio del kernel para las llamadas al sistema.
El registro GS almacena la dirección base para el área por CPU. Para adquirir la pila de espacio del kernel, en entry_SYSCALL_64
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
Después de expandir PER_CPU_VAR, obtenemos lo siguiente:
movq %gs:cpu_current_top_of_stack, %rsp
Para responder realmente a su fs:0
pregunta:La ABI x86_64 requiere que fs:0
contiene la dirección "señalada" por fs
sí mismo. Es decir, fs:-4
carga el valor almacenado en fs:0 - 4
. Esta función es necesaria porque no puede obtener fácilmente la dirección a la que apunta fs
sin pasar por el código del kernel. Tener la dirección almacenada en fs:0
por lo tanto, hace que trabajar con el almacenamiento local de subprocesos sea mucho más eficiente.
Puede ver esto en acción cuando toma la dirección de una variable local de subproceso:
static __thread int test = 0;
int *f(void) {
return &test;
}
int g(void) {
return test;
}
compila a
f:
movq %fs:0, %rax
leaq -4(%rax), %rax
retq
g:
movl %fs:-4, %eax
retq
i686 hace lo mismo pero con %gs
. En aarch64 esto no es necesario porque la dirección se puede leer desde el propio registro tls.
En x86-64 hay 3 entradas TLS, dos de ellas accesibles vía FS y GS, FS es usado internamente por glibc (en IA32 aparentemente FS es usado por Wine y GS por glibc).
Glibc hace que su punto de entrada TLS sea un struct pthread
que contiene algunas estructuras internas para roscar. Glibc generalmente se refiere a un struct pthread
variable como pd
, presumiblemente para pthread descriptor .
En x86-64, struct pthread
comienza con un tcbhead_t
(esto depende de la arquitectura, ver las macros TLS_DTV_AT_TP
y TLS_TCB_AT_TP
). Este encabezado de bloque de control de subprocesos, AFAIU, contiene algunos campos que son necesarios incluso cuando hay un solo subproceso. El DTV es el vector de subproceso dinámico y contiene punteros a bloques TLS para DSO cargados a través de dlopen()
. Antes o después de la TCB, hay un bloque TLS estático para el ejecutable y los DSO vinculados en el momento de la carga (del programa). TCB y DTV se explican bastante bien en el documento TLS de Ulrich Drepper (busque los diagramas en el capítulo 3).