Skip to content

Commit

Permalink
Break into the debugger (if attached) on panics under Windows
Browse files Browse the repository at this point in the history
The developer experience for panics is to provide the backtrace and
exit the program. When running under debugger, that might be improved
by breaking into the debugger once the code panics thus enabling
the developer to examine the program state at the exact time when
the code panicked.

Let the developer catch the panic in the debugger if it is attached,
under Windows. If the debugger is not attached, nothing changes.
Providing this feature inside the standard library facilitates better
debugging experience.
  • Loading branch information
kromych committed Aug 12, 2024
1 parent e08b80c commit b4235b8
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 1 deletion.
16 changes: 15 additions & 1 deletion library/std/src/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use crate::mem::{self, ManuallyDrop};
use crate::panic::{BacktraceStyle, PanicHookInfo};
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sync::{PoisonError, RwLock};
use crate::sys::backtrace;
use crate::sys::stdio::panic_output;
use crate::sys::{backtrace, dbg};
use crate::{fmt, intrinsics, process, thread};

// Binary interface to the panic runtime that the standard library depends on.
Expand Down Expand Up @@ -855,13 +855,27 @@ pub fn rust_panic_without_hook(payload: Box<dyn Any + Send>) -> ! {
#[cfg_attr(not(test), rustc_std_internal_symbol)]
#[cfg(not(feature = "panic_immediate_abort"))]
fn rust_panic(msg: &mut dyn PanicPayload) -> ! {
// Break into the debugger if it is attached.
//
// This function isn't used anywhere else, and
// using inside `#[panic_handler]` doesn't seem
// to count, so a warning is issued.
dbg::dbg_breakpoint();

let code = unsafe { __rust_start_panic(msg) };
rtabort!("failed to initiate panic, error {code}")
}

#[cfg_attr(not(test), rustc_std_internal_symbol)]
#[cfg(feature = "panic_immediate_abort")]
fn rust_panic(_: &mut dyn PanicPayload) -> ! {
// Break into the debugger if it is attached.
//
// This function isn't used anywhere else, and
// using inside `#[panic_handler]` doesn't seem
// to count, so a warning is issued.
dbg::dbg_breakpoint();

unsafe {
crate::intrinsics::abort();
}
Expand Down
129 changes: 129 additions & 0 deletions library/std/src/sys/dbg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//! Debugging aids.
//!
//! On Windows, the approach is based on the Structured Exception Handling:
//! https://learn.microsoft.com/en-us/windows/win32/debug/structured-exception-handling
//! and is conceptually equivalent to
//!
//! ```c
//! __try {
//! __debugbreak();
//! } __except (1) {
//! /* Nothing */
//! }
//! ```
//!
//! The implementation is available for the `x86_64` and the `aarch64`
//! targets under Windows.

#[cfg(target_os = "windows")]
mod windows {
#[cfg(not(target_arch = "x86"))]
extern "C" {
/// Breakpoint that is passed to the debugger as the first chance exception
/// if the debugger is attached, and is skipped over otherwise.
pub(super) fn __dbg_breakpoint();
}

#[cfg(target_arch = "x86_64")]
core::arch::global_asm!(
r#"
.pushsection .text
.globl __dbg_breakpoint_flt
.p2align 4
__dbg_breakpoint_flt:
mov eax, 1 # EXCEPTION_EXECUTE_HANDLER
ret
.globl __dbg_breakpoint
.p2align 4
.def __dbg_breakpoint; .scl 2; .type 32; .endef
.seh_proc __dbg_breakpoint
__dbg_breakpoint:
sub rsp, 64
.seh_stackalloc 64
.seh_endprologue
1:
int3
2:
3:
add rsp, 64
ret
4:
jmp 3b
.seh_handler __C_specific_handler, @except
.seh_handlerdata
.long 1 # One handler entry
.long (1b)@IMGREL # Start address of __try block
.long (2b)@IMGREL # End address of __try block
.long (__dbg_breakpoint_flt)@IMGREL # Exception filter
.long (4b)@IMGREL # Exception handler
.text
.seh_endproc
.popsection
"#
);

#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
r#"
.pushsection .text
.globl __dbg_breakpoint_flt
.p2align 2
__dbg_breakpoint_flt:
mov w0, 1 // EXCEPTION_EXECUTE_HANDLER
ret
.globl __dbg_breakpoint
.p2align 2
.def __dbg_breakpoint; .scl 2; .type 32; .endef
.seh_proc __dbg_breakpoint
__dbg_breakpoint:
str lr, [sp, #-16]!
.seh_save_reg_x lr, 16
.seh_endprologue
1:
brk #0xf000
2:
3:
.seh_startepilogue
ldr lr, [sp], #16
.seh_save_reg_x lr, 16
.seh_endepilogue
ret
4:
b 3b
.seh_handler __C_specific_handler, @except
.seh_handlerdata
.long 1 // One handler entry
.long (1b)@IMGREL // Start address of __try block
.long (2b)@IMGREL // End address of __try block
.long (__dbg_breakpoint_flt)@IMGREL // Exception filter
.long (4b)@IMGREL // Exception handler
.text
.seh_endproc
.popsection
"#
);

#[cfg(target_arch = "x86")]
fn __dbg_breakpoint() {
// Not implemented
}
}

/// Breakpoint that is passed to the debugger as the first chance exception
/// if the debugger is attached, and is skipped over otherwise.
#[cfg(target_os = "windows")]
pub fn dbg_breakpoint() {
// SAFETY: the call does not access any state shared between threads.
unsafe {
windows::__dbg_breakpoint();
}
}

#[cfg(not(target_os = "windows"))]
pub fn dbg_breakpoint() {
// Not implemented
}
1 change: 1 addition & 0 deletions library/std/src/sys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod personality;
pub mod anonymous_pipe;
pub mod backtrace;
pub mod cmath;
pub mod dbg;
pub mod exit_guard;
pub mod os_str;
pub mod path;
Expand Down

0 comments on commit b4235b8

Please sign in to comment.