Commit d53d9bc0 authored by Peter Zijlstra's avatar Peter Zijlstra Committed by Thomas Gleixner

x86/debug: Change thread.debugreg6 to thread.virtual_dr6

Current usage of thread.debugreg6 is convoluted at best. It starts life as
a copy of the hardware DR6 value, but then various bits are cleared and
set.

Replace this with a new variable thread.virtual_dr6 that is initialized to
0 when DR6 is read and only gains bits, at the same time the actual (on
stack) dr6 value which is read from the hardware only gets bits cleared.
Suggested-by: default avatarAndy Lutomirski <luto@kernel.org>
Signed-off-by: default avatarPeter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: default avatarThomas Gleixner <tglx@linutronix.de>
Tested-by: default avatarDaniel Thompson <daniel.thompson@linaro.org>
Link: https://lore.kernel.org/r/20200902133201.415372940@infradead.org
parent f4956cf8
...@@ -517,7 +517,7 @@ struct thread_struct { ...@@ -517,7 +517,7 @@ struct thread_struct {
/* Save middle states of ptrace breakpoints */ /* Save middle states of ptrace breakpoints */
struct perf_event *ptrace_bps[HBP_NUM]; struct perf_event *ptrace_bps[HBP_NUM];
/* Debug status used for traps, single steps, etc... */ /* Debug status used for traps, single steps, etc... */
unsigned long debugreg6; unsigned long virtual_dr6;
/* Keep track of the exact dr7 value set by the user */ /* Keep track of the exact dr7 value set by the user */
unsigned long ptrace_dr7; unsigned long ptrace_dr7;
/* Fault info: */ /* Fault info: */
......
...@@ -454,7 +454,7 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk) ...@@ -454,7 +454,7 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
t->ptrace_bps[i] = NULL; t->ptrace_bps[i] = NULL;
} }
t->debugreg6 = 0; t->virtual_dr6 = 0;
t->ptrace_dr7 = 0; t->ptrace_dr7 = 0;
} }
...@@ -489,8 +489,8 @@ static int hw_breakpoint_handler(struct die_args *args) ...@@ -489,8 +489,8 @@ static int hw_breakpoint_handler(struct die_args *args)
{ {
int i, rc = NOTIFY_STOP; int i, rc = NOTIFY_STOP;
struct perf_event *bp; struct perf_event *bp;
unsigned long dr6;
unsigned long *dr6_p; unsigned long *dr6_p;
unsigned long dr6;
/* The DR6 value is pointed by args->err */ /* The DR6 value is pointed by args->err */
dr6_p = (unsigned long *)ERR_PTR(args->err); dr6_p = (unsigned long *)ERR_PTR(args->err);
...@@ -504,12 +504,6 @@ static int hw_breakpoint_handler(struct die_args *args) ...@@ -504,12 +504,6 @@ static int hw_breakpoint_handler(struct die_args *args)
if ((dr6 & DR_TRAP_BITS) == 0) if ((dr6 & DR_TRAP_BITS) == 0)
return NOTIFY_DONE; return NOTIFY_DONE;
/*
* Reset the DRn bits in the virtualized register value.
* The ptrace trigger routine will add in whatever is needed.
*/
current->thread.debugreg6 &= ~DR_TRAP_BITS;
/* Handle all the breakpoints that were triggered */ /* Handle all the breakpoints that were triggered */
for (i = 0; i < HBP_NUM; ++i) { for (i = 0; i < HBP_NUM; ++i) {
if (likely(!(dr6 & (DR_TRAP0 << i)))) if (likely(!(dr6 & (DR_TRAP0 << i))))
...@@ -554,7 +548,7 @@ static int hw_breakpoint_handler(struct die_args *args) ...@@ -554,7 +548,7 @@ static int hw_breakpoint_handler(struct die_args *args)
* breakpoints (to generate signals) and b) when the system has * breakpoints (to generate signals) and b) when the system has
* taken exception due to multiple causes * taken exception due to multiple causes
*/ */
if ((current->thread.debugreg6 & DR_TRAP_BITS) || if ((current->thread.virtual_dr6 & DR_TRAP_BITS) ||
(dr6 & (~DR_TRAP_BITS))) (dr6 & (~DR_TRAP_BITS)))
rc = NOTIFY_DONE; rc = NOTIFY_DONE;
......
...@@ -629,9 +629,10 @@ static void kgdb_hw_overflow_handler(struct perf_event *event, ...@@ -629,9 +629,10 @@ static void kgdb_hw_overflow_handler(struct perf_event *event,
struct task_struct *tsk = current; struct task_struct *tsk = current;
int i; int i;
for (i = 0; i < 4; i++) for (i = 0; i < 4; i++) {
if (breakinfo[i].enabled) if (breakinfo[i].enabled)
tsk->thread.debugreg6 |= (DR_TRAP0 << i); tsk->thread.virtual_dr6 |= (DR_TRAP0 << i);
}
} }
void kgdb_arch_late(void) void kgdb_arch_late(void)
......
...@@ -465,7 +465,7 @@ static void ptrace_triggered(struct perf_event *bp, ...@@ -465,7 +465,7 @@ static void ptrace_triggered(struct perf_event *bp,
break; break;
} }
thread->debugreg6 |= (DR_TRAP0 << i); thread->virtual_dr6 |= (DR_TRAP0 << i);
} }
/* /*
...@@ -601,7 +601,7 @@ static unsigned long ptrace_get_debugreg(struct task_struct *tsk, int n) ...@@ -601,7 +601,7 @@ static unsigned long ptrace_get_debugreg(struct task_struct *tsk, int n)
if (bp) if (bp)
val = bp->hw.info.address; val = bp->hw.info.address;
} else if (n == 6) { } else if (n == 6) {
val = thread->debugreg6 ^ DR6_RESERVED; /* Flip back to arch polarity */ val = thread->virtual_dr6 ^ DR6_RESERVED; /* Flip back to arch polarity */
} else if (n == 7) { } else if (n == 7) {
val = thread->ptrace_dr7; val = thread->ptrace_dr7;
} }
...@@ -657,7 +657,7 @@ static int ptrace_set_debugreg(struct task_struct *tsk, int n, ...@@ -657,7 +657,7 @@ static int ptrace_set_debugreg(struct task_struct *tsk, int n,
if (n < HBP_NUM) { if (n < HBP_NUM) {
rc = ptrace_set_breakpoint_addr(tsk, n, val); rc = ptrace_set_breakpoint_addr(tsk, n, val);
} else if (n == 6) { } else if (n == 6) {
thread->debugreg6 = val ^ DR6_RESERVED; /* Flip to positive polarity */ thread->virtual_dr6 = val ^ DR6_RESERVED; /* Flip to positive polarity */
rc = 0; rc = 0;
} else if (n == 7) { } else if (n == 7) {
rc = ptrace_write_dr7(tsk, val); rc = ptrace_write_dr7(tsk, val);
......
...@@ -748,6 +748,12 @@ static __always_inline unsigned long debug_read_clear_dr6(void) ...@@ -748,6 +748,12 @@ static __always_inline unsigned long debug_read_clear_dr6(void)
set_debugreg(DR6_RESERVED, 6); set_debugreg(DR6_RESERVED, 6);
dr6 ^= DR6_RESERVED; /* Flip to positive polarity */ dr6 ^= DR6_RESERVED; /* Flip to positive polarity */
/*
* Clear the virtual DR6 value, ptrace routines will set bits here for
* things we want signals for.
*/
current->thread.virtual_dr6 = 0;
/* /*
* The SDM says "The processor clears the BTF flag when it * The SDM says "The processor clears the BTF flag when it
* generates a debug exception." Clear TIF_BLOCKSTEP to keep * generates a debug exception." Clear TIF_BLOCKSTEP to keep
...@@ -785,17 +791,16 @@ static __always_inline unsigned long debug_read_clear_dr6(void) ...@@ -785,17 +791,16 @@ static __always_inline unsigned long debug_read_clear_dr6(void)
static bool notify_debug(struct pt_regs *regs, unsigned long *dr6) static bool notify_debug(struct pt_regs *regs, unsigned long *dr6)
{ {
struct task_struct *tsk = current; /*
* Notifiers will clear bits in @dr6 to indicate the event has been
/* Store the virtualized DR6 value */ * consumed - hw_breakpoint_handler(), single_stop_cont().
tsk->thread.debugreg6 = *dr6; *
* Notifiers will set bits in @virtual_dr6 to indicate the desire
* for signals - ptrace_triggered(), kgdb_hw_overflow_handler().
*/
if (notify_die(DIE_DEBUG, "debug", regs, (long)dr6, 0, SIGTRAP) == NOTIFY_STOP) if (notify_die(DIE_DEBUG, "debug", regs, (long)dr6, 0, SIGTRAP) == NOTIFY_STOP)
return true; return true;
/* Reload the DR6 value, the notifier might have changed it */
*dr6 = tsk->thread.debugreg6;
return false; return false;
} }
...@@ -853,7 +858,7 @@ static __always_inline void exc_debug_kernel(struct pt_regs *regs, ...@@ -853,7 +858,7 @@ static __always_inline void exc_debug_kernel(struct pt_regs *regs,
* A known way to trigger this is through QEMU's GDB stub, * A known way to trigger this is through QEMU's GDB stub,
* which leaks #DB into the guest and causes IST recursion. * which leaks #DB into the guest and causes IST recursion.
*/ */
if (WARN_ON_ONCE(current->thread.debugreg6 & DR_STEP)) if (WARN_ON_ONCE(dr6 & DR_STEP))
regs->flags &= ~X86_EFLAGS_TF; regs->flags &= ~X86_EFLAGS_TF;
out: out:
instrumentation_end(); instrumentation_end();
...@@ -903,6 +908,8 @@ static __always_inline void exc_debug_user(struct pt_regs *regs, ...@@ -903,6 +908,8 @@ static __always_inline void exc_debug_user(struct pt_regs *regs,
goto out_irq; goto out_irq;
} }
/* Add the virtual_dr6 bits for signals. */
dr6 |= current->thread.virtual_dr6;
if (dr6 & (DR_STEP | DR_TRAP_BITS) || icebp) if (dr6 & (DR_STEP | DR_TRAP_BITS) || icebp)
send_sigtrap(regs, 0, get_si_code(dr6)); send_sigtrap(regs, 0, get_si_code(dr6));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment