Skip to content

Commit 3a5a436

Browse files
committed
arm64: ptrace: Override SPSR.SS when single-stepping is enabled
Luis reports that, when reverse debugging with GDB, single-step does not function as expected on arm64: | I've noticed, under very specific conditions, that a PTRACE_SINGLESTEP | request by GDB won't execute the underlying instruction. As a consequence, | the PC doesn't move, but we return a SIGTRAP just like we would for a | regular successful PTRACE_SINGLESTEP request. The underlying problem is that when the CPU register state is restored as part of a reverse step, the SPSR.SS bit is cleared and so the hardware single-step state can transition to the "active-pending" state, causing an unexpected step exception to be taken immediately if a step operation is attempted. In hindsight, we probably shouldn't have exposed SPSR.SS in the pstate accessible by the GPR regset, but it's a bit late for that now. Instead, simply prevent userspace from configuring the bit to a value which is inconsistent with the TIF_SINGLESTEP state for the task being traced. Cc: <[email protected]> Cc: Mark Rutland <[email protected]> Cc: Keno Fischer <[email protected]> Link: https://lore.kernel.org/r/[email protected] Reported-by: Luis Machado <[email protected]> Tested-by: Luis Machado <[email protected]> Signed-off-by: Will Deacon <[email protected]>
1 parent ac2081c commit 3a5a436

File tree

3 files changed

+20
-6
lines changed

3 files changed

+20
-6
lines changed

arch/arm64/include/asm/debug-monitors.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ void disable_debug_monitors(enum dbg_active_el el);
109109

110110
void user_rewind_single_step(struct task_struct *task);
111111
void user_fastforward_single_step(struct task_struct *task);
112+
void user_regs_reset_single_step(struct user_pt_regs *regs,
113+
struct task_struct *task);
112114

113115
void kernel_enable_single_step(struct pt_regs *regs);
114116
void kernel_disable_single_step(void);

arch/arm64/kernel/debug-monitors.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,17 +141,20 @@ postcore_initcall(debug_monitors_init);
141141
/*
142142
* Single step API and exception handling.
143143
*/
144-
static void set_regs_spsr_ss(struct pt_regs *regs)
144+
static void set_user_regs_spsr_ss(struct user_pt_regs *regs)
145145
{
146146
regs->pstate |= DBG_SPSR_SS;
147147
}
148-
NOKPROBE_SYMBOL(set_regs_spsr_ss);
148+
NOKPROBE_SYMBOL(set_user_regs_spsr_ss);
149149

150-
static void clear_regs_spsr_ss(struct pt_regs *regs)
150+
static void clear_user_regs_spsr_ss(struct user_pt_regs *regs)
151151
{
152152
regs->pstate &= ~DBG_SPSR_SS;
153153
}
154-
NOKPROBE_SYMBOL(clear_regs_spsr_ss);
154+
NOKPROBE_SYMBOL(clear_user_regs_spsr_ss);
155+
156+
#define set_regs_spsr_ss(r) set_user_regs_spsr_ss(&(r)->user_regs)
157+
#define clear_regs_spsr_ss(r) clear_user_regs_spsr_ss(&(r)->user_regs)
155158

156159
static DEFINE_SPINLOCK(debug_hook_lock);
157160
static LIST_HEAD(user_step_hook);
@@ -402,6 +405,15 @@ void user_fastforward_single_step(struct task_struct *task)
402405
clear_regs_spsr_ss(task_pt_regs(task));
403406
}
404407

408+
void user_regs_reset_single_step(struct user_pt_regs *regs,
409+
struct task_struct *task)
410+
{
411+
if (test_tsk_thread_flag(task, TIF_SINGLESTEP))
412+
set_user_regs_spsr_ss(regs);
413+
else
414+
clear_user_regs_spsr_ss(regs);
415+
}
416+
405417
/* Kernel API */
406418
void kernel_enable_single_step(struct pt_regs *regs)
407419
{

arch/arm64/kernel/ptrace.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1947,8 +1947,8 @@ static int valid_native_regs(struct user_pt_regs *regs)
19471947
*/
19481948
int valid_user_regs(struct user_pt_regs *regs, struct task_struct *task)
19491949
{
1950-
if (!test_tsk_thread_flag(task, TIF_SINGLESTEP))
1951-
regs->pstate &= ~DBG_SPSR_SS;
1950+
/* https://lore.kernel.org/lkml/20191118131525.GA4180@willie-the-truck */
1951+
user_regs_reset_single_step(regs, task);
19521952

19531953
if (is_compat_thread(task_thread_info(task)))
19541954
return valid_compat_regs(regs);

0 commit comments

Comments
 (0)