diff options
author | Jung-uk Kim <jkim@FreeBSD.org> | 2007-03-30 00:06:21 +0000 |
---|---|---|
committer | Jung-uk Kim <jkim@FreeBSD.org> | 2007-03-30 00:06:21 +0000 |
commit | 9c5b213e51c6921e23f89395cb44d18ed26dc0d2 (patch) | |
tree | 9d89cc517054300aafea61fc017fcfc6460f2207 /sys/amd64/linux32 | |
parent | 78f66a0f2108d2ebeb4b102ea4e8bf8b311b91b6 (diff) | |
download | src-9c5b213e51c6921e23f89395cb44d18ed26dc0d2.tar.gz src-9c5b213e51c6921e23f89395cb44d18ed26dc0d2.zip |
MFP4: Linux set_thread_area syscall (aka TLS) support for amd64.
Initial version was submitted by Divacky Roman and mostly rewritten by me.
Tested by: emulation
Notes
Notes:
svn path=/head/; revision=168035
Diffstat (limited to 'sys/amd64/linux32')
-rw-r--r-- | sys/amd64/linux32/linux32_locore.s | 4 | ||||
-rw-r--r-- | sys/amd64/linux32/linux32_machdep.c | 160 | ||||
-rw-r--r-- | sys/amd64/linux32/linux32_sysvec.c | 8 | ||||
-rw-r--r-- | sys/amd64/linux32/syscalls.master | 2 |
4 files changed, 158 insertions, 16 deletions
diff --git a/sys/amd64/linux32/linux32_locore.s b/sys/amd64/linux32/linux32_locore.s index 6c3d2082e87a..8055e566cbdc 100644 --- a/sys/amd64/linux32/linux32_locore.s +++ b/sys/amd64/linux32/linux32_locore.s @@ -11,8 +11,6 @@ NON_GPROF_ENTRY(linux_sigcode) call *LINUX_SIGF_HANDLER(%esp) leal LINUX_SIGF_SC(%esp),%ebx /* linux scp */ - movl LINUX_SC_GS(%ebx),%gs - movl LINUX_SC_FS(%ebx),%fs movl LINUX_SC_ES(%ebx),%es movl LINUX_SC_DS(%ebx),%ds movl %esp, %ebx /* pass sigframe */ @@ -25,8 +23,6 @@ NON_GPROF_ENTRY(linux_sigcode) linux_rt_sigcode: call *LINUX_RT_SIGF_HANDLER(%esp) leal LINUX_RT_SIGF_UC(%esp),%ebx /* linux ucp */ - movl LINUX_SC_GS(%ebx),%gs - movl LINUX_SC_FS(%ebx),%fs movl LINUX_SC_ES(%ebx),%es movl LINUX_SC_DS(%ebx),%ds push %eax /* fake ret addr */ diff --git a/sys/amd64/linux32/linux32_machdep.c b/sys/amd64/linux32/linux32_machdep.c index a83ec4fbfb5b..888c4fcdbf87 100644 --- a/sys/amd64/linux32/linux32_machdep.c +++ b/sys/amd64/linux32/linux32_machdep.c @@ -53,7 +53,10 @@ __FBSDID("$FreeBSD$"); #include <sys/unistd.h> #include <machine/frame.h> +#include <machine/pcb.h> #include <machine/psl.h> +#include <machine/segments.h> +#include <machine/specialreg.h> #include <vm/vm.h> #include <vm/pmap.h> @@ -656,7 +659,43 @@ linux_clone(struct thread *td, struct linux_clone_args *args) td2->td_frame->tf_rsp = PTROUT(args->stack); if (args->flags & LINUX_CLONE_SETTLS) { - /* XXX: todo */ + struct user_segment_descriptor sd; + struct l_user_desc info; + int a[2]; + + error = copyin((void *)td->td_frame->tf_rsi, &info, + sizeof(struct l_user_desc)); + if (error) { + printf(LMSG("copyin failed!")); + } else { + /* We might copy out the entry_number as GUGS32_SEL. */ + info.entry_number = GUGS32_SEL; + error = copyout(&info, (void *)td->td_frame->tf_rsi, + sizeof(struct l_user_desc)); + if (error) + printf(LMSG("copyout failed!")); + + a[0] = LINUX_LDT_entry_a(&info); + a[1] = LINUX_LDT_entry_b(&info); + + memcpy(&sd, &a, sizeof(a)); +#ifdef DEBUG + if (ldebug(clone)) + printf("Segment created in clone with " + "CLONE_SETTLS: lobase: %x, hibase: %x, " + "lolimit: %x, hilimit: %x, type: %i, " + "dpl: %i, p: %i, xx: %i, long: %i, " + "def32: %i, gran: %i\n", sd.sd_lobase, + sd.sd_hibase, sd.sd_lolimit, sd.sd_hilimit, + sd.sd_type, sd.sd_dpl, sd.sd_p, sd.sd_xx, + sd.sd_long, sd.sd_def32, sd.sd_gran); +#endif + td2->td_pcb->pcb_gsbase = (register_t)info.base_addr; + td2->td_pcb->pcb_gs32sd = sd; + td2->td_pcb->pcb_gs32p = &gdt[GUGS32_SEL]; + td2->td_pcb->pcb_gs = GSEL(GUGS32_SEL, SEL_UPL); + td2->td_pcb->pcb_flags |= PCB_32BIT; + } } #ifdef DEBUG @@ -905,6 +944,19 @@ linux_mmap_common(struct thread *td, struct l_mmap_argv *linux_args) } int +linux_mprotect(struct thread *td, struct linux_mprotect_args *uap) +{ + struct mprotect_args bsd_args; + + bsd_args.addr = uap->addr; + bsd_args.len = uap->len; + bsd_args.prot = uap->prot; + if (bsd_args.prot & (PROT_READ | PROT_WRITE | PROT_EXEC)) + bsd_args.prot |= PROT_READ | PROT_EXEC; + return (mprotect(td, &bsd_args)); +} + +int linux_iopl(struct thread *td, struct linux_iopl_args *args) { int error; @@ -1177,14 +1229,104 @@ linux_sched_rr_get_interval(struct thread *td, } int -linux_mprotect(struct thread *td, struct linux_mprotect_args *uap) +linux_set_thread_area(struct thread *td, + struct linux_set_thread_area_args *args) { - struct mprotect_args bsd_args; + struct l_user_desc info; + struct user_segment_descriptor sd; + int a[2]; + int error; - bsd_args.addr = uap->addr; - bsd_args.len = uap->len; - bsd_args.prot = uap->prot; - if (bsd_args.prot & (PROT_READ | PROT_WRITE | PROT_EXEC)) - bsd_args.prot |= PROT_READ | PROT_EXEC; - return (mprotect(td, &bsd_args)); + error = copyin(args->desc, &info, sizeof(struct l_user_desc)); + if (error) + return (error); + +#ifdef DEBUG + if (ldebug(set_thread_area)) + printf(ARGS(set_thread_area, "%i, %x, %x, %i, %i, %i, " + "%i, %i, %i"), info.entry_number, info.base_addr, + info.limit, info.seg_32bit, info.contents, + info.read_exec_only, info.limit_in_pages, + info.seg_not_present, info.useable); +#endif + + /* + * Semantics of Linux version: every thread in the system has array + * of three TLS descriptors. 1st is GLIBC TLS, 2nd is WINE, 3rd unknown. + * This syscall loads one of the selected TLS decriptors with a value + * and also loads GDT descriptors 6, 7 and 8 with the content of + * the per-thread descriptors. + * + * Semantics of FreeBSD version: I think we can ignore that Linux has + * three per-thread descriptors and use just the first one. + * The tls_array[] is used only in [gs]et_thread_area() syscalls and + * for loading the GDT descriptors. We use just one GDT descriptor + * for TLS, so we will load just one. + * XXX: This doesnt work when user-space process tries to use more + * than one TLS segment. Comment in the Linux source says wine might + * do that. + */ + + /* + * GLIBC reads current %gs and call set_thread_area() with it. + * We should let GUDATA_SEL and GUGS32_SEL proceed as well because + * we use these segments. + */ + switch (info.entry_number) { + case GUGS32_SEL: + case GUDATA_SEL: + case 6: + case -1: + info.entry_number = GUGS32_SEL; + break; + default: + return (EINVAL); + } + + /* + * We have to copy out the GDT entry we use. + * XXX: What if userspace program does not check return value and + * tries to use 6, 7 or 8? + */ + error = copyout(&info, args->desc, sizeof(struct l_user_desc)); + if (error) + return (error); + + if (LINUX_LDT_empty(&info)) { + a[0] = 0; + a[1] = 0; + } else { + a[0] = LINUX_LDT_entry_a(&info); + a[1] = LINUX_LDT_entry_b(&info); + } + + memcpy(&sd, &a, sizeof(a)); +#ifdef DEBUG + if (ldebug(set_thread_area)) + printf("Segment created in set_thread_area: " + "lobase: %x, hibase: %x, lolimit: %x, hilimit: %x, " + "type: %i, dpl: %i, p: %i, xx: %i, long: %i, " + "def32: %i, gran: %i\n", + sd.sd_lobase, + sd.sd_hibase, + sd.sd_lolimit, + sd.sd_hilimit, + sd.sd_type, + sd.sd_dpl, + sd.sd_p, + sd.sd_xx, + sd.sd_long, + sd.sd_def32, + sd.sd_gran); +#endif + + critical_enter(); + td->td_pcb->pcb_gsbase = (register_t)info.base_addr; + td->td_pcb->pcb_gs32sd = gdt[GUGS32_SEL] = sd; + td->td_pcb->pcb_gs32p = &gdt[GUGS32_SEL]; + td->td_pcb->pcb_flags |= PCB_32BIT; + wrmsr(MSR_KGSBASE, td->td_pcb->pcb_gsbase); + critical_exit(); + + return (0); } diff --git a/sys/amd64/linux32/linux32_sysvec.c b/sys/amd64/linux32/linux32_sysvec.c index 8490e65fd8b4..f77d0b972a7b 100644 --- a/sys/amd64/linux32/linux32_sysvec.c +++ b/sys/amd64/linux32/linux32_sysvec.c @@ -408,6 +408,7 @@ linux_rt_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) td->td_pcb->pcb_ds = _udatasel; load_es(_udatasel); td->td_pcb->pcb_es = _udatasel; + /* leave user %fs and %gs untouched */ PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } @@ -528,6 +529,7 @@ linux_sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *mask) td->td_pcb->pcb_ds = _udatasel; load_es(_udatasel); td->td_pcb->pcb_es = _udatasel; + /* leave user %fs and %gs untouched */ PROC_LOCK(p); mtx_lock(&psp->ps_mtx); } @@ -813,18 +815,20 @@ exec_linux_setregs(td, entry, stack, ps_strings) struct trapframe *regs = td->td_frame; struct pcb *pcb = td->td_pcb; + critical_enter(); wrmsr(MSR_FSBASE, 0); wrmsr(MSR_KGSBASE, 0); /* User value while we're in the kernel */ pcb->pcb_fsbase = 0; pcb->pcb_gsbase = 0; + critical_exit(); load_ds(_udatasel); load_es(_udatasel); load_fs(_udatasel); - load_gs(0); + load_gs(_udatasel); pcb->pcb_ds = _udatasel; pcb->pcb_es = _udatasel; pcb->pcb_fs = _udatasel; - pcb->pcb_gs = 0; + pcb->pcb_gs = _udatasel; bzero((char *)regs, sizeof(struct trapframe)); regs->tf_rip = entry; diff --git a/sys/amd64/linux32/syscalls.master b/sys/amd64/linux32/syscalls.master index 9fc4cb0c0444..958c5ced5b8b 100644 --- a/sys/amd64/linux32/syscalls.master +++ b/sys/amd64/linux32/syscalls.master @@ -409,7 +409,7 @@ struct l_timespec *timeout, void *uaddr2, int val3); } 241 AUE_NULL UNIMPL linux_sched_setaffinity 242 AUE_NULL UNIMPL linux_sched_getaffinity -243 AUE_NULL UNIMPL linux_set_thread_area +243 AUE_NULL STD { int linux_set_thread_area(struct l_user_desc *desc); } 244 AUE_NULL UNIMPL linux_get_thread_area 245 AUE_NULL UNIMPL linux_io_setup 246 AUE_NULL UNIMPL linux_io_destroy |