diff options
author | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-11-14 15:18:40 +0000 |
---|---|---|
committer | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-11-14 15:18:40 +0000 |
commit | 8d18e89309f08efc44eb777631aba2fbee70375a (patch) | |
tree | 407fd6e6f2897ff312c5a32533434a1b1ca93b25 /linux-user/syscall.c | |
parent | 4683b130e562b5718115442e02b85e90501bb8b0 (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/syscall.c')
-rw-r--r-- | linux-user/syscall.c | 150 |
1 files changed, 147 insertions, 3 deletions
diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 1e4ad96b30..01d042a51f 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; |