Skip to content

Commit

Permalink
x86/fred: Add a debug fault entry stub for FRED
Browse files Browse the repository at this point in the history
When occurred on different ring level, i.e., from user or kernel context,
stack, while kernel #DB on a dedicated stack. This is exactly how FRED
event delivery invokes an exception handler: ring 3 event on level 0
stack, i.e., current task stack; ring 0 event on the #DB dedicated stack
specified in the IA32_FRED_STKLVLS MSR. So unlike IDT, the FRED debug
exception entry stub doesn't do stack switch.

On a FRED system, the debug trap status information (DR6) is passed on
the stack, to avoid the problem of transient state. Furthermore, FRED
transitions avoid a lot of ugly corner cases the handling of which can,
and should be, skipped.

The FRED debug trap status information saved on the stack differs from
DR6 in both stickiness and polarity; it is exactly in the format which
debug_read_clear_dr6() returns for the IDT entry points.

Signed-off-by: H. Peter Anvin (Intel) <hpa@zytor.com>
Signed-off-by: Xin Li <xin3.li@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Tested-by: Shan Kang <shan.kang@intel.com>
Link: https://lore.kernel.org/r/20231205105030.8698-24-xin3.li@intel.com
  • Loading branch information
H. Peter Anvin (Intel) authored and Borislav Petkov (AMD) committed Jan 31, 2024
1 parent 90f3572 commit 99fcc96
Showing 1 changed file with 38 additions and 5 deletions.
43 changes: 38 additions & 5 deletions arch/x86/kernel/traps.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <asm/ftrace.h>
#include <asm/traps.h>
#include <asm/desc.h>
#include <asm/fred.h>
#include <asm/fpu/api.h>
#include <asm/cpu.h>
#include <asm/cpu_entry_area.h>
Expand Down Expand Up @@ -935,8 +936,7 @@ static bool notify_debug(struct pt_regs *regs, unsigned long *dr6)
return false;
}

static __always_inline void exc_debug_kernel(struct pt_regs *regs,
unsigned long dr6)
static noinstr void exc_debug_kernel(struct pt_regs *regs, unsigned long dr6)
{
/*
* Disable breakpoints during exception handling; recursive exceptions
Expand All @@ -948,6 +948,11 @@ static __always_inline void exc_debug_kernel(struct pt_regs *regs,
*
* Entry text is excluded for HW_BP_X and cpu_entry_area, which
* includes the entry stack is excluded for everything.
*
* For FRED, nested #DB should just work fine. But when a watchpoint or
* breakpoint is set in the code path which is executed by #DB handler,
* it results in an endless recursion and stack overflow. Thus we stay
* with the IDT approach, i.e., save DR7 and disable #DB.
*/
unsigned long dr7 = local_db_save();
irqentry_state_t irq_state = irqentry_nmi_enter(regs);
Expand Down Expand Up @@ -977,7 +982,8 @@ static __always_inline void exc_debug_kernel(struct pt_regs *regs,
* Catch SYSENTER with TF set and clear DR_STEP. If this hit a
* watchpoint at the same time then that will still be handled.
*/
if ((dr6 & DR_STEP) && is_sysenter_singlestep(regs))
if (!cpu_feature_enabled(X86_FEATURE_FRED) &&
(dr6 & DR_STEP) && is_sysenter_singlestep(regs))
dr6 &= ~DR_STEP;

/*
Expand Down Expand Up @@ -1009,8 +1015,7 @@ static __always_inline void exc_debug_kernel(struct pt_regs *regs,
local_db_restore(dr7);
}

static __always_inline void exc_debug_user(struct pt_regs *regs,
unsigned long dr6)
static noinstr void exc_debug_user(struct pt_regs *regs, unsigned long dr6)
{
bool icebp;

Expand Down Expand Up @@ -1094,6 +1099,34 @@ DEFINE_IDTENTRY_DEBUG_USER(exc_debug)
{
exc_debug_user(regs, debug_read_clear_dr6());
}

#ifdef CONFIG_X86_FRED
/*
* When occurred on different ring level, i.e., from user or kernel
* context, #DB needs to be handled on different stack: User #DB on
* current task stack, while kernel #DB on a dedicated stack.
*
* This is exactly how FRED event delivery invokes an exception
* handler: ring 3 event on level 0 stack, i.e., current task stack;
* ring 0 event on the #DB dedicated stack specified in the
* IA32_FRED_STKLVLS MSR. So unlike IDT, the FRED debug exception
* entry stub doesn't do stack switch.
*/
DEFINE_FREDENTRY_DEBUG(exc_debug)
{
/*
* FRED #DB stores DR6 on the stack in the format which
* debug_read_clear_dr6() returns for the IDT entry points.
*/
unsigned long dr6 = fred_event_data(regs);

if (user_mode(regs))
exc_debug_user(regs, dr6);
else
exc_debug_kernel(regs, dr6);
}
#endif /* CONFIG_X86_FRED */

#else
/* 32 bit does not have separate entry points. */
DEFINE_IDTENTRY_RAW(exc_debug)
Expand Down

0 comments on commit 99fcc96

Please sign in to comment.