Skip to main content

kprobe/arch/x86/
mod.rs

1use alloc::{string::ToString, sync::Arc};
2use core::{
3    fmt::Debug,
4    ops::{Deref, DerefMut},
5    sync::atomic::AtomicUsize,
6};
7
8use lock_api::RawMutex;
9use yaxpeax_arch::LengthedInstruction;
10
11use crate::{KprobeAuxiliaryOps, KprobeOps, ProbeBasic, ProbeBuilder, arch::ExecMemType};
12
13const EBREAK_INST: u8 = 0xcc; // x86_64: 0xcc
14const MAX_INSTRUCTION_SIZE: usize = 15; // x86_64 max instruction length
15
16/// The x86_64 implementation of Probe.
17pub struct Probe<L: RawMutex + 'static, F: KprobeAuxiliaryOps> {
18    basic: ProbeBasic<L>,
19    point: Arc<X86ProbePoint<F>>,
20}
21
22/// The probe point for x86_64 architecture.
23#[derive(Debug)]
24pub struct X86ProbePoint<F: KprobeAuxiliaryOps> {
25    addr: usize,
26    old_instruction_ptr: ExecMemType<F>,
27    old_instruction_len: usize,
28    user_pid: Option<i32>,
29    // Dynamic user pointer for handling user space instruction pointer adjustments
30    dynamic_user_ptr: AtomicUsize,
31    _marker: core::marker::PhantomData<F>,
32}
33
34impl<F: KprobeAuxiliaryOps> Drop for X86ProbePoint<F> {
35    fn drop(&mut self) {
36        let address = self.addr;
37        F::set_writeable_for_address(address, self.old_instruction_len, self.user_pid, |ptr| {
38            // Restore the original instruction at the probe point
39            unsafe {
40                core::ptr::copy_nonoverlapping(
41                    self.old_instruction_ptr.as_ptr(),
42                    ptr,
43                    self.old_instruction_len,
44                );
45            }
46        });
47
48        // Free the dynamic user pointer if it was allocated
49        let dyn_ptr = self.dynamic_user_ptr();
50        if dyn_ptr != 0 {
51            F::free_user_exec_memory(self.user_pid, dyn_ptr as *mut u8);
52        }
53        log::trace!(
54            "X86KprobePoint::drop: Restored instruction at address: {:#x}",
55            address
56        );
57    }
58}
59
60impl<L: RawMutex + 'static, F: KprobeAuxiliaryOps> Debug for Probe<L, F> {
61    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
62        f.debug_struct("Probe")
63            .field("basic", &self.basic)
64            .field("point", &self.point)
65            .finish()
66    }
67}
68
69impl<L: RawMutex + 'static, F: KprobeAuxiliaryOps> Deref for Probe<L, F> {
70    type Target = ProbeBasic<L>;
71
72    fn deref(&self) -> &Self::Target {
73        &self.basic
74    }
75}
76
77impl<L: RawMutex + 'static, F: KprobeAuxiliaryOps> DerefMut for Probe<L, F> {
78    fn deref_mut(&mut self) -> &mut Self::Target {
79        &mut self.basic
80    }
81}
82
83impl<F: KprobeAuxiliaryOps> ProbeBuilder<F> {
84    pub(crate) fn install<L: RawMutex + 'static>(self) -> (Probe<L, F>, Arc<X86ProbePoint<F>>) {
85        let probe_point = match &self.probe_point {
86            Some(point) => point.clone(),
87            None => self.replace_inst(),
88        };
89        let probe = Probe {
90            basic: ProbeBasic::from(self),
91            point: probe_point.clone(),
92        };
93        (probe, probe_point)
94    }
95
96    /// Replace the instruction at the specified address with a breakpoint instruction.
97    fn replace_inst(&self) -> Arc<X86ProbePoint<F>> {
98        let address = self.symbol_addr + self.offset;
99        let inst_tmp = super::alloc_exec_memory::<F>(self.user_pid);
100
101        F::copy_memory(
102            address as *const u8,
103            inst_tmp.as_ptr(),
104            MAX_INSTRUCTION_SIZE,
105            self.user_pid,
106        );
107
108        let buf = unsafe { core::slice::from_raw_parts(inst_tmp.as_ptr(), MAX_INSTRUCTION_SIZE) };
109
110        let decoder = yaxpeax_x86::amd64::InstDecoder::default();
111        let inst = decoder.decode_slice(buf).unwrap();
112        let len = inst.len().to_const();
113        log::trace!("inst: {:?}, len: {:?}", inst.to_string(), len);
114
115        let point = Arc::new(X86ProbePoint {
116            addr: address,
117            old_instruction_ptr: inst_tmp,
118            old_instruction_len: len as usize,
119            user_pid: self.user_pid,
120            dynamic_user_ptr: AtomicUsize::new(0),
121            _marker: core::marker::PhantomData,
122        });
123
124        F::set_writeable_for_address(address, len as usize, self.user_pid, |ptr| unsafe {
125            core::ptr::write(ptr, EBREAK_INST);
126        });
127        log::trace!(
128            "Kprobe::install: address: {:#x}, func_name: {:?}",
129            address,
130            self.symbol
131        );
132        point
133    }
134}
135
136impl<L: RawMutex + 'static, F: KprobeAuxiliaryOps> Probe<L, F> {
137    /// Get the probe point associated with this probe.
138    pub fn probe_point(&self) -> &Arc<X86ProbePoint<F>> {
139        &self.point
140    }
141}
142
143impl<F: KprobeAuxiliaryOps> KprobeOps for X86ProbePoint<F> {
144    fn return_address(&self) -> usize {
145        self.addr + self.old_instruction_len
146    }
147
148    fn single_step_address(&self) -> usize {
149        self.old_instruction_ptr.as_ptr() as usize
150    }
151
152    fn debug_address(&self) -> usize {
153        let dynamic_user_ptr = self.dynamic_user_ptr();
154        if dynamic_user_ptr != 0 {
155            dynamic_user_ptr + self.old_instruction_len
156        } else {
157            self.old_instruction_ptr.as_ptr() as usize + self.old_instruction_len
158        }
159    }
160
161    fn break_address(&self) -> usize {
162        self.addr
163    }
164
165    fn dynamic_user_ptr(&self) -> usize {
166        self.dynamic_user_ptr
167            .load(core::sync::atomic::Ordering::SeqCst)
168    }
169
170    fn set_dynamic_user_ptr(&self, ptr: usize) -> usize {
171        self.dynamic_user_ptr
172            .store(ptr, core::sync::atomic::Ordering::SeqCst);
173        ptr + self.old_instruction_len
174    }
175
176    fn old_instruction_len(&self) -> usize {
177        self.old_instruction_len
178    }
179
180    fn pid(&self) -> Option<i32> {
181        self.user_pid
182    }
183}
184
185/// Set up a single step for the given address.
186///
187/// This function updates the program counter (PC) to the specified address.
188pub(crate) fn setup_single_step(pt_regs: &mut PtRegs, single_step_address: usize) {
189    pt_regs.update_pc(single_step_address);
190    pt_regs.set_single_step(true);
191}
192
193/// Clear the single step for the given address.
194///
195/// This function updates the program counter (PC) to the specified address.
196pub(crate) fn clear_single_step(pt_regs: &mut PtRegs, single_step_address: usize) {
197    pt_regs.update_pc(single_step_address);
198    pt_regs.set_single_step(false);
199}
200
201/// The CPU register state for x86_64 architecture.
202#[repr(C)]
203#[derive(Debug, Copy, Clone)]
204#[allow(missing_docs)]
205pub struct PtRegs {
206    pub r15: usize,
207    pub r14: usize,
208    pub r13: usize,
209    pub r12: usize,
210    pub rbp: usize,
211    pub rbx: usize,
212    pub r11: usize,
213    pub r10: usize,
214    pub r9: usize,
215    pub r8: usize,
216    pub rax: usize,
217    pub rcx: usize,
218    pub rdx: usize,
219    pub rsi: usize,
220    pub rdi: usize,
221    // On syscall entry, this is syscall#. On CPU exception, this is error code.
222    // On hw interrupt, it's IRQ number
223    pub orig_rax: usize,
224    pub rip: usize,
225    pub cs: usize,
226    pub rflags: usize,
227    pub rsp: usize,
228    pub ss: usize,
229}
230
231impl PtRegs {
232    pub(crate) fn break_address(&self) -> usize {
233        self.rip - 1 // The breakpoint instruction is at the address of rip - 1
234    }
235
236    pub(crate) fn debug_address(&self) -> usize {
237        self.rip // The debug address is the current instruction pointer
238    }
239
240    pub(crate) fn update_pc(&mut self, pc: usize) {
241        self.rip = pc as _;
242    }
243
244    pub(crate) fn set_single_step(&mut self, enable: bool) {
245        if enable {
246            self.rflags |= 0x100;
247        } else {
248            self.rflags &= !0x100;
249        }
250    }
251
252    pub(crate) fn sp(&self) -> usize {
253        self.rsp
254    }
255
256    /// Get the return value from the rax register.
257    pub fn first_ret_value(&self) -> usize {
258        self.rax
259    }
260
261    /// Get the return value from the rdx register.
262    pub fn second_ret_value(&self) -> usize {
263        self.rdx
264    }
265}
266
267const KERNEL_DS: usize = 24; // Kernel data segment selector
268
269/// See <https://elixir.bootlin.com/linux/v6.6/source/arch/x86/kernel/rethook.c#L62>
270#[unsafe(naked)]
271pub(crate) unsafe extern "C" fn arch_rethook_trampoline<
272    L: RawMutex + 'static,
273    F: KprobeAuxiliaryOps + 'static,
274>() {
275    core::arch::naked_asm!(
276        // Push a fake return address to tell the unwinder it's a kretprobe.
277        // TODO: Use the real return address later.
278        "pushq $0",
279        "pushq ${kernel_data_segment}", // fake ss
280        // Save the 'sp - 16', this will be fixed later.
281        "pushq %rsp",
282        "pushfq", // rflags
283
284        // SAVE_REGS_STRING
285        "subq $24, %rsp", // skip cs, ip, orig_ax
286        "pushq %rdi",
287        "pushq %rsi",
288        "pushq %rdx",
289        "pushq %rcx",
290        "pushq %rax",
291        "pushq %r8",
292        "pushq %r9",
293        "pushq %r10",
294        "pushq %r11",
295        "pushq %rbx",
296        "pushq %rbp",
297        "pushq %r12",
298        "pushq %r13",
299        "pushq %r14",
300        "pushq %r15",
301        // ENCODE_FRAME_POINTER
302        // "lea 1(%rsp), %rbp",
303        "movq %rsp, %rdi",
304        "call {rethook_trampoline_callback}",
305        // RESTORE_REGS_STRING
306        "popq %r15",
307        "popq %r14",
308        "popq %r13",
309        "popq %r12",
310        "popq %rbp",
311        "popq %rbx",
312        "popq %r11",
313        "popq %r10",
314        "popq %r9",
315        "popq %r8",
316        "popq %rax",
317        "popq %rcx",
318        "popq %rdx",
319        "popq %rsi",
320        "popq %rdi",
321        // Skip orig_ax, ip, cs
322        "addq $24, %rsp",
323
324        // In the callback function, 'regs->flags' is copied to 'regs->ss'.
325        "addq $16, %rsp",
326
327        "popfq",
328        "ret",
329        kernel_data_segment = const KERNEL_DS, // Kernel data segment
330        // arch_rethook_trampoline = sym arch_rethook_trampoline::<L,F>,
331        rethook_trampoline_callback = sym arch_rethook_trampoline_callback::<L, F>,
332        options(att_syntax)
333    )
334}
335
336pub(crate) fn arch_rethook_trampoline_callback<
337    L: RawMutex + 'static,
338    F: KprobeAuxiliaryOps + 'static,
339>(
340    pt_regs: &mut PtRegs,
341) -> usize {
342    pt_regs.rip = arch_rethook_trampoline::<L, F> as *const () as usize; // Set return address to trampoline
343    pt_regs.orig_rax = usize::MAX;
344    pt_regs.rsp += 16; // Adjust rsp to remove the fake return address
345
346    let pt_regs_pointer = unsafe { (pt_regs as *mut PtRegs).add(1) as *mut usize };
347
348    let correct_ret_addr =
349        super::retprobe::rethook_trampoline_handler::<L, F>(pt_regs, pt_regs_pointer as _);
350    pt_regs.ss = pt_regs.rflags; // Copy eflags to ss
351    correct_ret_addr
352}
353
354/// Fix up the return address in the pt_regs after a rethook.
355pub(crate) fn arch_rethook_fixup_return(pt_regs: &mut PtRegs, correct_ret_addr: usize) {
356    let pt_regs_pointer = unsafe { (pt_regs as *mut PtRegs).add(1) as *mut usize };
357    unsafe {
358        // Replace fake return address with real one.
359        *pt_regs_pointer = correct_ret_addr;
360    }
361}
362
363/// Prepare the kretprobe instance for the rethook.
364pub(crate) fn arch_rethook_prepare<L: RawMutex + 'static, F: KprobeAuxiliaryOps + 'static>(
365    kretprobe_instance: &mut super::retprobe::RetprobeInstance,
366    pt_regs: &mut PtRegs,
367) {
368    let sp = pt_regs.sp();
369    let stack = unsafe { &mut *(sp as *mut usize) };
370    // Prepare the kretprobe instance for the rethook
371    // Get the return address from the stack
372    kretprobe_instance.ret_addr = *stack;
373    kretprobe_instance.frame = sp;
374    // Set the return address to the trampoline
375    *stack = arch_rethook_trampoline::<L, F> as *const () as usize;
376}