Skip to content

Commit 9ad22e1

Browse files
Peter Zijlstrasuryasaimadhu
authored andcommitted
x86/debug: Fix DR6 handling
Tom reported that one of the GDB test-cases failed, and Boris bisected it to commit: d53d9bc ("x86/debug: Change thread.debugreg6 to thread.virtual_dr6") The debugging session led us to commit: 6c0aca2 ("x86: Ignore trap bits on single step exceptions") It turns out that TF and data breakpoints are both traps and will be merged, while instruction breakpoints are faults and will not be merged. This means 6c0aca2 is wrong, only TF and instruction breakpoints need to be excluded while TF and data breakpoints can be merged. [ bp: Massage commit message. ] Fixes: d53d9bc ("x86/debug: Change thread.debugreg6 to thread.virtual_dr6") Fixes: 6c0aca2 ("x86: Ignore trap bits on single step exceptions") Reported-by: Tom de Vries <[email protected]> Signed-off-by: Peter Zijlstra (Intel) <[email protected]> Signed-off-by: Borislav Petkov <[email protected]> Cc: <[email protected]> Link: https://lkml.kernel.org/r/YBMAbQGACujjfz%[email protected] Link: https://lkml.kernel.org/r/[email protected]
1 parent 20bf2b3 commit 9ad22e1

File tree

1 file changed

+18
-21
lines changed

1 file changed

+18
-21
lines changed

arch/x86/kernel/hw_breakpoint.c

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -491,15 +491,12 @@ static int hw_breakpoint_handler(struct die_args *args)
491491
struct perf_event *bp;
492492
unsigned long *dr6_p;
493493
unsigned long dr6;
494+
bool bpx;
494495

495496
/* The DR6 value is pointed by args->err */
496497
dr6_p = (unsigned long *)ERR_PTR(args->err);
497498
dr6 = *dr6_p;
498499

499-
/* If it's a single step, TRAP bits are random */
500-
if (dr6 & DR_STEP)
501-
return NOTIFY_DONE;
502-
503500
/* Do an early return if no trap bits are set in DR6 */
504501
if ((dr6 & DR_TRAP_BITS) == 0)
505502
return NOTIFY_DONE;
@@ -509,40 +506,40 @@ static int hw_breakpoint_handler(struct die_args *args)
509506
if (likely(!(dr6 & (DR_TRAP0 << i))))
510507
continue;
511508

509+
bp = this_cpu_read(bp_per_reg[i]);
510+
if (!bp)
511+
continue;
512+
513+
bpx = bp->hw.info.type == X86_BREAKPOINT_EXECUTE;
514+
512515
/*
513-
* The counter may be concurrently released but that can only
514-
* occur from a call_rcu() path. We can then safely fetch
515-
* the breakpoint, use its callback, touch its counter
516-
* while we are in an rcu_read_lock() path.
516+
* TF and data breakpoints are traps and can be merged, however
517+
* instruction breakpoints are faults and will be raised
518+
* separately.
519+
*
520+
* However DR6 can indicate both TF and instruction
521+
* breakpoints. In that case take TF as that has precedence and
522+
* delay the instruction breakpoint for the next exception.
517523
*/
518-
rcu_read_lock();
524+
if (bpx && (dr6 & DR_STEP))
525+
continue;
519526

520-
bp = this_cpu_read(bp_per_reg[i]);
521527
/*
522528
* Reset the 'i'th TRAP bit in dr6 to denote completion of
523529
* exception handling
524530
*/
525531
(*dr6_p) &= ~(DR_TRAP0 << i);
526-
/*
527-
* bp can be NULL due to lazy debug register switching
528-
* or due to concurrent perf counter removing.
529-
*/
530-
if (!bp) {
531-
rcu_read_unlock();
532-
break;
533-
}
534532

535533
perf_bp_event(bp, args->regs);
536534

537535
/*
538536
* Set up resume flag to avoid breakpoint recursion when
539537
* returning back to origin.
540538
*/
541-
if (bp->hw.info.type == X86_BREAKPOINT_EXECUTE)
539+
if (bpx)
542540
args->regs->flags |= X86_EFLAGS_RF;
543-
544-
rcu_read_unlock();
545541
}
542+
546543
/*
547544
* Further processing in do_debug() is needed for a) user-space
548545
* breakpoints (to generate signals) and b) when the system has

0 commit comments

Comments
 (0)