load_fs() and load_gs()
Kostik Belousov
kostikbel at gmail.com
Mon Jan 29 12:16:50 UTC 2007
On Fri, Jan 26, 2007 at 06:21:09PM -0500, Jung-uk Kim wrote:
> I have been chasing TLS problem for Linuxulator/amd64. The whole
> thing actually boils down to the following simulation:
>
> ----------------
> #include <stdio.h>
> #include <sys/types.h>
> #include <machine/cpufunc.h>
> #include <machine/sysarch.h>
>
> static __thread u_int tls = 0xdeadbeef;
>
> int
> main(void)
> {
> #if defined(__amd64__)
> u_int fs;
> uint64_t fsbase;
>
> fs = rfs();
> if (sysarch(AMD64_GET_FSBASE, &fsbase))
> return (-1);
> printf("fsbase = 0x%lx, %%fs: 0x%08x, tls = 0x%x\n",
> fsbase, fs, tls);
>
> /*
> * glibc does the following two calls.
> * Note: Actually we don't do anything here
> * but writing them back.
> */
> if (sysarch(AMD64_SET_FSBASE, &fsbase))
> return (-1);
> load_fs(fs);
According to Intel docs,
In 64-bit mode, memory accesses using FS-segment and GS-segment
overrides are not checked for a runtime limit nor subjected to
attribute-checking. Normal segment loads (MOV to Sreg and POP Sreg) into
FS and GS load a standard 32-bit base value in the hidden portion of the
segment descriptor register. The base address bits above the standard 32
bits are cleared to 0 to allow consistency for implementations that use
less than 64 bits.
So, by executing load_fs(fs), you effectively load some low (<= 2^32) value
into fs base (I suspect that it is just 0, since GUDATA_SEL has 0 as segment
base, see gdt_segs in amd64/machdep.c). And then,
mov %fs:0x0,%rax
instruction just dereferences 0 instead of TLS.
I suspect that Linux does not use that code sequence too, since behaviour
on the segment register load in 64-bit mode is defined by CPU.
>
> if (sysarch(AMD64_GET_FSBASE, &fsbase))
> return (-1);
> printf("fsbase = 0x%lx, %%fs: 0x%08x, tls = 0x%x\n",
> fsbase, rfs(), tls);
> #elif defined(__i386__)
> u_int gs;
> uint32_t gsbase;
>
> gs = rgs();
> if (sysarch(I386_GET_GSBASE, &gsbase))
> return (-1);
> printf("gsbase = 0x%lx, %%gs: 0x%08x, tls = 0x%x\n",
> gsbase, gs, tls);
>
> /*
> * glibc does the following two calls.
> * Note: Actually we don't do anything here
> * but writing them back.
> */
> if (sysarch(I386_SET_GSBASE, &gsbase))
> return (-1);
> load_gs(gs);
Again, this load segment base hidden register from the segment descriptor
in memory, that is 0. Access to tls would dereference NULL pointer.
In 32-bit mode, the problem is that FreeBSD does not support segmentation
(yet ?).
>
> if (sysarch(I386_GET_GSBASE, &gsbase))
> return (-1);
> printf("gsbase = 0x%lx, %%gs: 0x%08x, tls = 0x%x\n",
> gsbase, rgs(), tls);
> #endif
>
> return (0);
> }
> ----------------
>
> If you run it on amd64 (both amd64 and i386 binaries), it segfaults
> at:
>
> mov %fs:0x0,%rax (amd64)
>
> or
>
> mov %gs:0x0,%eax (i386)
>
> which is basically reading tls. Why does it segfaults when we just
> read and write them back? Can anyone enlighten me?
In normal situation, when segment registers are not reloaded,
the IA32_FS_BASE and IA32_GS_BASE MSRs define the actual base
used when fs: or gs: segment override is supplied (that is set by
sysarch(XXX_SET_XSBASE) syscalls).
In fact, it seems that kernel uses only gs: for per-cpu data, and completely
ignores fs base. Due to this, IA32_FS_BASE is changed only on thread context
switch.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 187 bytes
Desc: not available
Url : http://lists.freebsd.org/pipermail/freebsd-amd64/attachments/20070129/4ad7ea6b/attachment.pgp
More information about the freebsd-amd64
mailing list