53

On the x86-64 architecture, two registers have a special purpose: FS and GS. In linux 2.6.*, the FS register seem to be used to store thread-local information.

  • Is that correct?
  • What is stored at fs:0? Is there any C structure that describe this content?
  • What is then the use of GS?
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
BP8467
  • 1,949
  • 3
  • 15
  • 17

3 Answers3

52

In x86-64 there are 3 TLS entries, two of them accesible via FS and GS, FS is used internally by glibc (in IA32 apparently FS is used by Wine and GS by glibc).

Glibc makes its TLS entry point to a struct pthread that contains some internal structures for threading. Glibc usually refers to a struct pthread variable as pd, presumably for pthread descriptor.

On x86-64, struct pthread starts with a tcbhead_t (this depends on the architecture, see the macros TLS_DTV_AT_TP and TLS_TCB_AT_TP). This Thread Control Block Header, AFAIU, contains some fields that are needed even when there is a single thread. The DTV is the Dynamic Thread Vector, and contains pointers to TLS blocks for DSOs loaded via dlopen(). Before or after the TCB there is a static TLS block for the executable and DSOs linked at (program's) load time. The TCB and DTV are explained pretty well in Ulrich Drepper's TLS document (look for the diagrams in chapter 3).

ninjalj
  • 42,493
  • 9
  • 106
  • 148
  • 2
    FS is used by win32 on x86 to point at the windows thread info -- wine is just matching that. – Chris Dodd Sep 08 '16 at 23:45
  • `TLS_DTV_AT_TP` is what Drepper's document (p. 5-6, 8) calls "Variant I", which he notes was invented for IA-64 (Itanium); `TLS_TCB_AT_TP` is what he calls "Variant II". In "Variant I", the DTV pointer is always at TCB offset 0, and the compiler is allowed to assume that; in "Variant II", it can be at an arbitrary offset, which the compiler can't know, so the compiler must rely on the runtime library `__tls_get_addr` function. On Linux, all new architectures use Variant I, Variant II is used by older architectures (x86_64, i386, s390, s390x and SPARC) due to backward compatibility. – Simon Kissane Feb 12 '23 at 22:29
25

To actually answer your fs:0 question: The x86_64 ABI requires that fs:0 contains the address "pointed to" by fs itself. That is, fs:-4 loads the value stored at fs:0 - 4. This feature is necessary because you cannot easily get the address pointed to by fs without going through kernel code. Having the address stored at fs:0 thus makes working with thread local storage much more efficient.

You can see this in action when you take the address of a thread local variable:

static __thread int test = 0;

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

int g(void) {
    return test;
}

compiles to

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

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

i686 does the same but with %gs. On aarch64 this is not necessary because the address can be read from the tls register itself.

MuhKarma
  • 705
  • 7
  • 13
  • 1
    Can you please privide source for "The x86_64 ABI requires that fs:0 contains the address "pointed to" by fs itself" ? – Zhani Baramidze Mar 28 '18 at 14:33
  • 1
    Apparently this is in the document "[ELF Handling for Thread-Local Storage](https://akkadia.org/drepper/tls.pdf)" which the ABI references. `%fs:0` is described for two different models in sections 4.3.6 and 4.4.6. – Nate Eldredge Oct 13 '21 at 00:44
7

What is then the use of GS?

x86_64 Linux kernel uses GS register as a efficiency way to acquire kernel space stack for system calls.

GS register stores the base address for per-cpu area. To acquire the kernel space stack, in entry_SYSCALL_64

movq    PER_CPU_VAR(cpu_current_top_of_stack), %rsp

After expanding PER_CPU_VAR, we get the following:

movq    %gs:cpu_current_top_of_stack, %rsp
firo
  • 1,002
  • 12
  • 20
  • 9
    It does this after `swapgs`, of course, to set the gs segment base to the *kernel's* GS value, not user-space's value. On entry from user-space, `%gs` is still whatever user-space set it to! (`swapgs` and `syscall` were designed by AMD to be used together like this.) – Peter Cordes Sep 18 '18 at 10:50