sh: ptrace: Introduce user_regset interface for gp regs.
[pandora-kernel.git] / arch / sh / kernel / ptrace_32.c
index ce0664a..5e3ba10 100644 (file)
@@ -1,12 +1,14 @@
 /*
- * linux/arch/sh/kernel/ptrace.c
+ * SuperH process tracing
  *
- * Original x86 implementation:
- *     By Ross Biro 1/23/92
- *     edited by Linus Torvalds
+ * Copyright (C) 1999, 2000  Kaz Kojima & Niibe Yutaka
+ * Copyright (C) 2002 - 2008  Paul Mundt
  *
- * SuperH version:   Copyright (C) 1999, 2000  Kaz Kojima & Niibe Yutaka
- * Audit support: Yuichi Nakamura <ynakam@hitachisoft.jp>
+ * Audit support by Yuichi Nakamura <ynakam@hitachisoft.jp>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
  */
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/signal.h>
 #include <linux/io.h>
 #include <linux/audit.h>
+#include <linux/seccomp.h>
+#include <linux/tracehook.h>
+#include <linux/elf.h>
+#include <linux/regset.h>
 #include <asm/uaccess.h>
 #include <asm/pgtable.h>
 #include <asm/system.h>
 #include <asm/processor.h>
 #include <asm/mmu_context.h>
-
-/*
- * does not yet catch signals sent when the child dies.
- * in exit.c or in signal.c.
- */
+#include <asm/syscalls.h>
 
 /*
  * This routine will get a word off of the process kernel stack.
@@ -57,7 +59,19 @@ static inline int put_stack_long(struct task_struct *task, int offset,
        return 0;
 }
 
-static void ptrace_disable_singlestep(struct task_struct *child)
+void user_enable_single_step(struct task_struct *child)
+{
+       /* Next scheduling will set up UBC */
+       if (child->thread.ubc_pc == 0)
+               ubc_usercnt += 1;
+
+       child->thread.ubc_pc = get_stack_long(child,
+                               offsetof(struct pt_regs, pc));
+
+       set_tsk_thread_flag(child, TIF_SINGLESTEP);
+}
+
+void user_disable_single_step(struct task_struct *child)
 {
        clear_tsk_thread_flag(child, TIF_SINGLESTEP);
 
@@ -81,21 +95,93 @@ static void ptrace_disable_singlestep(struct task_struct *child)
  */
 void ptrace_disable(struct task_struct *child)
 {
-       ptrace_disable_singlestep(child);
+       user_disable_single_step(child);
+}
+
+static int genregs_get(struct task_struct *target,
+                      const struct user_regset *regset,
+                      unsigned int pos, unsigned int count,
+                      void *kbuf, void __user *ubuf)
+{
+       const struct pt_regs *regs = task_pt_regs(target);
+       int ret;
+
+       ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
+                                 regs->regs,
+                                 0, 16 * sizeof(unsigned long));
+       if (!ret)
+               /* PC, PR, SR, GBR, MACH, MACL, TRA */
+               ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf,
+                                         &regs->pc,
+                                         offsetof(struct pt_regs, pc),
+                                         sizeof(struct pt_regs));
+       if (!ret)
+               ret = user_regset_copyout_zero(&pos, &count, &kbuf, &ubuf,
+                                              sizeof(struct pt_regs), -1);
+
+       return ret;
+}
+
+static int genregs_set(struct task_struct *target,
+                      const struct user_regset *regset,
+                      unsigned int pos, unsigned int count,
+                      const void *kbuf, const void __user *ubuf)
+{
+       struct pt_regs *regs = task_pt_regs(target);
+       int ret;
+
+       ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                regs->regs,
+                                0, 16 * sizeof(unsigned long));
+       if (!ret && count > 0)
+               ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf,
+                                        &regs->pc,
+                                        offsetof(struct pt_regs, pc),
+                                        sizeof(struct pt_regs));
+       if (!ret)
+               ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf,
+                                               sizeof(struct pt_regs), -1);
+
+       return ret;
 }
 
+/*
+ * These are our native regset flavours.
+ */
+enum sh_regset {
+       REGSET_GENERAL,
+};
+
+static const struct user_regset sh_regsets[] = {
+       /*
+        * Format is:
+        *      R0 --> R15
+        *      PC, PR, SR, GBR, MACH, MACL, TRA
+        */
+       [REGSET_GENERAL] = {
+               .core_note_type = NT_PRSTATUS,
+               .n              = ELF_NGREG,
+               .size           = sizeof(long),
+               .align          = sizeof(long),
+               .get            = genregs_get,
+               .set            = genregs_set,
+       },
+};
+
+static const struct user_regset_view user_sh_native_view = {
+       .name           = "sh",
+       .e_machine      = EM_SH,
+       .regsets        = sh_regsets,
+       .n              = ARRAY_SIZE(sh_regsets),
+};
+
 long arch_ptrace(struct task_struct *child, long request, long addr, long data)
 {
        struct user * dummy = NULL;
+       unsigned long __user *datap = (unsigned long __user *)data;
        int ret;
 
        switch (request) {
-       /* when I and D space are separate, these will need to be fixed. */
-       case PTRACE_PEEKTEXT: /* read word at location addr. */
-       case PTRACE_PEEKDATA:
-               ret = generic_ptrace_peekdata(child, addr, data);
-               break;
-
        /* read the word at location addr in the USER area. */
        case PTRACE_PEEKUSR: {
                unsigned long tmp;
@@ -121,16 +207,10 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                        tmp = !!tsk_used_math(child);
                else
                        tmp = 0;
-               ret = put_user(tmp, (unsigned long __user *)data);
+               ret = put_user(tmp, datap);
                break;
        }
 
-       /* when I and D space are separate, this will have to be fixed. */
-       case PTRACE_POKETEXT: /* write the word at location addr. */
-       case PTRACE_POKEDATA:
-               ret = generic_ptrace_pokedata(child, addr, data);
-               break;
-
        case PTRACE_POKEUSR: /* write the word at location addr in the USER area */
                ret = -EIO;
                if ((addr & 3) || addr < 0 ||
@@ -151,67 +231,16 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                }
                break;
 
-       case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */
-       case PTRACE_CONT: { /* restart after signal. */
-               ret = -EIO;
-               if (!valid_signal(data))
-                       break;
-               if (request == PTRACE_SYSCALL)
-                       set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
-               else
-                       clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
-
-               ptrace_disable_singlestep(child);
-
-               child->exit_code = data;
-               wake_up_process(child);
-               ret = 0;
-               break;
-       }
-
-/*
- * make the child exit.  Best I can do is send it a sigkill.
- * perhaps it should be put in the status that it wants to
- * exit.
- */
-       case PTRACE_KILL: {
-               ret = 0;
-               if (child->exit_state == EXIT_ZOMBIE)   /* already dead */
-                       break;
-               ptrace_disable_singlestep(child);
-               child->exit_code = SIGKILL;
-               wake_up_process(child);
-               break;
-       }
-
-       case PTRACE_SINGLESTEP: {  /* set the trap flag. */
-               long pc;
-               struct pt_regs *regs = NULL;
-
-               ret = -EIO;
-               if (!valid_signal(data))
-                       break;
-               clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
-               if ((child->ptrace & PT_DTRACE) == 0) {
-                       /* Spurious delayed TF traps may occur */
-                       child->ptrace |= PT_DTRACE;
-               }
-
-               pc = get_stack_long(child, (long)&regs->pc);
-
-               /* Next scheduling will set up UBC */
-               if (child->thread.ubc_pc == 0)
-                       ubc_usercnt += 1;
-               child->thread.ubc_pc = pc;
-
-               set_tsk_thread_flag(child, TIF_SINGLESTEP);
-               child->exit_code = data;
-               /* give it a chance to run. */
-               wake_up_process(child);
-               ret = 0;
-               break;
-       }
-
+       case PTRACE_GETREGS:
+               return copy_regset_to_user(child, &user_sh_native_view,
+                                          REGSET_GENERAL,
+                                          0, sizeof(struct pt_regs),
+                                          (void __user *)data);
+       case PTRACE_SETREGS:
+               return copy_regset_from_user(child, &user_sh_native_view,
+                                            REGSET_GENERAL,
+                                            0, sizeof(struct pt_regs),
+                                            (const void __user *)data);
 #ifdef CONFIG_SH_DSP
        case PTRACE_GETDSPREGS: {
                unsigned long dp;
@@ -220,7 +249,7 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                dp = ((unsigned long) child) + THREAD_SIZE -
                         sizeof(struct pt_dspregs);
                if (*((int *) (dp - 4)) == SR_FD) {
-                       copy_to_user(addr, (void *) dp,
+                       copy_to_user((void *)addr, (void *) dp,
                                sizeof(struct pt_dspregs));
                        ret = 0;
                }
@@ -234,12 +263,35 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
                dp = ((unsigned long) child) + THREAD_SIZE -
                         sizeof(struct pt_dspregs);
                if (*((int *) (dp - 4)) == SR_FD) {
-                       copy_from_user((void *) dp, addr,
+                       copy_from_user((void *) dp, (void *)addr,
                                sizeof(struct pt_dspregs));
                        ret = 0;
                }
                break;
        }
+#endif
+#ifdef CONFIG_BINFMT_ELF_FDPIC
+       case PTRACE_GETFDPIC: {
+               unsigned long tmp = 0;
+
+               switch (addr) {
+               case PTRACE_GETFDPIC_EXEC:
+                       tmp = child->mm->context.exec_fdpic_loadmap;
+                       break;
+               case PTRACE_GETFDPIC_INTERP:
+                       tmp = child->mm->context.interp_fdpic_loadmap;
+                       break;
+               default:
+                       break;
+               }
+
+               ret = 0;
+               if (put_user(tmp, datap)) {
+                       ret = -EFAULT;
+                       break;
+               }
+               break;
+       }
 #endif
        default:
                ret = ptrace_request(child, request, addr, data);
@@ -249,39 +301,49 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data)
        return ret;
 }
 
-asmlinkage void do_syscall_trace(struct pt_regs *regs, int entryexit)
+static inline int audit_arch(void)
 {
-       struct task_struct *tsk = current;
+       int arch = EM_SH;
 
-       if (unlikely(current->audit_context) && entryexit)
-               audit_syscall_exit(AUDITSC_RESULT(regs->regs[0]),
-                                  regs->regs[0]);
+#ifdef CONFIG_CPU_LITTLE_ENDIAN
+       arch |= __AUDIT_ARCH_LE;
+#endif
+
+       return arch;
+}
 
-       if (!test_thread_flag(TIF_SYSCALL_TRACE) &&
-           !test_thread_flag(TIF_SINGLESTEP))
-               goto out;
-       if (!(tsk->ptrace & PT_PTRACED))
-               goto out;
+asmlinkage long do_syscall_trace_enter(struct pt_regs *regs)
+{
+       long ret = 0;
 
-       /* the 0x80 provides a way for the tracing parent to distinguish
-          between a syscall stop and SIGTRAP delivery */
-       ptrace_notify(SIGTRAP | ((current->ptrace & PT_TRACESYSGOOD) &&
-                                !test_thread_flag(TIF_SINGLESTEP) ? 0x80 : 0));
+       secure_computing(regs->regs[0]);
 
-       /*
-        * this isn't the same as continuing with a signal, but it will do
-        * for normal use.  strace only continues with a signal if the
-        * stopping signal is not SIGTRAP.  -brl
-        */
-       if (tsk->exit_code) {
-               send_sig(tsk->exit_code, tsk, 1);
-               tsk->exit_code = 0;
-       }
+       if (test_thread_flag(TIF_SYSCALL_TRACE) &&
+           tracehook_report_syscall_entry(regs))
+               /*
+                * Tracing decided this syscall should not happen.
+                * We'll return a bogus call number to get an ENOSYS
+                * error, but leave the original number in regs->regs[0].
+                */
+               ret = -1L;
 
-out:
-       if (unlikely(current->audit_context) && !entryexit)
-               audit_syscall_entry(AUDIT_ARCH_SH, regs->regs[3],
+       if (unlikely(current->audit_context))
+               audit_syscall_entry(audit_arch(), regs->regs[3],
                                    regs->regs[4], regs->regs[5],
                                    regs->regs[6], regs->regs[7]);
 
+       return ret ?: regs->regs[0];
+}
+
+asmlinkage void do_syscall_trace_leave(struct pt_regs *regs)
+{
+       int step;
+
+       if (unlikely(current->audit_context))
+               audit_syscall_exit(AUDITSC_RESULT(regs->regs[0]),
+                                  regs->regs[0]);
+
+       step = test_thread_flag(TIF_SINGLESTEP);
+       if (step || test_thread_flag(TIF_SYSCALL_TRACE))
+               tracehook_report_syscall_exit(regs, step);
 }