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