ARC: ptrace support
authorVineet Gupta <vgupta@synopsys.com>
Fri, 18 Jan 2013 09:42:22 +0000 (15:12 +0530)
committerVineet Gupta <vgupta@synopsys.com>
Fri, 15 Feb 2013 17:45:59 +0000 (23:15 +0530)
Signed-off-by: Vineet Gupta <vgupta@synopsys.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
arch/arc/Kconfig
arch/arc/kernel/Makefile
arch/arc/kernel/entry.S
arch/arc/kernel/ptrace.c

index 03347cb..409b937 100644 (file)
@@ -22,6 +22,7 @@ config ARC
        select GENERIC_PENDING_IRQ if SMP
        select GENERIC_SIGALTSTACK
        select GENERIC_SMP_IDLE_THREAD
+       select HAVE_ARCH_TRACEHOOK
        select HAVE_GENERIC_HARDIRQS
        select HAVE_MEMBLOCK
        select IRQ_DOMAIN
index c9ec066..f488589 100644 (file)
@@ -5,6 +5,9 @@
 # it under the terms of the GNU General Public License version 2 as
 # published by the Free Software Foundation.
 
+# Pass UTS_MACHINE for user_regset definition
+CFLAGS_ptrace.o                += -DUTS_MACHINE='"$(UTS_MACHINE)"'
+
 obj-y  := arcksyms.o setup.o irq.o time.o reset.o ptrace.o entry.o process.o
 obj-y  += signal.o traps.o sys.o troubleshoot.o stacktrace.o clk.o
 obj-y  += devtree.o
index 69d0d37..76697ae 100644 (file)
@@ -7,6 +7,13 @@
  * it under the terms of the GNU General Public License version 2 as
  * published by the Free Software Foundation.
  *
+ * vineetg: Feb 2011 (ptrace low level code fixes)
+ *  -traced syscall return code (r0) was not saved into pt_regs for restoring
+ *   into user reg-file when traded task rets to user space.
+ *  -syscalls needing arch-wrappers (mainly for passing sp as pt_regs)
+ *   were not invoking post-syscall trace hook (jumping directly into
+ *   ret_from_system_call)
+ *
  * vineetg: Nov 2010:
  *  -Vector table jumps (@8 bytes) converted into branches (@4 bytes)
  *  -To maintain the slot size of 8 bytes/vector, added nop, which is
@@ -347,6 +354,50 @@ ARC_ENTRY EV_Extension
        b   ret_from_exception
 ARC_EXIT EV_Extension
 
+;######################### System Call Tracing #########################
+
+tracesys:
+       ; save EFA in case tracer wants the PC of traced task
+       ; using ERET won't work since next-PC has already committed
+       lr  r12, [efa]
+       GET_CURR_TASK_FIELD_PTR   TASK_THREAD, r11
+       st  r12, [r11, THREAD_FAULT_ADDR]
+
+       ; PRE Sys Call Ptrace hook
+       mov r0, sp                      ; pt_regs needed
+       bl  @syscall_trace_entry
+
+       ; Tracing code now returns the syscall num (orig or modif)
+       mov r8, r0
+
+       ; Do the Sys Call as we normally would.
+       ; Validate the Sys Call number
+       cmp     r8,  NR_syscalls
+       mov.hi  r0, -ENOSYS
+       bhi     tracesys_exit
+
+       ; Restore the sys-call args. Mere invocation of the hook abv could have
+       ; clobbered them (since they are in scratch regs). The tracer could also
+       ; have deliberately changed the syscall args: r0-r7
+       ld  r0, [sp, PT_r0]
+       ld  r1, [sp, PT_r1]
+       ld  r2, [sp, PT_r2]
+       ld  r3, [sp, PT_r3]
+       ld  r4, [sp, PT_r4]
+       ld  r5, [sp, PT_r5]
+       ld  r6, [sp, PT_r6]
+       ld  r7, [sp, PT_r7]
+       ld.as   r9, [sys_call_table, r8]
+       jl      [r9]        ; Entry into Sys Call Handler
+
+tracesys_exit:
+       st  r0, [sp, PT_r0]     ; sys call return value in pt_regs
+
+       ;POST Sys Call Ptrace Hook
+       bl  @syscall_trace_exit
+       b   ret_from_exception ; NOT ret_from_system_call at is saves r0 which
+       ; we'd done before calling post hook above
+
 ;################### Break Point TRAP ##########################
 
        ; ======= (5b) Trap is due to Break-Point =========
@@ -412,6 +463,11 @@ ARC_ENTRY EV_Trap
        ; Before doing anything, return from CPU Exception Mode
        FAKE_RET_FROM_EXCPN r11
 
+       ; If syscall tracing ongoing, invoke pre-pos-hooks
+       GET_CURR_THR_INFO_FLAGS   r10
+       btst r10, TIF_SYSCALL_TRACE
+       bnz tracesys  ; this never comes back
+
        ;============ This is normal System Call case ==========
        ; Sys-call num shd not exceed the total system calls avail
        cmp     r8,  NR_syscalls
@@ -608,6 +664,10 @@ ARC_ENTRY sys_fork_wrapper
        bl  @sys_fork
        DISCARD_CALLEE_SAVED_USER
 
+       GET_CURR_THR_INFO_FLAGS   r10
+       btst r10, TIF_SYSCALL_TRACE
+       bnz  tracesys_exit
+
        b ret_from_system_call
 ARC_EXIT sys_fork_wrapper
 
@@ -616,6 +676,10 @@ ARC_ENTRY sys_vfork_wrapper
        bl  @sys_vfork
        DISCARD_CALLEE_SAVED_USER
 
+       GET_CURR_THR_INFO_FLAGS   r10
+       btst r10, TIF_SYSCALL_TRACE
+       bnz  tracesys_exit
+
        b ret_from_system_call
 ARC_EXIT sys_vfork_wrapper
 
@@ -624,5 +688,9 @@ ARC_ENTRY sys_clone_wrapper
        bl  @sys_clone
        DISCARD_CALLEE_SAVED_USER
 
+       GET_CURR_THR_INFO_FLAGS   r10
+       btst r10, TIF_SYSCALL_TRACE
+       bnz  tracesys_exit
+
        b ret_from_system_call
 ARC_EXIT sys_clone_wrapper
index 1cf944a..c6a81c5 100644 (file)
@@ -7,6 +7,122 @@
  */
 
 #include <linux/ptrace.h>
+#include <linux/tracehook.h>
+#include <linux/regset.h>
+#include <linux/unistd.h>
+#include <linux/elf.h>
+
+static struct callee_regs *task_callee_regs(struct task_struct *tsk)
+{
+       struct callee_regs *tmp = (struct callee_regs *)tsk->thread.callee_reg;
+       return tmp;
+}
+
+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 *ptregs = task_pt_regs(target);
+       const struct callee_regs *cregs = task_callee_regs(target);
+       int ret = 0;
+       unsigned int stop_pc_val;
+
+#define REG_O_CHUNK(START, END, PTR)   \
+       if (!ret)       \
+               ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, PTR, \
+                       offsetof(struct user_regs_struct, START), \
+                       offsetof(struct user_regs_struct, END));
+
+#define REG_O_ONE(LOC, PTR)    \
+       if (!ret)               \
+               ret = user_regset_copyout(&pos, &count, &kbuf, &ubuf, PTR, \
+                       offsetof(struct user_regs_struct, LOC), \
+                       offsetof(struct user_regs_struct, LOC) + 4);
+
+       REG_O_CHUNK(scratch, callee, ptregs);
+       REG_O_CHUNK(callee, efa, cregs);
+       REG_O_CHUNK(efa, stop_pc, &target->thread.fault_address);
+
+       if (!ret) {
+               if (in_brkpt_trap(ptregs)) {
+                       stop_pc_val = target->thread.fault_address;
+                       pr_debug("\t\tstop_pc (brk-pt)\n");
+               } else {
+                       stop_pc_val = ptregs->ret;
+                       pr_debug("\t\tstop_pc (others)\n");
+               }
+
+               REG_O_ONE(stop_pc, &stop_pc_val);
+       }
+
+       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)
+{
+       const struct pt_regs *ptregs = task_pt_regs(target);
+       const struct callee_regs *cregs = task_callee_regs(target);
+       int ret = 0;
+
+#define REG_IN_CHUNK(FIRST, NEXT, PTR) \
+       if (!ret)                       \
+               ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, \
+                       (void *)(PTR), \
+                       offsetof(struct user_regs_struct, FIRST), \
+                       offsetof(struct user_regs_struct, NEXT));
+
+#define REG_IN_ONE(LOC, PTR)           \
+       if (!ret)                       \
+               ret = user_regset_copyin(&pos, &count, &kbuf, &ubuf, \
+                       (void *)(PTR), \
+                       offsetof(struct user_regs_struct, LOC), \
+                       offsetof(struct user_regs_struct, LOC) + 4);
+
+#define REG_IGNORE_ONE(LOC)            \
+       if (!ret)                       \
+               ret = user_regset_copyin_ignore(&pos, &count, &kbuf, &ubuf, \
+                       offsetof(struct user_regs_struct, LOC), \
+                       offsetof(struct user_regs_struct, LOC) + 4);
+
+       /* TBD: disallow updates to STATUS32, orig_r8 etc*/
+       REG_IN_CHUNK(scratch, callee, ptregs);  /* pt_regs[bta..orig_r8] */
+       REG_IN_CHUNK(callee, efa, cregs);       /* callee_regs[r25..r13] */
+       REG_IGNORE_ONE(efa);                    /* efa update invalid */
+       REG_IN_ONE(stop_pc, &ptregs->ret);      /* stop_pc: PC update */
+
+       return ret;
+}
+
+enum arc_getset {
+       REGSET_GENERAL,
+};
+
+static const struct user_regset arc_regsets[] = {
+       [REGSET_GENERAL] = {
+              .core_note_type = NT_PRSTATUS,
+              .n = ELF_NGREG,
+              .size = sizeof(unsigned long),
+              .align = sizeof(unsigned long),
+              .get = genregs_get,
+              .set = genregs_set,
+       }
+};
+
+static const struct user_regset_view user_arc_view = {
+       .name           = UTS_MACHINE,
+       .e_machine      = EM_ARCOMPACT,
+       .regsets        = arc_regsets,
+       .n              = ARRAY_SIZE(arc_regsets)
+};
+
+const struct user_regset_view *task_user_regset_view(struct task_struct *task)
+{
+       return &user_arc_view;
+}
 
 void ptrace_disable(struct task_struct *child)
 {
@@ -16,11 +132,27 @@ long arch_ptrace(struct task_struct *child, long request,
                 unsigned long addr, unsigned long data)
 {
        int ret = -EIO;
+
+       pr_debug("REQ=%ld: ADDR =0x%lx, DATA=0x%lx)\n", request, addr, data);
+
+       switch (request) {
+       default:
+               ret = ptrace_request(child, request, addr, data);
+               break;
+       }
+
        return ret;
 }
 
+asmlinkage int syscall_trace_entry(struct pt_regs *regs)
+{
+       if (tracehook_report_syscall_entry(regs))
+               return ULONG_MAX;
 
-const struct user_regset_view *task_user_regset_view(struct task_struct *task)
+       return regs->r8;
+}
+
+asmlinkage void syscall_trace_exit(struct pt_regs *regs)
 {
-       return (const struct user_regset_view *)NULL;
+       tracehook_report_syscall_exit(regs, 0);
 }