Skip to main content

starry_kernel/
kprobe.rs

1//! Kernel probe (kprobe) subsystem for StarryOS.
2//!
3//! This module provides dynamic tracing support by allowing breakpoint
4//! insertion at kernel function entry/return points. It integrates the
5//! [`kprobe`] crate with StarryOS kernel infrastructure.
6//!
7//! # Architecture Support
8//!
9//! All four supported architectures are enabled: x86_64, riscv64, aarch64,
10//! and loongarch64. Each architecture provides TrapFrame↔PtRegs register
11//! conversion to bridge the kernel's trap frame format with the kprobe
12//! crate's portable `PtRegs` type.
13//!
14//! # Key Components
15//!
16//! - [`KernelKprobeOps`]: Platform-specific auxiliary operations for the kprobe crate
17//! - [`handle_breakpoint`]: Entry point for breakpoint exceptions (INT3/EBREAK/BRK)
18//! - [`handle_debug`]: Entry point for debug exceptions (x86_64 single-step only)
19
20use alloc::{
21    collections::BTreeMap,
22    sync::{Arc, Weak},
23    vec::Vec,
24};
25use core::{
26    fmt,
27    num::NonZeroI32,
28    sync::atomic::{AtomicI32, Ordering},
29};
30
31use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr, VirtAddrRange};
32use ax_runtime::hal::{
33    cpu::{KernelTrapFrame, UserRegisters},
34    paging::MappingFlags,
35};
36use kprobe::{
37    KprobeAuxiliaryOps, KretprobeBuilder, ProbeBuilder, ProbePointList,
38    register_kprobe as kprobe_crate_register_kprobe,
39    register_kretprobe as kprobe_crate_register_kretprobe, retprobe::RetprobeInstance,
40    unregister_kprobe as kprobe_crate_unregister_kprobe,
41    unregister_kretprobe as kprobe_crate_unregister_kretprobe,
42};
43
44use crate::{
45    StarryError, StarryResult,
46    sync::{IrqMutex, RawSpinNoIrq},
47    task::{AsThread, PidIdentity},
48};
49
50static NEXT_UPROBE_TARGET_ID: AtomicI32 = AtomicI32::new(1);
51static UPROBE_TARGETS: IrqMutex<BTreeMap<UprobeTargetId, Weak<PidIdentity>>> =
52    IrqMutex::new(BTreeMap::new());
53
54/// Opaque handle passed through `kprobe`; it is never interpreted as a Linux PID.
55#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
56#[repr(transparent)]
57struct UprobeTargetId(NonZeroI32);
58
59impl UprobeTargetId {
60    fn allocate() -> StarryResult<Self> {
61        let id = NEXT_UPROBE_TARGET_ID.fetch_add(1, Ordering::Relaxed);
62        (id > 0)
63            .then(|| NonZeroI32::new(id).map(Self))
64            .flatten()
65            .ok_or(StarryError::NoMemory)
66    }
67
68    const fn get(self) -> i32 {
69        self.0.get()
70    }
71}
72
73/// Keeps the exact uprobe target generation registered for auxiliary callbacks.
74pub(crate) struct UprobeTargetLease {
75    id: UprobeTargetId,
76    identity: Arc<PidIdentity>,
77}
78
79impl UprobeTargetLease {
80    pub(crate) fn register(identity: Arc<PidIdentity>) -> StarryResult<Self> {
81        let id = UprobeTargetId::allocate()?;
82        UPROBE_TARGETS.lock().insert(id, Arc::downgrade(&identity));
83        Ok(Self { id, identity })
84    }
85
86    pub(crate) const fn opaque_id(&self) -> i32 {
87        self.id.get()
88    }
89}
90
91impl fmt::Debug for UprobeTargetLease {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.debug_struct("UprobeTargetLease")
94            .field("id", &self.id)
95            .field("identity_id", &self.identity.id())
96            .finish()
97    }
98}
99
100impl Drop for UprobeTargetLease {
101    fn drop(&mut self) {
102        UPROBE_TARGETS.lock().remove(&self.id);
103    }
104}
105
106fn uprobe_target_task(opaque_id: i32) -> ax_task::AxTaskRef {
107    let id = NonZeroI32::new(opaque_id)
108        .map(UprobeTargetId)
109        .expect("uprobe target handle must be non-zero");
110    let identity = UPROBE_TARGETS
111        .lock()
112        .get(&id)
113        .and_then(Weak::upgrade)
114        .expect("uprobe target generation is no longer registered");
115    identity
116        .live_task()
117        .expect("uprobe target task exited while probe remained armed")
118}
119
120/// Raw mutex used as the `L` type parameter for the `kprobe` crate's
121/// `ProbeManager` / `Kprobe` / `Kretprobe` (the perf subsystem refers to the
122/// concrete probe types parameterized on it — see [`KernelKprobe`] /
123/// [`KernelKretprobe`]).
124///
125/// Backed by [`crate::sync::RawSpinNoIrq`], which disables kernel preemption and
126/// local IRQs across the critical section (IRQ-save semantics, the
127/// same as the rest of the kernel's spin locks). This matters because the lock
128/// is taken on trap / kprobe-callback paths: a plain atomic spin lock that left
129/// preemption and IRQs enabled could be re-entered on the same CPU and would
130/// then deadlock spinning on a lock it already holds.
131pub type KernelRawMutex = RawSpinNoIrq;
132
133#[derive(Debug)]
134pub struct KernelKprobeOps;
135
136impl KprobeAuxiliaryOps for KernelKprobeOps {
137    fn copy_memory(src: *const u8, dst: *mut u8, len: usize, user_pid: Option<i32>) {
138        if let Some(pid) = user_pid {
139            // Uprobe arm/disarm reads the target process' original text bytes
140            // while the per-process kprobe manager spin-lock is held (IRQs
141            // disabled), so the faultable user-access path (`vm_read_slice`,
142            // which asserts IRQs enabled) cannot be used. Read through the
143            // *kernel* direct-map alias of the target page's physical frame
144            // instead — the same aliasing `set_writeable_for_address` uses to
145            // write. The text page is already resident (the loader executes the
146            // probed function before arming).
147            let task = uprobe_target_task(pid);
148            let aspace = task.as_thread().proc_data.aspace();
149            let mm = aspace.lock();
150            let pt = mm.page_table();
151            let mut copied = 0;
152            while copied < len {
153                let vaddr = VirtAddr::from(src as usize + copied);
154                let Ok((paddr, ..)) = pt.query(vaddr) else {
155                    warn!(
156                        "kprobe copy_memory: user addr {:#x} not mapped",
157                        vaddr.as_usize()
158                    );
159                    return;
160                };
161                let page_off = vaddr.as_usize() & (PAGE_SIZE_4K - 1);
162                let chunk = core::cmp::min(len - copied, PAGE_SIZE_4K - page_off);
163                let kvaddr = ax_runtime::hal::mem::phys_to_virt(paddr);
164                unsafe {
165                    core::ptr::copy_nonoverlapping(kvaddr.as_ptr(), dst.add(copied), chunk);
166                }
167                copied += chunk;
168            }
169        } else {
170            unsafe {
171                core::ptr::copy_nonoverlapping(src, dst, len);
172            }
173        }
174    }
175
176    fn set_writeable_for_address<F: FnOnce(*mut u8)>(
177        address: usize,
178        len: usize,
179        user_pid: Option<i32>,
180        action: F,
181    ) {
182        if let Some(pid) = user_pid {
183            // User-space probe (uprobe): patch the target process' text by
184            // writing through the *kernel* direct-map alias of the page's
185            // physical frame. The user PTE keeps its read-only/exec flags
186            // untouched (no per-fire `protect` dance needed — uprobe single-step
187            // is out-of-line, see `alloc_user_exec_memory`). This runs at
188            // arm/disarm time (syscall context), so taking the sleeping aspace
189            // lock is fine. The instruction patch (≤ a few bytes) stays within
190            // the resolved page.
191            let task = uprobe_target_task(pid);
192            let aspace = task.as_thread().proc_data.aspace();
193            let mm = aspace.lock();
194            let vaddr = VirtAddr::from(address);
195            let (paddr, ..) = mm
196                .page_table()
197                .query(vaddr)
198                .expect("uprobe: target address not mapped");
199            let kvaddr = ax_runtime::hal::mem::phys_to_virt(paddr);
200            action(kvaddr.as_mut_ptr());
201            ax_runtime::hal::cache::sync_kernel_text(vaddr.align_down_4k(), PAGE_SIZE_4K);
202            return;
203        }
204        let addr = VirtAddr::from(address);
205        crate::mm::patch_kernel_text(addr, len, action)
206            .expect("kprobe: set_writeable: patch kernel text failed");
207    }
208
209    fn alloc_kernel_exec_memory() -> *mut u8 {
210        let mut guard = ax_mm::kernel_aspace().lock();
211        let range = VirtAddrRange::new(guard.base(), guard.end());
212        let vaddr = guard
213            .find_free_area(guard.base(), PAGE_SIZE_4K, range)
214            .expect("kprobe: no free virtual address for exec memory");
215        guard
216            .map_alloc(
217                vaddr,
218                PAGE_SIZE_4K,
219                MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE,
220                true,
221            )
222            .expect("kprobe: map_alloc for exec memory failed");
223        vaddr.as_mut_ptr()
224    }
225
226    fn free_kernel_exec_memory(ptr: *mut u8) {
227        let vaddr = VirtAddr::from(ptr as usize);
228        let mut guard = ax_mm::kernel_aspace().lock();
229        guard
230            .unmap(vaddr, PAGE_SIZE_4K)
231            .expect("kprobe: unmap exec memory failed");
232    }
233
234    fn alloc_user_exec_memory<F: FnOnce(*mut u8)>(pid: Option<i32>, action: F) -> *mut u8 {
235        // Allocate one anonymous, user-executable page in the target process for
236        // out-of-line single-stepping (the displaced original instruction is
237        // copied here so the planted `int3` can stay armed). `action` writes
238        // that instruction through the kernel alias of the freshly-mapped frame.
239        let pid = pid.expect("uprobe: alloc_user_exec_memory needs a pid");
240        let task = uprobe_target_task(pid);
241        let aspace = task.as_thread().proc_data.aspace();
242        let mut mm = aspace.lock();
243        let range = VirtAddrRange::new(mm.base(), mm.end());
244        let vaddr = mm
245            .find_free_area(mm.base(), PAGE_SIZE_4K, range, PAGE_SIZE_4K)
246            .expect("uprobe: no free user va for exec memory");
247        let backend = crate::mm::Backend::new_alloc(vaddr, PAGE_SIZE_4K, "uprobe-ols");
248        mm.map(
249            vaddr,
250            PAGE_SIZE_4K,
251            MappingFlags::READ | MappingFlags::EXECUTE | MappingFlags::USER,
252            true,
253            backend,
254        )
255        .expect("uprobe: map user exec memory failed");
256        let (paddr, ..) = mm
257            .page_table()
258            .query(vaddr)
259            .expect("uprobe: exec page not mapped after populate");
260        let kvaddr = ax_runtime::hal::mem::phys_to_virt(paddr);
261        action(kvaddr.as_mut_ptr());
262        ax_runtime::hal::cache::sync_kernel_text(vaddr, PAGE_SIZE_4K);
263        vaddr.as_mut_ptr()
264    }
265
266    fn free_user_exec_memory(pid: Option<i32>, ptr: *mut u8) {
267        let pid = pid.expect("uprobe: free_user_exec_memory needs a pid");
268        let task = uprobe_target_task(pid);
269        let aspace = task.as_thread().proc_data.aspace();
270        let mut mm = aspace.lock();
271        mm.unmap(VirtAddr::from(ptr as usize), PAGE_SIZE_4K)
272            .expect("uprobe: unmap user exec memory failed");
273    }
274
275    fn insert_kretprobe_instance_to_task(instance: RetprobeInstance) {
276        let task = ax_task::current_may_uninit();
277        if let Some(task) = task {
278            let thread = task.try_as_thread();
279            if let Some(thread) = thread {
280                let mut kretprobe_instances = thread.kretprobe_stack.lock();
281                kretprobe_instances.push(instance);
282                return;
283            }
284        }
285        // If the current task is None, we can store it in a static variable
286        let mut instances = INSTANCE.lock();
287        instances.push(instance);
288    }
289
290    fn pop_kretprobe_instance_from_task() -> RetprobeInstance {
291        let task = ax_task::current_may_uninit();
292        if let Some(task) = task {
293            let thread = task.try_as_thread();
294            if let Some(thread) = thread {
295                let mut kretprobe_instances = thread.kretprobe_stack.lock();
296                return kretprobe_instances
297                    .pop()
298                    .expect("kretprobe instance stack underflow");
299            }
300        }
301        // If the current task is None, we can pop it from the static variable
302        let mut instances = INSTANCE.lock();
303        instances.pop().unwrap()
304    }
305}
306
307pub(crate) type KprobeManager = kprobe::ProbeManager<KernelRawMutex, KernelKprobeOps>;
308pub(crate) type KprobePointList = ProbePointList<KernelKprobeOps>;
309
310/// Concrete `kprobe::Kprobe` parameterized on the kernel's `RawMutex` and
311/// auxiliary ops, named to match what the perf module expects.
312pub type KernelKprobe = kprobe::Kprobe<KernelRawMutex, KernelKprobeOps>;
313/// Concrete `kprobe::Kretprobe`.
314pub type KernelKretprobe = kprobe::Kretprobe<KernelRawMutex, KernelKprobeOps>;
315/// The `KprobeAuxiliaryOps` impl, aliased under the name the perf module uses.
316pub type KprobeAuxiliary = KernelKprobeOps;
317
318static KPROBE_MANAGER: KprobeManager = KprobeManager::new();
319static KPROBE_POINT_LIST: IrqMutex<KprobePointList> = IrqMutex::new(KprobePointList::new());
320static INSTANCE: IrqMutex<Vec<RetprobeInstance>> = IrqMutex::new(Vec::new());
321
322fn with_manager<F, R>(f: F) -> R
323where
324    F: FnOnce(&KprobeManager) -> R,
325{
326    f(&KPROBE_MANAGER)
327}
328
329fn with_manager_and_list<F, R>(f: F) -> R
330where
331    F: FnOnce(&KprobeManager, &mut KprobePointList) -> R,
332{
333    let mut list = KPROBE_POINT_LIST.try_lock().unwrap();
334    f(&KPROBE_MANAGER, &mut list)
335}
336
337/// Register a kprobe into the global manager, returning the live handle.
338#[inline(never)]
339pub fn register_kprobe(builder: ProbeBuilder<KernelKprobeOps>) -> Arc<KernelKprobe> {
340    with_manager_and_list(|mgr, list| {
341        kprobe_crate_register_kprobe(mgr, list, builder).expect("Failed to register kprobe")
342    })
343}
344
345/// Unregister a previously registered kprobe.
346#[inline(never)]
347pub fn unregister_kprobe(kprobe: Arc<KernelKprobe>) {
348    with_manager_and_list(|mgr, list| kprobe_crate_unregister_kprobe(mgr, list, kprobe));
349}
350
351/// Register a kretprobe and return its live handle.
352#[inline(never)]
353pub fn register_kretprobe(builder: KretprobeBuilder<KernelRawMutex>) -> Arc<KernelKretprobe> {
354    with_manager_and_list(|mgr, list| {
355        kprobe_crate_register_kretprobe(mgr, list, builder).expect("Failed to register kretprobe")
356    })
357}
358
359/// Unregister a previously registered kretprobe.
360#[inline(never)]
361pub fn unregister_kretprobe(kretprobe: Arc<KernelKretprobe>) {
362    with_manager_and_list(|mgr, list| kprobe_crate_unregister_kretprobe(mgr, list, kretprobe));
363}
364
365pub(crate) fn trapframe_to_ptregs(tf: &UserRegisters) -> kprobe::PtRegs {
366    #[cfg(target_arch = "x86_64")]
367    {
368        kprobe::PtRegs {
369            r15: tf.r15 as usize,
370            r14: tf.r14 as usize,
371            r13: tf.r13 as usize,
372            r12: tf.r12 as usize,
373            rbp: tf.rbp as usize,
374            rbx: tf.rbx as usize,
375            r11: tf.r11 as usize,
376            r10: tf.r10 as usize,
377            r9: tf.r9 as usize,
378            r8: tf.r8 as usize,
379            rax: tf.rax as usize,
380            rcx: tf.rcx as usize,
381            rdx: tf.rdx as usize,
382            rsi: tf.rsi as usize,
383            rdi: tf.rdi as usize,
384            orig_rax: tf.vector as usize,
385            rip: tf.rip as usize,
386            cs: tf.cs as usize,
387            rflags: tf.rflags as usize,
388            rsp: tf.rsp as usize,
389            ss: tf.ss as usize,
390        }
391    }
392    #[cfg(target_arch = "riscv64")]
393    {
394        kprobe::PtRegs {
395            epc: tf.sepc,
396            ra: tf.regs.ra,
397            sp: tf.regs.sp,
398            gp: tf.regs.gp,
399            tp: tf.regs.tp,
400            t0: tf.regs.t0,
401            t1: tf.regs.t1,
402            t2: tf.regs.t2,
403            s0: tf.regs.s0,
404            s1: tf.regs.s1,
405            a0: tf.regs.a0,
406            a1: tf.regs.a1,
407            a2: tf.regs.a2,
408            a3: tf.regs.a3,
409            a4: tf.regs.a4,
410            a5: tf.regs.a5,
411            a6: tf.regs.a6,
412            a7: tf.regs.a7,
413            s2: tf.regs.s2,
414            s3: tf.regs.s3,
415            s4: tf.regs.s4,
416            s5: tf.regs.s5,
417            s6: tf.regs.s6,
418            s7: tf.regs.s7,
419            s8: tf.regs.s8,
420            s9: tf.regs.s9,
421            s10: tf.regs.s10,
422            s11: tf.regs.s11,
423            t3: tf.regs.t3,
424            t4: tf.regs.t4,
425            t5: tf.regs.t5,
426            t6: tf.regs.t6,
427            status: tf.sstatus.bits(),
428            badaddr: 0,
429            cause: 0,
430            orig_a0: tf.regs.a0,
431        }
432    }
433    #[cfg(target_arch = "aarch64")]
434    {
435        kprobe::PtRegs {
436            regs: tf.x,
437            sp: 0, // aarch64 SP is not saved in TrapFrame
438            pc: tf.elr,
439            pstate: tf.spsr,
440            orig_x0: tf.x[0],
441            syscallno: -1,
442            unused2: 0,
443        }
444    }
445    #[cfg(target_arch = "loongarch64")]
446    {
447        kprobe::PtRegs {
448            regs: [
449                tf.regs.zero,
450                tf.regs.ra,
451                tf.regs.tp,
452                tf.regs.sp,
453                tf.regs.a0,
454                tf.regs.a1,
455                tf.regs.a2,
456                tf.regs.a3,
457                tf.regs.a4,
458                tf.regs.a5,
459                tf.regs.a6,
460                tf.regs.a7,
461                tf.regs.t0,
462                tf.regs.t1,
463                tf.regs.t2,
464                tf.regs.t3,
465                tf.regs.t4,
466                tf.regs.t5,
467                tf.regs.t6,
468                tf.regs.t7,
469                tf.regs.t8,
470                tf.regs.u0,
471                tf.regs.fp,
472                tf.regs.s0,
473                tf.regs.s1,
474                tf.regs.s2,
475                tf.regs.s3,
476                tf.regs.s4,
477                tf.regs.s5,
478                tf.regs.s6,
479                tf.regs.s7,
480                tf.regs.s8,
481            ],
482            orig_a0: 0,
483            csr_era: tf.era,
484            csr_badvaddr: 0,
485            csr_crmd: 0,
486            csr_prmd: tf.prmd,
487            csr_euen: 0,
488            csr_ecfg: 0,
489            csr_estat: 0,
490        }
491    }
492}
493
494pub(crate) fn ptregs_write_back(pt: &kprobe::PtRegs, tf: &mut UserRegisters) {
495    #[cfg(target_arch = "x86_64")]
496    {
497        tf.r15 = pt.r15 as u64;
498        tf.r14 = pt.r14 as u64;
499        tf.r13 = pt.r13 as u64;
500        tf.r12 = pt.r12 as u64;
501        tf.rbp = pt.rbp as u64;
502        tf.rbx = pt.rbx as u64;
503        tf.r11 = pt.r11 as u64;
504        tf.r10 = pt.r10 as u64;
505        tf.r9 = pt.r9 as u64;
506        tf.r8 = pt.r8 as u64;
507        tf.rax = pt.rax as u64;
508        tf.rcx = pt.rcx as u64;
509        tf.rdx = pt.rdx as u64;
510        tf.rsi = pt.rsi as u64;
511        tf.rdi = pt.rdi as u64;
512        tf.rip = pt.rip as u64;
513        tf.cs = pt.cs as u64;
514        tf.vector = pt.orig_rax as u64;
515        tf.rflags = pt.rflags as u64;
516        tf.rsp = pt.rsp as u64;
517        tf.ss = pt.ss as u64;
518    }
519    #[cfg(target_arch = "riscv64")]
520    {
521        tf.sepc = pt.epc;
522        tf.regs.ra = pt.ra;
523        tf.regs.sp = pt.sp;
524        tf.regs.gp = pt.gp;
525        tf.regs.tp = pt.tp;
526        tf.regs.t0 = pt.t0;
527        tf.regs.t1 = pt.t1;
528        tf.regs.t2 = pt.t2;
529        tf.regs.s0 = pt.s0;
530        tf.regs.s1 = pt.s1;
531        tf.regs.a0 = pt.a0;
532        tf.regs.a1 = pt.a1;
533        tf.regs.a2 = pt.a2;
534        tf.regs.a3 = pt.a3;
535        tf.regs.a4 = pt.a4;
536        tf.regs.a5 = pt.a5;
537        tf.regs.a6 = pt.a6;
538        tf.regs.a7 = pt.a7;
539        tf.regs.s2 = pt.s2;
540        tf.regs.s3 = pt.s3;
541        tf.regs.s4 = pt.s4;
542        tf.regs.s5 = pt.s5;
543        tf.regs.s6 = pt.s6;
544        tf.regs.s7 = pt.s7;
545        tf.regs.s8 = pt.s8;
546        tf.regs.s9 = pt.s9;
547        tf.regs.s10 = pt.s10;
548        tf.regs.s11 = pt.s11;
549        tf.regs.t3 = pt.t3;
550        tf.regs.t4 = pt.t4;
551        tf.regs.t5 = pt.t5;
552        tf.regs.t6 = pt.t6;
553    }
554    #[cfg(target_arch = "aarch64")]
555    {
556        tf.x = pt.regs;
557        tf.elr = pt.pc;
558        tf.spsr = pt.pstate;
559    }
560    #[cfg(target_arch = "loongarch64")]
561    {
562        tf.regs.zero = pt.regs[0];
563        tf.regs.ra = pt.regs[1];
564        tf.regs.tp = pt.regs[2];
565        tf.regs.sp = pt.regs[3];
566        tf.regs.a0 = pt.regs[4];
567        tf.regs.a1 = pt.regs[5];
568        tf.regs.a2 = pt.regs[6];
569        tf.regs.a3 = pt.regs[7];
570        tf.regs.a4 = pt.regs[8];
571        tf.regs.a5 = pt.regs[9];
572        tf.regs.a6 = pt.regs[10];
573        tf.regs.a7 = pt.regs[11];
574        tf.regs.t0 = pt.regs[12];
575        tf.regs.t1 = pt.regs[13];
576        tf.regs.t2 = pt.regs[14];
577        tf.regs.t3 = pt.regs[15];
578        tf.regs.t4 = pt.regs[16];
579        tf.regs.t5 = pt.regs[17];
580        tf.regs.t6 = pt.regs[18];
581        tf.regs.t7 = pt.regs[19];
582        tf.regs.t8 = pt.regs[20];
583        tf.regs.u0 = pt.regs[21];
584        tf.regs.fp = pt.regs[22];
585        tf.regs.s0 = pt.regs[23];
586        tf.regs.s1 = pt.regs[24];
587        tf.regs.s2 = pt.regs[25];
588        tf.regs.s3 = pt.regs[26];
589        tf.regs.s4 = pt.regs[27];
590        tf.regs.s5 = pt.regs[28];
591        tf.regs.s6 = pt.regs[29];
592        tf.regs.s7 = pt.regs[30];
593        tf.regs.s8 = pt.regs[31];
594        tf.era = pt.csr_era;
595        tf.prmd = pt.csr_prmd;
596    }
597}
598
599pub fn handle_breakpoint(tf: &mut KernelTrapFrame<'_>) -> bool {
600    let mut updated = tf.snapshot();
601    let mut pt_regs = trapframe_to_ptregs(&updated);
602    let handled = with_manager(|manager| kprobe::kprobe_handler_from_break(manager, &mut pt_regs));
603    if handled.is_some() {
604        ptregs_write_back(&pt_regs, &mut updated);
605        tf.apply_registers(&updated);
606        return true;
607    }
608    false
609}
610
611#[cfg(target_arch = "x86_64")]
612pub fn handle_debug(tf: &mut KernelTrapFrame<'_>) -> bool {
613    let mut updated = tf.snapshot();
614    let mut pt_regs = trapframe_to_ptregs(&updated);
615    let handled = with_manager(|manager| kprobe::kprobe_handler_from_debug(manager, &mut pt_regs));
616    if handled.is_some() {
617        ptregs_write_back(&pt_regs, &mut updated);
618        tf.apply_registers(&updated);
619        return true;
620    }
621    false
622}