diff --git a/src/aarch64/trap.rs b/src/aarch64/trap.rs index 35c1b0c..bd98cf2 100644 --- a/src/aarch64/trap.rs +++ b/src/aarch64/trap.rs @@ -8,9 +8,9 @@ use crate::trap::PageFaultFlags; #[derive(Debug)] pub(super) enum TrapKind { Synchronous = 0, - Irq = 1, - Fiq = 2, - SError = 3, + Irq = 1, + Fiq = 2, + SError = 3, } #[repr(u8)] @@ -70,6 +70,13 @@ fn esr_value() -> u64 { } } +fn handle_breakpoint(tf: &mut TrapFrame) { + if handle_trap!(BREAK_HANDLER, tf) { + return; + } + tf.elr += 4; +} + fn handle_page_fault(tf: &mut TrapFrame, access_flags: PageFaultFlags) { let vaddr = va!(fault_addr()); if handle_trap!(PAGE_FAULT, vaddr, access_flags) { @@ -162,12 +169,12 @@ fn aarch64_trap_handler(tf: &mut TrapFrame, kind: TrapKind, source: TrapSource) #[cfg(not(feature = "arm-el2"))] Some(ESR_EL1::EC::Value::Brk64) => { debug!("BRK #{:#x} @ {:#x} ", iss, tf.elr); - tf.elr += 4; + handle_breakpoint(tf); } #[cfg(feature = "arm-el2")] Some(ESR_EL2::EC::Value::Brk64) => { debug!("BRK #{:#x} @ {:#x} ", iss, tf.elr); - tf.elr += 4; + handle_breakpoint(tf); } e => { let vaddr = va!(fault_addr()); diff --git a/src/aarch64/uspace.rs b/src/aarch64/uspace.rs index 12072ac..34b4358 100644 --- a/src/aarch64/uspace.rs +++ b/src/aarch64/uspace.rs @@ -2,13 +2,13 @@ use core::ops::{Deref, DerefMut}; -use aarch64_cpu::registers::{ESR_EL1, FAR_EL1, Readable}; +use aarch64_cpu::registers::{Readable, ESR_EL1, FAR_EL1}; use memory_addr::VirtAddr; use tock_registers::LocalRegisterCopy; -use super::trap::{TrapKind, is_valid_page_fault}; +use super::trap::{is_valid_page_fault, TrapKind}; pub use crate::uspace_common::{ExceptionKind, ReturnReason}; -use crate::{TrapFrame, trap::PageFaultFlags}; +use crate::{trap::PageFaultFlags, TrapFrame}; /// Context to enter user space. #[repr(C, align(16))] @@ -149,7 +149,9 @@ impl ExceptionInfo { /// Returns a generalized kind of this exception. pub fn kind(&self) -> ExceptionKind { match self.esr.read_as_enum(ESR_EL1::EC) { - Some(ESR_EL1::EC::Value::BreakpointLowerEL) => ExceptionKind::Breakpoint, + Some(ESR_EL1::EC::Value::Brk64 | ESR_EL1::EC::Value::Bkpt32) => { + ExceptionKind::Breakpoint + } Some(ESR_EL1::EC::Value::IllegalExecutionState) => ExceptionKind::IllegalInstruction, Some(ESR_EL1::EC::Value::PCAlignmentFault) | Some(ESR_EL1::EC::Value::SPAlignmentFault) => ExceptionKind::Misaligned, diff --git a/src/loongarch64/trap.rs b/src/loongarch64/trap.rs index a979e6b..86d34b7 100644 --- a/src/loongarch64/trap.rs +++ b/src/loongarch64/trap.rs @@ -12,9 +12,12 @@ core::arch::global_asm!( trapframe_size = const (core::mem::size_of::()), ); -fn handle_breakpoint(era: &mut usize) { - debug!("Exception(Breakpoint) @ {era:#x} "); - *era += 4; +fn handle_breakpoint(tf: &mut TrapFrame) { + debug!("Exception(Breakpoint) @ {:#x} ", tf.era); + if handle_trap!(BREAK_HANDLER, tf) { + return; + } + tf.era += 4; } fn handle_page_fault(tf: &mut TrapFrame, access_flags: PageFaultFlags) { @@ -53,7 +56,7 @@ fn loongarch64_trap_handler(tf: &mut TrapFrame) { | Trap::Exception(Exception::PageNonExecutableFault) => { handle_page_fault(tf, PageFaultFlags::EXECUTE); } - Trap::Exception(Exception::Breakpoint) => handle_breakpoint(&mut tf.era), + Trap::Exception(Exception::Breakpoint) => handle_breakpoint(tf), Trap::Exception(Exception::AddressNotAligned) => unsafe { tf.emulate_unaligned().unwrap(); }, diff --git a/src/loongarch64/uspace.rs b/src/loongarch64/uspace.rs index d8ff0c0..87903c9 100644 --- a/src/loongarch64/uspace.rs +++ b/src/loongarch64/uspace.rs @@ -9,7 +9,7 @@ use loongArch64::register::{ use memory_addr::VirtAddr; pub use crate::uspace_common::{ExceptionKind, ReturnReason}; -use crate::{TrapFrame, trap::PageFaultFlags}; +use crate::{trap::PageFaultFlags, TrapFrame}; /// Context to enter user space. #[derive(Debug, Clone, Copy)] diff --git a/src/riscv/trap.rs b/src/riscv/trap.rs index 5d27fc8..ca5a59a 100644 --- a/src/riscv/trap.rs +++ b/src/riscv/trap.rs @@ -2,8 +2,8 @@ use riscv::register::sstatus; use riscv::{ interrupt::{ - Trap, supervisor::{Exception as E, Interrupt as I}, + Trap, }, register::{scause, stval}, }; @@ -17,9 +17,12 @@ core::arch::global_asm!( trapframe_size = const core::mem::size_of::(), ); -fn handle_breakpoint(sepc: &mut usize) { - debug!("Exception(Breakpoint) @ {sepc:#x} "); - *sepc += 2 +fn handle_breakpoint(tf: &mut TrapFrame) { + debug!("Exception(Breakpoint) @ {:#x} ", tf.sepc); + if handle_trap!(BREAK_HANDLER, tf) { + return; + } + tf.sepc += 2; } fn handle_page_fault(tf: &mut TrapFrame, access_flags: PageFaultFlags) { @@ -51,7 +54,7 @@ fn riscv_trap_handler(tf: &mut TrapFrame) { Trap::Exception(E::InstructionPageFault) => { handle_page_fault(tf, PageFaultFlags::EXECUTE) } - Trap::Exception(E::Breakpoint) => handle_breakpoint(&mut tf.sepc), + Trap::Exception(E::Breakpoint) => handle_breakpoint(tf), Trap::Interrupt(_) => { handle_trap!(IRQ, scause.bits()); } diff --git a/src/riscv/uspace.rs b/src/riscv/uspace.rs index 3451626..914c406 100644 --- a/src/riscv/uspace.rs +++ b/src/riscv/uspace.rs @@ -7,14 +7,14 @@ use memory_addr::VirtAddr; use riscv::register::sstatus::FS; use riscv::{ interrupt::{ - Trap, supervisor::{Exception as E, Interrupt as I}, + Trap, }, register::{scause, sstatus::Sstatus, stval}, }; pub use crate::uspace_common::{ExceptionKind, ReturnReason}; -use crate::{GeneralRegisters, TrapFrame, trap::PageFaultFlags}; +use crate::{trap::PageFaultFlags, GeneralRegisters, TrapFrame}; /// Context to enter user space. #[derive(Debug, Clone, Copy)] diff --git a/src/trap.rs b/src/trap.rs index 1a07101..8fdd374 100644 --- a/src/trap.rs +++ b/src/trap.rs @@ -16,6 +16,49 @@ pub static IRQ: [fn(usize) -> bool]; #[def_trap_handler] pub static PAGE_FAULT: [fn(VirtAddr, PageFaultFlags) -> bool]; +/// A slice of breakpoint handler functions. +/// +/// Each handler is invoked with a mutable reference to the trapped [`TrapFrame`] +/// and must return a boolean indicating whether it has fully handled the trap: +/// +/// - `true` means the breakpoint has been handled and control should resume +/// according to the state encoded in the trap frame. +/// - `false` means the breakpoint was not handled and default processing +/// (such as falling back to another mechanism or terminating) should occur. +/// +/// When returning `true`, the handler is responsible for updating the saved +/// program counter (or equivalent PC field) in the trap frame as required by +/// the target architecture. In particular, the handler must ensure that, +/// upon resuming from the trap, execution does not immediately re-trigger the +/// same breakpoint instruction or condition, which could otherwise lead to an +/// infinite trap loop. The exact way to advance or modify the PC is +/// architecture-specific and depends on how [`TrapFrame`] encodes the saved +/// context. +#[def_trap_handler] +pub static BREAK_HANDLER: [fn(&mut TrapFrame) -> bool]; + +/// A slice of debug handler functions. +/// +/// On `x86_64`, these handlers are invoked for debug-related traps (for +/// example, hardware breakpoints, single-step traps, or other debug +/// exceptions). The handler receives a mutable reference to the trapped +/// [`TrapFrame`] and returns a boolean with the following meaning: +/// +/// - `true` means the debug trap has been fully handled and execution should +/// resume from the state stored in the trap frame. +/// - `false` means the debug trap was not handled and default/secondary +/// processing should take place. +/// +/// As with [`BREAK_HANDLER`], when returning `true`, the handler must adjust +/// the saved program counter (or equivalent) in the trap frame if required by +/// the architecture so that resuming execution does not immediately cause the +/// same debug condition to fire again. Callers must take the architecture- +/// specific PC semantics into account when deciding how to advance or modify +/// the PC. +#[cfg(target_arch = "x86_64")] +#[def_trap_handler] +pub static DEBUG_HANDLER: [fn(&mut TrapFrame) -> bool]; + #[allow(unused_macros)] macro_rules! handle_trap { ($trap:ident, $($args:tt)*) => {{ diff --git a/src/uspace_common.rs b/src/uspace_common.rs index b8c22cc..0d68a7b 100644 --- a/src/uspace_common.rs +++ b/src/uspace_common.rs @@ -1,6 +1,6 @@ use memory_addr::VirtAddr; -use crate::{TrapFrame, trap::PageFaultFlags, uspace::ExceptionInfo}; +use crate::{trap::PageFaultFlags, uspace::ExceptionInfo, TrapFrame}; /// A reason as to why the control of the CPU is returned from /// the user space to the kernel. @@ -21,6 +21,9 @@ pub enum ReturnReason { /// A generalized kind for [`ExceptionInfo`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ExceptionKind { + #[cfg(target_arch = "x86_64")] + /// A debug exception. + Debug, /// A breakpoint exception. Breakpoint, /// An illegal instruction exception. @@ -50,7 +53,7 @@ impl ExceptionTableEntry { #[cfg(target_arch = "aarch64")] { let base = (&self.from as *const i32) as isize; - return (base + self.from as isize) as usize; + (base + self.from as isize) as usize } #[cfg(not(target_arch = "aarch64"))] @@ -64,7 +67,7 @@ impl ExceptionTableEntry { #[cfg(target_arch = "aarch64")] { let base = (&self.to as *const i32) as isize; - return (base + self.to as isize) as usize; + (base + self.to as isize) as usize } #[cfg(not(target_arch = "aarch64"))] diff --git a/src/x86_64/gdt.rs b/src/x86_64/gdt.rs index cb86bd3..057abdb 100644 --- a/src/x86_64/gdt.rs +++ b/src/x86_64/gdt.rs @@ -1,11 +1,11 @@ use x86_64::{ - PrivilegeLevel, instructions::tables::load_tss, - registers::segmentation::{CS, Segment, SegmentSelector}, + registers::segmentation::{Segment, SegmentSelector, CS}, structures::{ gdt::{Descriptor, GlobalDescriptorTable}, tss::TaskStateSegment, }, + PrivilegeLevel, }; #[percpu::def_percpu] diff --git a/src/x86_64/trap.rs b/src/x86_64/trap.rs index 04342df..7c160fd 100644 --- a/src/x86_64/trap.rs +++ b/src/x86_64/trap.rs @@ -1,7 +1,9 @@ +use core::panic; + use x86::{controlregs::cr2, irq::*}; use x86_64::structures::idt::PageFaultErrorCode; -use super::{TrapFrame, gdt}; +use super::{gdt, TrapFrame}; use crate::trap::PageFaultFlags; core::arch::global_asm!( @@ -38,11 +40,31 @@ fn handle_page_fault(tf: &mut TrapFrame) { ); } +fn handle_breakpoint(tf: &mut TrapFrame) { + debug!("#BP @ {:#x} ", tf.rip); + if handle_trap!(BREAK_HANDLER, tf) {} +} + +fn handle_debug(tf: &mut TrapFrame) { + debug!("#DB @ {:#x} ", tf.rip); + if handle_trap!(DEBUG_HANDLER, tf) { + return; + } + panic!( + "Unhandled #DB @ {:#x}, error_code={:#x}:\n{:#x?}\n{}", + tf.rip, + tf.error_code, + tf, + tf.backtrace() + ); +} + #[unsafe(no_mangle)] fn x86_trap_handler(tf: &mut TrapFrame) { match tf.vector as u8 { PAGE_FAULT_VECTOR => handle_page_fault(tf), - BREAKPOINT_VECTOR => debug!("#BP @ {:#x} ", tf.rip), + BREAKPOINT_VECTOR => handle_breakpoint(tf), + DEBUG_VECTOR => handle_debug(tf), GENERAL_PROTECTION_FAULT_VECTOR => { panic!( "#GP @ {:#x}, error_code={:#x}:\n{:#x?}\n{}", diff --git a/src/x86_64/uspace.rs b/src/x86_64/uspace.rs index 226c6ae..dce0888 100644 --- a/src/x86_64/uspace.rs +++ b/src/x86_64/uspace.rs @@ -13,10 +13,10 @@ use x86_64::{ }; use super::{ - TrapFrame, asm::{read_thread_pointer, write_thread_pointer}, gdt, - trap::{IRQ_VECTOR_END, IRQ_VECTOR_START, LEGACY_SYSCALL_VECTOR, err_code_to_flags}, + trap::{err_code_to_flags, IRQ_VECTOR_END, IRQ_VECTOR_START, LEGACY_SYSCALL_VECTOR}, + TrapFrame, }; pub use crate::uspace_common::{ExceptionKind, ReturnReason}; @@ -140,6 +140,7 @@ impl ExceptionInfo { /// Returns a generalized kind of this exception. pub fn kind(&self) -> ExceptionKind { match ExceptionVector::try_from(self.vector) { + Ok(ExceptionVector::Debug) => ExceptionKind::Debug, Ok(ExceptionVector::Breakpoint) => ExceptionKind::Breakpoint, Ok(ExceptionVector::InvalidOpcode) => ExceptionKind::IllegalInstruction, _ => ExceptionKind::Other,