diff --git a/arch/mips/include/asm/ptrace.h b/arch/mips/include/asm/ptrace.h index daf3cf244ea9..c733daefd015 100644 --- a/arch/mips/include/asm/ptrace.h +++ b/arch/mips/include/asm/ptrace.h @@ -186,4 +186,6 @@ static inline void user_stack_pointer_set(struct pt_regs *regs, regs->regs[29] = val; } +#define arch_has_single_step() (1) + #endif /* _ASM_PTRACE_H */ diff --git a/arch/mips/include/asm/thread_info.h b/arch/mips/include/asm/thread_info.h index e2c352da3877..bd4dbb5b2900 100644 --- a/arch/mips/include/asm/thread_info.h +++ b/arch/mips/include/asm/thread_info.h @@ -35,6 +35,10 @@ struct thread_info { */ struct pt_regs *regs; long syscall; /* syscall number */ + + int bpt_nsaved; + unsigned long bpt_addr[1]; /* breakpoint handling */ + unsigned int bpt_insn[1]; }; /* @@ -117,6 +121,7 @@ static inline struct thread_info *current_thread_info(void) #define TIF_UPROBE 6 /* breakpointed or singlestepping */ #define TIF_NOTIFY_SIGNAL 7 /* signal notifications exist */ #define TIF_RESTORE_SIGMASK 9 /* restore signal mask in do_signal() */ +#define TIF_SINGLESTEP 10 /* restore singlestep on return to user mode */ #define TIF_USEDFPU 16 /* FPU was used by this task this quantum (SMP) */ #define TIF_MEMDIE 18 /* is terminating due to OOM killer */ #define TIF_NOHZ 19 /* in adaptive nohz mode */ diff --git a/arch/mips/kernel/ptrace.c b/arch/mips/kernel/ptrace.c index db7c5be1d4a3..f29141922001 100644 --- a/arch/mips/kernel/ptrace.c +++ b/arch/mips/kernel/ptrace.c @@ -45,10 +45,15 @@ #include #include #include +#include #define CREATE_TRACE_POINTS #include +#include "probes-common.h" + +#define BREAKINST 0x0000000d + /* * Called by kernel/ptrace.c when detaching.. * @@ -58,6 +63,7 @@ void ptrace_disable(struct task_struct *child) { /* Don't load the watchpoint registers for the ex-child. */ clear_tsk_thread_flag(child, TIF_LOAD_WATCH); + user_disable_single_step(child); } /* @@ -1072,6 +1078,108 @@ const struct user_regset_view *task_user_regset_view(struct task_struct *task) #endif } +static int read_insn(struct task_struct *task, unsigned long addr, unsigned int *insn) +{ + int copied = access_process_vm(task, addr, insn, + sizeof(unsigned int), FOLL_FORCE); + + if (copied != sizeof(unsigned int)) { + pr_err("failed to read instruction from 0x%lx\n", addr); + return -EIO; + } + + return 0; +} + +static int write_insn(struct task_struct *task, unsigned long addr, unsigned int insn) +{ + int copied = access_process_vm(task, addr, &insn, + sizeof(unsigned int), FOLL_FORCE | FOLL_WRITE); + + if (copied != sizeof(unsigned int)) { + pr_err("failed to write instruction to 0x%lx\n", addr); + return -EIO; + } + + return 0; +} + +static int insn_has_delayslot(union mips_instruction insn) +{ + return __insn_has_delay_slot(insn); +} + +static void ptrace_set_bpt(struct task_struct *child) +{ + union mips_instruction mips_insn = { 0 }; + struct pt_regs *regs; + unsigned long pc; + unsigned int insn; + int i, ret, nsaved = 0; + + regs = task_pt_regs(child); + pc = regs->cp0_epc; + + ret = read_insn(child, pc, &insn); + if (ret < 0) + return; + + if (insn_has_delayslot(mips_insn)) { + pr_info("executing branch insn\n"); + ret = __compute_return_epc(regs); + if (ret < 0) + return; + task_thread_info(child)->bpt_addr[nsaved++] = regs->cp0_epc; + } else { + pr_info("executing normal insn\n"); + task_thread_info(child)->bpt_addr[nsaved++] = pc + 4; + } + + /* install breakpoints */ + for (i = 0; i < nsaved; i++) { + ret = read_insn(child, task_thread_info(child)->bpt_addr[i], &insn); + if (ret < 0) + return; + + task_thread_info(child)->bpt_insn[i] = insn; + + ret = write_insn(child, task_thread_info(child)->bpt_addr[i], BREAKINST); + if (ret < 0) + return; + } + + task_thread_info(child)->bpt_nsaved = nsaved; +} + +static void ptrace_cancel_bpt(struct task_struct *child) +{ + int i, nsaved = task_thread_info(child)->bpt_nsaved; + + task_thread_info(child)->bpt_nsaved = 0; + + if (nsaved > 1) { + pr_info("%s: bogus nsaved: %d!\n", __func__, nsaved); + nsaved = 1; + } + + for (i = 0; i < nsaved; i++) { + write_insn(child, task_thread_info(child)->bpt_addr[i], + task_thread_info(child)->bpt_insn[i]); + } +} + +void user_enable_single_step(struct task_struct *child) +{ + set_tsk_thread_flag(child, TIF_SINGLESTEP); + ptrace_set_bpt(child); +} + +void user_disable_single_step(struct task_struct *child) +{ + clear_tsk_thread_flag(child, TIF_SINGLESTEP); + ptrace_cancel_bpt(child); +} + long arch_ptrace(struct task_struct *child, long request, unsigned long addr, unsigned long data) { diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c index f1e985109da0..82d11d88d3a5 100644 --- a/arch/mips/kernel/signal.c +++ b/arch/mips/kernel/signal.c @@ -849,7 +849,7 @@ static void handle_signal(struct ksignal *ksig, struct pt_regs *regs) ret = abi->setup_frame(vdso + abi->vdso->off_sigreturn, ksig, regs, oldset); - signal_setup_done(ret, ksig, 0); + signal_setup_done(ret, ksig, test_thread_flag(TIF_SINGLESTEP)); } static void do_signal(struct pt_regs *regs)