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