diff options
-rw-r--r-- | linux-user/i386/syscall.h | 1 | ||||
-rw-r--r-- | linux-user/main.c | 21 | ||||
-rw-r--r-- | linux-user/syscall.c | 150 | ||||
-rw-r--r-- | linux-user/x86_64/syscall.h | 1 |
4 files changed, 161 insertions, 12 deletions
diff --git a/linux-user/i386/syscall.h b/linux-user/i386/syscall.h index 8045e1c..266e2c4 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 eb5861a..ac7a174 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 1e4ad96..01d042a 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 cdba432..f82589c 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 |