From: Heiher <r@xxxxxx> I found an operation can trigger a kernel hang, when we write an user-space address to hardward watchpoints before access this address in kernel mode. Looks the problem is the current thread can't return to user mode because memory accesses blocked by hardward watchpoints. so SIGTRAP not handled and the debugger can't receive watchpoints event. Execution flow: 1. write address to cp0 watchpoints. 2. access this address in kernel mode, then an exception triggered by watchpoints. 3. goto do_watch() to handle exception. 4. Force a SIGTRAP to current thread. 5. exception return, goto step 2. If we clear the watchpoints in do_watch() for exceptions from kernel mode, and re-install watchpoints on thread return to user mode, it'll works fine. Test case: #include <stdio.h> #include <unistd.h> #include <signal.h> int main (int argc, char *argv[]) { char buf[16]; printf ("%p\n", buf); raise (SIGINT); write (1, buf, 16); return 0; } # gcc -O0 -o t t.c # gdb ./t (gdb) r (gdb) watch *<printed buf address> (gdb) c Signed-off-by: Heiher <r@xxxxxx> --- arch/mips/kernel/entry.S | 23 +++++++++++++++++++++++ arch/mips/kernel/traps.c | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/arch/mips/kernel/entry.S b/arch/mips/kernel/entry.S index 38a302919e6b..6094844fc63f 100644 --- a/arch/mips/kernel/entry.S +++ b/arch/mips/kernel/entry.S @@ -49,6 +49,13 @@ resume_userspace: # interrupt setting need_resched # between sampling and return LONG_L a2, TI_FLAGS($28) # current->work + li t0, _TIF_LOAD_WATCH + and t0, a2 + beqz t0, 1f + move a0, $28 + jal mips_install_watch_registers + LONG_L a2, TI_FLAGS($28) # current->work +1: andi t0, a2, _TIF_WORK_MASK # (ignoring syscall_trace) bnez t0, work_pending j restore_all @@ -82,7 +89,15 @@ FEXPORT(syscall_exit) local_irq_disable # make sure need_resched and # signals dont change between # sampling and return + + LONG_L a2, TI_FLAGS($28) # current->work + li t0, _TIF_LOAD_WATCH + and t0, a2 + beqz t0, 1f + move a0, $28 + jal mips_install_watch_registers LONG_L a2, TI_FLAGS($28) # current->work +1: li t0, _TIF_ALLWORK_MASK and t0, a2, t0 bnez t0, syscall_exit_work @@ -143,7 +158,15 @@ work_notifysig: # deal with pending signals and FEXPORT(syscall_exit_partial) local_irq_disable # make sure need_resched doesn't # change between and return + + LONG_L a2, TI_FLAGS($28) # current->work + li t0, _TIF_LOAD_WATCH + and t0, a2 + beqz t0, 1f + move a0, $28 + jal mips_install_watch_registers LONG_L a2, TI_FLAGS($28) # current->work +1: li t0, _TIF_ALLWORK_MASK and t0, a2 beqz t0, restore_partial diff --git a/arch/mips/kernel/traps.c b/arch/mips/kernel/traps.c index 967e9e4e795e..22f671263b27 100644 --- a/arch/mips/kernel/traps.c +++ b/arch/mips/kernel/traps.c @@ -1525,7 +1525,7 @@ asmlinkage void do_watch(struct pt_regs *regs) * their values and send SIGTRAP. Otherwise another thread * left the registers set, clear them and continue. */ - if (test_tsk_thread_flag(current, TIF_LOAD_WATCH)) { + if (user_mode(regs) && test_tsk_thread_flag(current, TIF_LOAD_WATCH)) { mips_read_watch_registers(); local_irq_enable(); force_sig_info(SIGTRAP, &info, current); -- 2.16.3