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

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
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -517,7 +517,7 @@ struct thread_struct {
	/* Save middle states of ptrace breakpoints */
	struct perf_event	*ptrace_bps[HBP_NUM];
	/* 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 */
	unsigned long           ptrace_dr7;
	/* Fault info: */
+3 −9
Original line number Diff line number Diff line
@@ -454,7 +454,7 @@ void flush_ptrace_hw_breakpoint(struct task_struct *tsk)
		t->ptrace_bps[i] = NULL;
	}

	t->debugreg6 = 0;
	t->virtual_dr6 = 0;
	t->ptrace_dr7 = 0;
}

@@ -489,8 +489,8 @@ static int hw_breakpoint_handler(struct die_args *args)
{
	int i, rc = NOTIFY_STOP;
	struct perf_event *bp;
	unsigned long dr6;
	unsigned long *dr6_p;
	unsigned long dr6;

	/* The DR6 value is pointed by args->err */
	dr6_p = (unsigned long *)ERR_PTR(args->err);
@@ -504,12 +504,6 @@ static int hw_breakpoint_handler(struct die_args *args)
	if ((dr6 & DR_TRAP_BITS) == 0)
		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 */
	for (i = 0; i < HBP_NUM; ++i) {
		if (likely(!(dr6 & (DR_TRAP0 << i))))
@@ -554,7 +548,7 @@ static int hw_breakpoint_handler(struct die_args *args)
	 * breakpoints (to generate signals) and b) when the system has
	 * 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)))
		rc = NOTIFY_DONE;

+3 −2
Original line number Diff line number Diff line
@@ -629,9 +629,10 @@ static void kgdb_hw_overflow_handler(struct perf_event *event,
	struct task_struct *tsk = current;
	int i;

	for (i = 0; i < 4; i++)
	for (i = 0; i < 4; i++) {
		if (breakinfo[i].enabled)
			tsk->thread.debugreg6 |= (DR_TRAP0 << i);
			tsk->thread.virtual_dr6 |= (DR_TRAP0 << i);
	}
}

void kgdb_arch_late(void)
+3 −3
Original line number Diff line number Diff line
@@ -465,7 +465,7 @@ static void ptrace_triggered(struct perf_event *bp,
			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)
		if (bp)
			val = bp->hw.info.address;
	} 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) {
		val = thread->ptrace_dr7;
	}
@@ -657,7 +657,7 @@ static int ptrace_set_debugreg(struct task_struct *tsk, int n,
	if (n < HBP_NUM) {
		rc = ptrace_set_breakpoint_addr(tsk, n, val);
	} 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;
	} else if (n == 7) {
		rc = ptrace_write_dr7(tsk, val);
+16 −9
Original line number Diff line number Diff line
@@ -748,6 +748,12 @@ static __always_inline unsigned long debug_read_clear_dr6(void)
	set_debugreg(DR6_RESERVED, 6);
	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
	 * generates a debug exception."  Clear TIF_BLOCKSTEP to keep
@@ -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)
{
	struct task_struct *tsk = current;

	/* Store the virtualized DR6 value */
	tsk->thread.debugreg6 = *dr6;

	/*
	 * Notifiers will clear bits in @dr6 to indicate the event has been
	 * consumed - hw_breakpoint_handler(), single_stop_cont().
	 *
	 * 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)
		return true;

	/* Reload the DR6 value, the notifier might have changed it */
	*dr6 = tsk->thread.debugreg6;

	return false;
}

@@ -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,
	 * 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;
out:
	instrumentation_end();
@@ -903,6 +908,8 @@ static __always_inline void exc_debug_user(struct pt_regs *regs,
		goto out_irq;
	}

	/* Add the virtual_dr6 bits for signals. */
	dr6 |= current->thread.virtual_dr6;
	if (dr6 & (DR_STEP | DR_TRAP_BITS) || icebp)
		send_sigtrap(regs, 0, get_si_code(dr6));