qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] linux user i386 fork: clone the GDT for each thread to, separate


From: Douglas Crosher
Subject: [PATCH] linux user i386 fork: clone the GDT for each thread to, separate TLS
Date: Tue, 22 Sep 2020 17:36:47 +1000
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.11.0

The Linux kernel i386 maintains three GDT entries for TLS, and these
are the same three indexes for all threads. The kernel swaps the GDT
entries at these indexes before running each thread so each thread can
have separate TLS.

Current qemu linux user i386 implements a single GDT per process so
shared by all threads. While it does cache the GDT entries per thread
this cache can need to be reloaded from the GDT entry. Other threads
are likely to have overwritten the GDT entry and so the thread TLS
can become inconsistent.

For example, the segment registers are reloaded on a signal return
path, so threaded coding returning from signals breaks.

This patch clones the GDT for each thread, to separate the TLS
entries. It mmaps space from the target for this, just as the initial
GDT is currently allocated. This memory is unmapped on thread exit.

The Linux x86_64 kernel offers a different strategy for TLS, so this
patch is limited to Linux user i386.

If there were some code relying on a global GDT then this patch might break that and a correct fix might be more involved and might need to add separate per-thread GDT state for just the TLS entries and for just Linux user. Would anyone be aware of such uses?

Signed-off-by: Douglas Crosher <dtc-ubuntu@scieneer.com>
---
 linux-user/syscall.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 55ac5c3208..099e4f875a 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -6424,6 +6424,15 @@ static int do_fork(CPUArchState *env, unsigned int flags, abi_ulong newsp,
             ts->child_tidptr = child_tidptr;
         }

+#if defined(TARGET_I386) && defined(TARGET_ABI32)
+ new_env->gdt.base = target_mmap(0, sizeof(uint64_t) * TARGET_GDT_ENTRIES,
+                                        PROT_READ|PROT_WRITE,
+                                        MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+        new_env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1;
+        memcpy(g2h(new_env->gdt.base), g2h(env->gdt.base),
+               sizeof(uint64_t) * TARGET_GDT_ENTRIES);
+#endif
+
         if (flags & CLONE_SETTLS) {
             cpu_set_tls (new_env, newtls);
         }
@@ -8193,6 +8202,12 @@ static abi_long do_syscall1(void *cpu_env, int num, abi_long arg1,
         if (CPU_NEXT(first_cpu)) {
             TaskState *ts = cpu->opaque;

+#if defined(TARGET_I386) && defined(TARGET_ABI32)
+            CPUX86State *env = cpu_env;
+ target_munmap(env->gdt.base, sizeof(uint64_t) * TARGET_GDT_ENTRIES);
+            env->gdt.base = 0;
+#endif
+
object_property_set_bool(OBJECT(cpu), "realized", false, NULL);
             object_unref(OBJECT(cpu));
             /*
--
2.25.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]