diff options
Diffstat (limited to 'arch/um/kernel/process.c')
| -rw-r--r-- | arch/um/kernel/process.c | 709 |
1 files changed, 343 insertions, 366 deletions
diff --git a/arch/um/kernel/process.c b/arch/um/kernel/process.c index 67acd92c532..f17bca8ed2c 100644 --- a/arch/um/kernel/process.c +++ b/arch/um/kernel/process.c @@ -1,439 +1,416 @@ -/* - * Copyright (C) 2000, 2001, 2002 Jeff Dike (jdike@karaya.com) +/* + * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) + * Copyright 2003 PathScale, Inc. * Licensed under the GPL */ -#include <stdio.h> -#include <unistd.h> -#include <signal.h> -#include <sched.h> -#include <errno.h> -#include <stdarg.h> -#include <stdlib.h> -#include <setjmp.h> -#include <sys/time.h> -#include <sys/wait.h> -#include <sys/mman.h> -#include <asm/unistd.h> -#include <asm/page.h> -#include "user_util.h" -#include "kern_util.h" -#include "user.h" -#include "process.h" -#include "signal_kern.h" -#include "signal_user.h" -#include "sysdep/ptrace.h" -#include "sysdep/sigcontext.h" -#include "irq_user.h" -#include "ptrace_user.h" -#include "time_user.h" -#include "init.h" -#include "os.h" -#include "uml-config.h" -#include "choose-mode.h" -#include "mode.h" -#include "tempfile.h" -#ifdef UML_CONFIG_MODE_SKAS -#include "skas.h" -#include "skas_ptrace.h" -#include "registers.h" -#endif +#include <linux/stddef.h> +#include <linux/err.h> +#include <linux/hardirq.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/personality.h> +#include <linux/proc_fs.h> +#include <linux/ptrace.h> +#include <linux/random.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/tick.h> +#include <linux/threads.h> +#include <linux/tracehook.h> +#include <asm/current.h> +#include <asm/pgtable.h> +#include <asm/mmu_context.h> +#include <asm/uaccess.h> +#include <as-layout.h> +#include <kern_util.h> +#include <os.h> +#include <skas.h> + +/* + * This is a per-cpu array. A processor only modifies its entry and it only + * cares about its entry, so it's OK if another processor is modifying its + * entry. + */ +struct cpu_task cpu_tasks[NR_CPUS] = { [0 ... NR_CPUS - 1] = { -1, NULL } }; + +static inline int external_pid(void) +{ + /* FIXME: Need to look up userspace_pid by cpu */ + return userspace_pid[0]; +} -void init_new_thread_stack(void *sig_stack, void (*usr1_handler)(int)) +int pid_to_processor_id(int pid) { - int flags = 0, pages; + int i; - if(sig_stack != NULL){ - pages = (1 << UML_CONFIG_KERNEL_STACK_ORDER); - set_sigstack(sig_stack, pages * page_size()); - flags = SA_ONSTACK; + for (i = 0; i < ncpus; i++) { + if (cpu_tasks[i].pid == pid) + return i; } - if(usr1_handler) set_handler(SIGUSR1, usr1_handler, flags, -1); + return -1; +} + +void free_stack(unsigned long stack, int order) +{ + free_pages(stack, order); } -void init_new_thread_signals(int altstack) +unsigned long alloc_stack(int order, int atomic) { - int flags = altstack ? SA_ONSTACK : 0; + unsigned long page; + gfp_t flags = GFP_KERNEL; - set_handler(SIGSEGV, (__sighandler_t) sig_handler, flags, - SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - set_handler(SIGTRAP, (__sighandler_t) sig_handler, flags, - SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - set_handler(SIGFPE, (__sighandler_t) sig_handler, flags, - SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - set_handler(SIGILL, (__sighandler_t) sig_handler, flags, - SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - set_handler(SIGBUS, (__sighandler_t) sig_handler, flags, - SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - set_handler(SIGUSR2, (__sighandler_t) sig_handler, - flags, SIGUSR1, SIGIO, SIGWINCH, SIGALRM, SIGVTALRM, -1); - signal(SIGHUP, SIG_IGN); + if (atomic) + flags = GFP_ATOMIC; + page = __get_free_pages(flags, order); - init_irq_signals(altstack); + return page; } -struct tramp { - int (*tramp)(void *); - void *tramp_data; - unsigned long temp_stack; - int flags; - int pid; -}; +static inline void set_current(struct task_struct *task) +{ + cpu_tasks[task_thread_info(task)->cpu] = ((struct cpu_task) + { external_pid(), task }); +} + +extern void arch_switch_to(struct task_struct *to); -/* See above for why sigkill is here */ +void *__switch_to(struct task_struct *from, struct task_struct *to) +{ + to->thread.prev_sched = from; + set_current(to); + + switch_threads(&from->thread.switch_buf, &to->thread.switch_buf); + arch_switch_to(current); + + return current->thread.prev_sched; +} -int sigkill = SIGKILL; +void interrupt_end(void) +{ + if (need_resched()) + schedule(); + if (test_thread_flag(TIF_SIGPENDING)) + do_signal(); + if (test_and_clear_thread_flag(TIF_NOTIFY_RESUME)) + tracehook_notify_resume(¤t->thread.regs); +} -int outer_tramp(void *arg) +void exit_thread(void) { - struct tramp *t; - int sig = sigkill; +} - t = arg; - t->pid = clone(t->tramp, (void *) t->temp_stack + page_size()/2, - t->flags, t->tramp_data); - if(t->pid > 0) wait_for_stop(t->pid, SIGSTOP, PTRACE_CONT, NULL); - kill(os_getpid(), sig); - _exit(0); +int get_current_pid(void) +{ + return task_pid_nr(current); } -int start_fork_tramp(void *thread_arg, unsigned long temp_stack, - int clone_flags, int (*tramp)(void *)) +/* + * This is called magically, by its address being stuffed in a jmp_buf + * and being longjmp-d to. + */ +void new_thread_handler(void) { - struct tramp arg; - unsigned long sp; - int new_pid, status, err; + int (*fn)(void *), n; + void *arg; - /* The trampoline will run on the temporary stack */ - sp = stack_sp(temp_stack); + if (current->thread.prev_sched != NULL) + schedule_tail(current->thread.prev_sched); + current->thread.prev_sched = NULL; - clone_flags |= CLONE_FILES | SIGCHLD; + fn = current->thread.request.u.thread.proc; + arg = current->thread.request.u.thread.arg; - arg.tramp = tramp; - arg.tramp_data = thread_arg; - arg.temp_stack = temp_stack; - arg.flags = clone_flags; + /* + * callback returns only if the kernel thread execs a process + */ + n = fn(arg); + userspace(¤t->thread.regs.regs); +} + +/* Called magically, see new_thread_handler above */ +void fork_handler(void) +{ + force_flush_all(); - /* Start the process and wait for it to kill itself */ - new_pid = clone(outer_tramp, (void *) sp, clone_flags, &arg); - if(new_pid < 0) - return(new_pid); + schedule_tail(current->thread.prev_sched); - CATCH_EINTR(err = waitpid(new_pid, &status, 0)); - if(err < 0) - panic("Waiting for outer trampoline failed - errno = %d", - errno); + /* + * XXX: if interrupt_end() calls schedule, this call to + * arch_switch_to isn't needed. We could want to apply this to + * improve performance. -bb + */ + arch_switch_to(current); - if(!WIFSIGNALED(status) || (WTERMSIG(status) != SIGKILL)) - panic("outer trampoline didn't exit with SIGKILL, " - "status = %d", status); + current->thread.prev_sched = NULL; - return(arg.pid); + userspace(¤t->thread.regs.regs); } -static int ptrace_child(void *arg) +int copy_thread(unsigned long clone_flags, unsigned long sp, + unsigned long arg, struct task_struct * p) { - int ret; - int pid = os_getpid(), ppid = getppid(); - int sc_result; + void (*handler)(void); + int kthread = current->flags & PF_KTHREAD; + int ret = 0; - if(ptrace(PTRACE_TRACEME, 0, 0, 0) < 0){ - perror("ptrace"); - os_kill_process(pid, 0); + p->thread = (struct thread_struct) INIT_THREAD; + + if (!kthread) { + memcpy(&p->thread.regs.regs, current_pt_regs(), + sizeof(p->thread.regs.regs)); + PT_REGS_SET_SYSCALL_RETURN(&p->thread.regs, 0); + if (sp != 0) + REGS_SP(p->thread.regs.regs.gp) = sp; + + handler = fork_handler; + + arch_copy_thread(¤t->thread.arch, &p->thread.arch); + } else { + get_safe_registers(p->thread.regs.regs.gp, p->thread.regs.regs.fp); + p->thread.request.u.thread.proc = (int (*)(void *))sp; + p->thread.request.u.thread.arg = (void *)arg; + handler = new_thread_handler; } - os_stop_process(pid); - - /*This syscall will be intercepted by the parent. Don't call more than - * once, please.*/ - sc_result = os_getpid(); - - if (sc_result == pid) - ret = 1; /*Nothing modified by the parent, we are running - normally.*/ - else if (sc_result == ppid) - ret = 0; /*Expected in check_ptrace and check_sysemu when they - succeed in modifying the stack frame*/ - else - ret = 2; /*Serious trouble! This could be caused by a bug in - host 2.6 SKAS3/2.6 patch before release -V6, together - with a bug in the UML code itself.*/ - _exit(ret); -} - -static int start_ptraced_child(void **stack_out) -{ - void *stack; - unsigned long sp; - int pid, n, status; - - stack = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if(stack == MAP_FAILED) - panic("check_ptrace : mmap failed, errno = %d", errno); - sp = (unsigned long) stack + PAGE_SIZE - sizeof(void *); - pid = clone(ptrace_child, (void *) sp, SIGCHLD, NULL); - if(pid < 0) - panic("check_ptrace : clone failed, errno = %d", errno); - CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED)); - if(n < 0) - panic("check_ptrace : wait failed, errno = %d", errno); - if(!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGSTOP)) - panic("check_ptrace : expected SIGSTOP, got status = %d", - status); - - *stack_out = stack; - return(pid); -} - -/* When testing for SYSEMU support, if it is one of the broken versions, we must - * just avoid using sysemu, not panic, but only if SYSEMU features are broken. - * So only for SYSEMU features we test mustpanic, while normal host features - * must work anyway!*/ -static int stop_ptraced_child(int pid, void *stack, int exitcode, int mustpanic) -{ - int status, n, ret = 0; - - if(ptrace(PTRACE_CONT, pid, 0, 0) < 0) - panic("check_ptrace : ptrace failed, errno = %d", errno); - CATCH_EINTR(n = waitpid(pid, &status, 0)); - if(!WIFEXITED(status) || (WEXITSTATUS(status) != exitcode)) { - int exit_with = WEXITSTATUS(status); - if (exit_with == 2) - printk("check_ptrace : child exited with status 2. " - "Serious trouble happening! Try updating your " - "host skas patch!\nDisabling SYSEMU support."); - printk("check_ptrace : child exited with exitcode %d, while " - "expecting %d; status 0x%x", exit_with, - exitcode, status); - if (mustpanic) - panic("\n"); - else - printk("\n"); - ret = -1; + + new_thread(task_stack_page(p), &p->thread.switch_buf, handler); + + if (!kthread) { + clear_flushed_tls(p); + + /* + * Set a new TLS for the child thread? + */ + if (clone_flags & CLONE_SETTLS) + ret = arch_copy_tls(p); } - if(munmap(stack, PAGE_SIZE) < 0) - panic("check_ptrace : munmap failed, errno = %d", errno); return ret; } -static int force_sysemu_disabled = 0; +void initial_thread_cb(void (*proc)(void *), void *arg) +{ + int save_kmalloc_ok = kmalloc_ok; -int ptrace_faultinfo = 1; -int proc_mm = 1; + kmalloc_ok = 0; + initial_thread_cb_skas(proc, arg); + kmalloc_ok = save_kmalloc_ok; +} -static int __init skas0_cmd_param(char *str, int* add) +void arch_cpu_idle(void) { - ptrace_faultinfo = proc_mm = 0; - return 0; + unsigned long long nsecs; + + cpu_tasks[current_thread_info()->cpu].pid = os_getpid(); + nsecs = disable_timer(); + idle_sleep(nsecs); + local_irq_enable(); +} + +int __cant_sleep(void) { + return in_atomic() || irqs_disabled() || in_interrupt(); + /* Is in_interrupt() really needed? */ +} + +int user_context(unsigned long sp) +{ + unsigned long stack; + + stack = sp & (PAGE_MASK << CONFIG_KERNEL_STACK_ORDER); + return stack != (unsigned long) current_thread_info(); +} + +extern exitcall_t __uml_exitcall_begin, __uml_exitcall_end; + +void do_uml_exitcalls(void) +{ + exitcall_t *call; + + call = &__uml_exitcall_end; + while (--call >= &__uml_exitcall_begin) + (*call)(); +} + +char *uml_strdup(const char *string) +{ + return kstrdup(string, GFP_KERNEL); +} +EXPORT_SYMBOL(uml_strdup); + +int copy_to_user_proc(void __user *to, void *from, int size) +{ + return copy_to_user(to, from, size); +} + +int copy_from_user_proc(void *to, void __user *from, int size) +{ + return copy_from_user(to, from, size); +} + +int clear_user_proc(void __user *buf, int size) +{ + return clear_user(buf, size); +} + +int strlen_user_proc(char __user *str) +{ + return strlen_user(str); } -static int __init nosysemu_cmd_param(char *str, int* add) +int smp_sigio_handler(void) { - force_sysemu_disabled = 1; +#ifdef CONFIG_SMP + int cpu = current_thread_info()->cpu; + IPI_handler(cpu); + if (cpu != 0) + return 1; +#endif return 0; } -__uml_setup("skas0", skas0_cmd_param, - "skas0\n" - " Disables SKAS3 usage, so that SKAS0 is used, unless you \n" - " specify mode=tt.\n\n"); - -__uml_setup("nosysemu", nosysemu_cmd_param, - "nosysemu\n" - " Turns off syscall emulation patch for ptrace (SYSEMU) on.\n" - " SYSEMU is a performance-patch introduced by Laurent Vivier. It changes\n" - " behaviour of ptrace() and helps reducing host context switch rate.\n" - " To make it working, you need a kernel patch for your host, too.\n" - " See http://perso.wanadoo.fr/laurent.vivier/UML/ for further information.\n\n"); - -static void __init check_sysemu(void) -{ - void *stack; - int pid, syscall, n, status, count=0; - - printk("Checking syscall emulation patch for ptrace..."); - sysemu_supported = 0; - pid = start_ptraced_child(&stack); - - if(ptrace(PTRACE_SYSEMU, pid, 0, 0) < 0) - goto fail; - - CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED)); - if (n < 0) - panic("check_sysemu : wait failed, errno = %d", errno); - if(!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGTRAP)) - panic("check_sysemu : expected SIGTRAP, " - "got status = %d", status); - - n = ptrace(PTRACE_POKEUSR, pid, PT_SYSCALL_RET_OFFSET, - os_getpid()); - if(n < 0) - panic("check_sysemu : failed to modify system " - "call return, errno = %d", errno); - - if (stop_ptraced_child(pid, stack, 0, 0) < 0) - goto fail_stopped; - - sysemu_supported = 1; - printk("OK\n"); - set_using_sysemu(!force_sysemu_disabled); - - printk("Checking advanced syscall emulation patch for ptrace..."); - pid = start_ptraced_child(&stack); - while(1){ - count++; - if(ptrace(PTRACE_SYSEMU_SINGLESTEP, pid, 0, 0) < 0) - goto fail; - CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED)); - if(n < 0) - panic("check_ptrace : wait failed, errno = %d", errno); - if(!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGTRAP)) - panic("check_ptrace : expected (SIGTRAP|SYSCALL_TRAP), " - "got status = %d", status); - - syscall = ptrace(PTRACE_PEEKUSR, pid, PT_SYSCALL_NR_OFFSET, - 0); - if(syscall == __NR_getpid){ - if (!count) - panic("check_ptrace : SYSEMU_SINGLESTEP doesn't singlestep"); - n = ptrace(PTRACE_POKEUSR, pid, PT_SYSCALL_RET_OFFSET, - os_getpid()); - if(n < 0) - panic("check_sysemu : failed to modify system " - "call return, errno = %d", errno); - break; - } - } - if (stop_ptraced_child(pid, stack, 0, 0) < 0) - goto fail_stopped; - - sysemu_supported = 2; - printk("OK\n"); - - if ( !force_sysemu_disabled ) - set_using_sysemu(sysemu_supported); - return; - -fail: - stop_ptraced_child(pid, stack, 1, 0); -fail_stopped: - printk("missing\n"); -} - -void __init check_ptrace(void) -{ - void *stack; - int pid, syscall, n, status; - - printk("Checking that ptrace can change system call numbers..."); - pid = start_ptraced_child(&stack); - - if (ptrace(PTRACE_OLDSETOPTIONS, pid, 0, (void *)PTRACE_O_TRACESYSGOOD) < 0) - panic("check_ptrace: PTRACE_SETOPTIONS failed, errno = %d", errno); - - while(1){ - if(ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) - panic("check_ptrace : ptrace failed, errno = %d", - errno); - CATCH_EINTR(n = waitpid(pid, &status, WUNTRACED)); - if(n < 0) - panic("check_ptrace : wait failed, errno = %d", errno); - if(!WIFSTOPPED(status) || (WSTOPSIG(status) != SIGTRAP + 0x80)) - panic("check_ptrace : expected SIGTRAP + 0x80, " - "got status = %d", status); - - syscall = ptrace(PTRACE_PEEKUSR, pid, PT_SYSCALL_NR_OFFSET, - 0); - if(syscall == __NR_getpid){ - n = ptrace(PTRACE_POKEUSR, pid, PT_SYSCALL_NR_OFFSET, - __NR_getppid); - if(n < 0) - panic("check_ptrace : failed to modify system " - "call, errno = %d", errno); - break; - } - } - stop_ptraced_child(pid, stack, 0, 1); - printk("OK\n"); - check_sysemu(); +int cpu(void) +{ + return current_thread_info()->cpu; } -int run_kernel_thread(int (*fn)(void *), void *arg, void **jmp_ptr) +static atomic_t using_sysemu = ATOMIC_INIT(0); +int sysemu_supported; + +void set_using_sysemu(int value) { - sigjmp_buf buf; - int n; + if (value > sysemu_supported) + return; + atomic_set(&using_sysemu, value); +} - *jmp_ptr = &buf; - n = sigsetjmp(buf, 1); - if(n != 0) - return(n); - (*fn)(arg); - return(0); +int get_using_sysemu(void) +{ + return atomic_read(&using_sysemu); } -void forward_pending_sigio(int target) +static int sysemu_proc_show(struct seq_file *m, void *v) { - sigset_t sigs; + seq_printf(m, "%d\n", get_using_sysemu()); + return 0; +} - if(sigpending(&sigs)) - panic("forward_pending_sigio : sigpending failed"); - if(sigismember(&sigs, SIGIO)) - kill(target, SIGIO); +static int sysemu_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, sysemu_proc_show, NULL); } -extern void *__syscall_stub_start, __syscall_stub_end; +static ssize_t sysemu_proc_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + char tmp[2]; + + if (copy_from_user(tmp, buf, 1)) + return -EFAULT; + + if (tmp[0] >= '0' && tmp[0] <= '2') + set_using_sysemu(tmp[0] - '0'); + /* We use the first char, but pretend to write everything */ + return count; +} -#ifdef UML_CONFIG_MODE_SKAS +static const struct file_operations sysemu_proc_fops = { + .owner = THIS_MODULE, + .open = sysemu_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = sysemu_proc_write, +}; -static inline void check_skas3_ptrace_support(void) +int __init make_proc_sysemu(void) { - struct ptrace_faultinfo fi; - void *stack; - int pid, n; + struct proc_dir_entry *ent; + if (!sysemu_supported) + return 0; - printf("Checking for the skas3 patch in the host..."); - pid = start_ptraced_child(&stack); + ent = proc_create("sysemu", 0600, NULL, &sysemu_proc_fops); - n = ptrace(PTRACE_FAULTINFO, pid, 0, &fi); - if (n < 0) { - ptrace_faultinfo = 0; - if(errno == EIO) - printf("not found\n"); - else { - perror("not found"); - } - } - else { - if (!ptrace_faultinfo) - printf("found but disabled on command line\n"); - else - printf("found\n"); + if (ent == NULL) + { + printk(KERN_WARNING "Failed to register /proc/sysemu\n"); + return 0; } - init_registers(pid); - stop_ptraced_child(pid, stack, 1, 1); + return 0; } -int can_do_skas(void) +late_initcall(make_proc_sysemu); + +int singlestepping(void * t) { - printf("Checking for /proc/mm..."); - if (os_access("/proc/mm", OS_ACC_W_OK) < 0) { - proc_mm = 0; - printf("not found\n"); - } else { - if (!proc_mm) - printf("found but disabled on command line\n"); - else - printf("found\n"); - } + struct task_struct *task = t ? t : current; - check_skas3_ptrace_support(); - return 1; + if (!(task->ptrace & PT_DTRACE)) + return 0; + + if (task->thread.singlestep_syscall) + return 1; + + return 2; } -#else -int can_do_skas(void) + +/* + * Only x86 and x86_64 have an arch_align_stack(). + * All other arches have "#define arch_align_stack(x) (x)" + * in their asm/exec.h + * As this is included in UML from asm-um/system-generic.h, + * we can use it to behave as the subarch does. + */ +#ifndef arch_align_stack +unsigned long arch_align_stack(unsigned long sp) { - return(0); + if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space) + sp -= get_random_int() % 8192; + return sp & ~0xf; } #endif + +unsigned long get_wchan(struct task_struct *p) +{ + unsigned long stack_page, sp, ip; + bool seen_sched = 0; + + if ((p == NULL) || (p == current) || (p->state == TASK_RUNNING)) + return 0; + + stack_page = (unsigned long) task_stack_page(p); + /* Bail if the process has no kernel stack for some reason */ + if (stack_page == 0) + return 0; + + sp = p->thread.switch_buf->JB_SP; + /* + * Bail if the stack pointer is below the bottom of the kernel + * stack for some reason + */ + if (sp < stack_page) + return 0; + + while (sp < stack_page + THREAD_SIZE) { + ip = *((unsigned long *) sp); + if (in_sched_functions(ip)) + /* Ignore everything until we're above the scheduler */ + seen_sched = 1; + else if (kernel_text_address(ip) && seen_sched) + return ip; + + sp += sizeof(unsigned long); + } + + return 0; +} + +int elf_core_copy_fpregs(struct task_struct *t, elf_fpregset_t *fpu) +{ + int cpu = current_thread_info()->cpu; + + return save_fp_registers(userspace_pid[cpu], (unsigned long *) fpu); +} + |
