aboutsummaryrefslogtreecommitdiffstats
path: root/linux-user
diff options
context:
space:
mode:
authorbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2007-11-14 15:18:40 +0000
committerbellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162>2007-11-14 15:18:40 +0000
commit8d18e89309f08efc44eb777631aba2fbee70375a (patch)
tree407fd6e6f2897ff312c5a32533434a1b1ca93b25 /linux-user
parent4683b130e562b5718115442e02b85e90501bb8b0 (diff)
i386 TLS support
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3644 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'linux-user')
-rw-r--r--linux-user/i386/syscall.h1
-rw-r--r--linux-user/main.c21
-rw-r--r--linux-user/syscall.c150
-rw-r--r--linux-user/x86_64/syscall.h1
4 files changed, 161 insertions, 12 deletions
diff --git a/linux-user/i386/syscall.h b/linux-user/i386/syscall.h
index 8045e1c7b..266e2c4c8 100644
--- a/linux-user/i386/syscall.h
+++ b/linux-user/i386/syscall.h
@@ -25,6 +25,7 @@ struct target_pt_regs {
#define TARGET_LDT_ENTRIES 8192
#define TARGET_LDT_ENTRY_SIZE 8
+#define TARGET_GDT_ENTRIES 9
#define TARGET_GDT_ENTRY_TLS_ENTRIES 3
#define TARGET_GDT_ENTRY_TLS_MIN 6
#define TARGET_GDT_ENTRY_TLS_MAX (TARGET_GDT_ENTRY_TLS_MIN + TARGET_GDT_ENTRY_TLS_ENTRIES - 1)
diff --git a/linux-user/main.c b/linux-user/main.c
index eb5861ad1..ac7a174d9 100644
--- a/linux-user/main.c
+++ b/linux-user/main.c
@@ -159,7 +159,6 @@ static void set_gate(void *ptr, unsigned int type, unsigned int dpl,
p[1] = tswapl(e2);
}
-uint64_t gdt_table[6];
uint64_t idt_table[256];
/* only dpl matters as we do only user space emulation */
@@ -2129,14 +2128,18 @@ int main(int argc, char **argv)
set_idt(0x80, 3);
/* linux segment setup */
- env->gdt.base = h2g(gdt_table);
- env->gdt.limit = sizeof(gdt_table) - 1;
- write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff,
- DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK |
- (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT));
- write_dt(&gdt_table[__USER_DS >> 3], 0, 0xfffff,
- DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK |
- (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT));
+ {
+ uint64_t *gdt_table;
+ gdt_table = qemu_mallocz(sizeof(uint64_t) * TARGET_GDT_ENTRIES);
+ env->gdt.base = h2g(gdt_table);
+ env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1;
+ write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff,
+ DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK |
+ (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT));
+ write_dt(&gdt_table[__USER_DS >> 3], 0, 0xfffff,
+ DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK |
+ (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT));
+ }
cpu_x86_load_seg(env, R_CS, __USER_CS);
cpu_x86_load_seg(env, R_DS, __USER_DS);
cpu_x86_load_seg(env, R_ES, __USER_DS);
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 1e4ad96b3..01d042a51 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -2285,7 +2285,7 @@ static abi_long write_ldt(CPUX86State *env,
struct target_modify_ldt_ldt_s ldt_info;
struct target_modify_ldt_ldt_s *target_ldt_info;
int seg_32bit, contents, read_exec_only, limit_in_pages;
- int seg_not_present, useable;
+ int seg_not_present, useable, lm;
uint32_t *lp, entry_1, entry_2;
if (bytecount != sizeof(ldt_info))
@@ -2306,7 +2306,11 @@ static abi_long write_ldt(CPUX86State *env,
limit_in_pages = (ldt_info.flags >> 4) & 1;
seg_not_present = (ldt_info.flags >> 5) & 1;
useable = (ldt_info.flags >> 6) & 1;
-
+#ifdef TARGET_ABI32
+ lm = 0;
+#else
+ lm = (ldt_info.flags >> 7) & 1;
+#endif
if (contents == 3) {
if (oldmode)
return -TARGET_EINVAL;
@@ -2349,6 +2353,7 @@ static abi_long write_ldt(CPUX86State *env,
((seg_not_present ^ 1) << 15) |
(seg_32bit << 22) |
(limit_in_pages << 23) |
+ (lm << 21) |
0x7000;
if (!oldmode)
entry_2 |= (useable << 20);
@@ -2384,6 +2389,138 @@ abi_long do_modify_ldt(CPUX86State *env, int func, abi_ulong ptr,
return ret;
}
+abi_long do_set_thread_area(CPUX86State *env, abi_ulong ptr)
+{
+ uint64_t *gdt_table = g2h(env->gdt.base);
+ struct target_modify_ldt_ldt_s ldt_info;
+ struct target_modify_ldt_ldt_s *target_ldt_info;
+ int seg_32bit, contents, read_exec_only, limit_in_pages;
+ int seg_not_present, useable, lm;
+ uint32_t *lp, entry_1, entry_2;
+ int i;
+
+ lock_user_struct(VERIFY_WRITE, target_ldt_info, ptr, 1);
+ if (!target_ldt_info)
+ return -TARGET_EFAULT;
+ ldt_info.entry_number = tswap32(target_ldt_info->entry_number);
+ ldt_info.base_addr = tswapl(target_ldt_info->base_addr);
+ ldt_info.limit = tswap32(target_ldt_info->limit);
+ ldt_info.flags = tswap32(target_ldt_info->flags);
+ if (ldt_info.entry_number == -1) {
+ for (i=TARGET_GDT_ENTRY_TLS_MIN; i<=TARGET_GDT_ENTRY_TLS_MAX; i++) {
+ if (gdt_table[i] == 0) {
+ ldt_info.entry_number = i;
+ target_ldt_info->entry_number = tswap32(i);
+ break;
+ }
+ }
+ }
+ unlock_user_struct(target_ldt_info, ptr, 1);
+
+ if (ldt_info.entry_number < TARGET_GDT_ENTRY_TLS_MIN ||
+ ldt_info.entry_number > TARGET_GDT_ENTRY_TLS_MAX)
+ return -TARGET_EINVAL;
+ seg_32bit = ldt_info.flags & 1;
+ contents = (ldt_info.flags >> 1) & 3;
+ read_exec_only = (ldt_info.flags >> 3) & 1;
+ limit_in_pages = (ldt_info.flags >> 4) & 1;
+ seg_not_present = (ldt_info.flags >> 5) & 1;
+ useable = (ldt_info.flags >> 6) & 1;
+#ifdef TARGET_ABI32
+ lm = 0;
+#else
+ lm = (ldt_info.flags >> 7) & 1;
+#endif
+
+ if (contents == 3) {
+ if (seg_not_present == 0)
+ return -TARGET_EINVAL;
+ }
+
+ /* NOTE: same code as Linux kernel */
+ /* Allow LDTs to be cleared by the user. */
+ if (ldt_info.base_addr == 0 && ldt_info.limit == 0) {
+ if ((contents == 0 &&
+ read_exec_only == 1 &&
+ seg_32bit == 0 &&
+ limit_in_pages == 0 &&
+ seg_not_present == 1 &&
+ useable == 0 )) {
+ entry_1 = 0;
+ entry_2 = 0;
+ goto install;
+ }
+ }
+
+ entry_1 = ((ldt_info.base_addr & 0x0000ffff) << 16) |
+ (ldt_info.limit & 0x0ffff);
+ entry_2 = (ldt_info.base_addr & 0xff000000) |
+ ((ldt_info.base_addr & 0x00ff0000) >> 16) |
+ (ldt_info.limit & 0xf0000) |
+ ((read_exec_only ^ 1) << 9) |
+ (contents << 10) |
+ ((seg_not_present ^ 1) << 15) |
+ (seg_32bit << 22) |
+ (limit_in_pages << 23) |
+ (useable << 20) |
+ (lm << 21) |
+ 0x7000;
+
+ /* Install the new entry ... */
+install:
+ lp = (uint32_t *)(gdt_table + ldt_info.entry_number);
+ lp[0] = tswap32(entry_1);
+ lp[1] = tswap32(entry_2);
+ return 0;
+}
+
+abi_long do_get_thread_area(CPUX86State *env, abi_ulong ptr)
+{
+ struct target_modify_ldt_ldt_s *target_ldt_info;
+ uint64_t *gdt_table = g2h(env->gdt.base);
+ uint32_t base_addr, limit, flags;
+ int seg_32bit, contents, read_exec_only, limit_in_pages, idx;
+ int seg_not_present, useable, lm;
+ uint32_t *lp, entry_1, entry_2;
+
+ lock_user_struct(VERIFY_WRITE, target_ldt_info, ptr, 1);
+ if (!target_ldt_info)
+ return -TARGET_EFAULT;
+ idx = tswap32(target_ldt_info->entry_number);
+ if (idx < TARGET_GDT_ENTRY_TLS_MIN ||
+ idx > TARGET_GDT_ENTRY_TLS_MAX) {
+ unlock_user_struct(target_ldt_info, ptr, 1);
+ return -TARGET_EINVAL;
+ }
+ lp = (uint32_t *)(gdt_table + idx);
+ entry_1 = tswap32(lp[0]);
+ entry_2 = tswap32(lp[1]);
+
+ read_exec_only = ((entry_2 >> 9) & 1) ^ 1;
+ contents = (entry_2 >> 10) & 3;
+ seg_not_present = ((entry_2 >> 15) & 1) ^ 1;
+ seg_32bit = (entry_2 >> 22) & 1;
+ limit_in_pages = (entry_2 >> 23) & 1;
+ useable = (entry_2 >> 20) & 1;
+#ifdef TARGET_ABI32
+ lm = 0;
+#else
+ lm = (entry_2 >> 21) & 1;
+#endif
+ flags = (seg_32bit << 0) | (contents << 1) |
+ (read_exec_only << 3) | (limit_in_pages << 4) |
+ (seg_not_present << 5) | (useable << 6) | (lm << 7);
+ limit = (entry_1 & 0xffff) | (entry_2 & 0xf0000);
+ base_addr = (entry_1 >> 16) |
+ (entry_2 & 0xff000000) |
+ ((entry_2 & 0xff) << 16);
+ target_ldt_info->base_addr = tswapl(base_addr);
+ target_ldt_info->limit = tswap32(limit);
+ target_ldt_info->flags = tswap32(flags);
+ unlock_user_struct(target_ldt_info, ptr, 1);
+ return 0;
+}
+
#endif /* defined(TARGET_I386) */
/* this stack is the equivalent of the kernel stack associated with a
@@ -5136,18 +5273,25 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
#endif
#ifdef TARGET_NR_set_thread_area
case TARGET_NR_set_thread_area:
-#ifdef TARGET_MIPS
+#if defined(TARGET_MIPS)
((CPUMIPSState *) cpu_env)->tls_value = arg1;
ret = 0;
break;
+#elif defined(TARGET_I386) && defined(TARGET_ABI32)
+ ret = do_set_thread_area(cpu_env, arg1);
+ break;
#else
goto unimplemented_nowarn;
#endif
#endif
#ifdef TARGET_NR_get_thread_area
case TARGET_NR_get_thread_area:
+#if defined(TARGET_I386) && defined(TARGET_ABI32)
+ ret = do_get_thread_area(cpu_env, arg1);
+#else
goto unimplemented_nowarn;
#endif
+#endif
#ifdef TARGET_NR_getdomainname
case TARGET_NR_getdomainname:
goto unimplemented_nowarn;
diff --git a/linux-user/x86_64/syscall.h b/linux-user/x86_64/syscall.h
index cdba432bf..f82589c49 100644
--- a/linux-user/x86_64/syscall.h
+++ b/linux-user/x86_64/syscall.h
@@ -34,6 +34,7 @@ struct target_pt_regs {
/* The size of each LDT entry. */
#define TARGET_LDT_ENTRY_SIZE 8
+#define TARGET_GDT_ENTRIES 16
#define TARGET_GDT_ENTRY_TLS_ENTRIES 3
#define TARGET_GDT_ENTRY_TLS_MIN 12
#define TARGET_GDT_ENTRY_TLS_MAX 14