Skip to main content

sys_rs/
handler.rs

1use libunwind_rs::{Accessors, AddressSpace, Byteorder, Cursor, PtraceState};
2use nix::{errno::Errno, sys::ptrace};
3use std::fs::read_to_string;
4
5use crate::{
6    diag::{Error, Result},
7    hwaccess::Registers,
8    param::{Join, Value},
9    print::Layout,
10    progress::{Execution, Mode, State},
11};
12
13/// Type of a command handler function.
14///
15/// A `CommandFn` is a function that receives the parsed argument list and
16/// a mutable reference to the current `progress::State` and returns a
17/// `Result` indicating success or failure.
18pub type CommandFn = fn(&[Value], &mut State) -> Result<()>;
19
20fn exit_with(args: &[Value], state: &mut State, f: CommandFn) -> Result<()> {
21    f(args, state)?;
22    state.set_execution(Execution::Exit);
23    Ok(())
24}
25
26fn proceed_with(args: &[Value], state: &mut State, f: CommandFn) -> Result<()> {
27    f(args, state)?;
28    state.set_execution(Execution::Run);
29    Ok(())
30}
31
32fn stall_with(args: &[Value], state: &mut State, f: CommandFn) -> Result<()> {
33    f(args, state)?;
34    state.set_execution(Execution::Skip);
35    Ok(())
36}
37
38fn do_breakpoint_common(
39    args: &[Value],
40    state: &mut State,
41    temporary: bool,
42) -> Result<()> {
43    let addr = match args {
44        [Value::Address(addr)] => Ok(*addr),
45        [] => state.prev_rip().ok_or_else(|| Error::from(Errno::ENODATA)),
46        _ => Err(Error::from(Errno::EINVAL)),
47    }?;
48
49    if let Ok(id) = state
50        .breakpoint_mgr()
51        .set_breakpoint(addr, temporary, true, None)
52    {
53        let id = id.ok_or_else(|| Error::from(Errno::ENODATA))?;
54        let bp_type = if temporary { "Temporary" } else { "Permanent" };
55        println!("{bp_type} breakpoint #{id} set at address {addr:#x}");
56    } else {
57        eprintln!("Failed to set breakpoint at address {addr:#x}");
58    }
59
60    Ok(())
61}
62
63/// Handler for ambiguous commands (user input matches multiple commands).
64///
65/// Prints an error message indicating the command was ambiguous and does
66/// not change execution state.
67///
68/// # Errors
69///
70/// Returns an error if the underlying printing operation fails (rare).
71pub fn do_ambiguous(args: &[Value], state: &mut State) -> Result<()> {
72    stall_with(args, state, |args, _| {
73        eprintln!("{}: ambiguous command", args.join(" "));
74        Ok(())
75    })
76}
77
78/// Handler that prints a backtrace for the traced process.
79///
80/// Uses libunwind via ptrace to walk the tracee's stack and prints each
81/// frame. When DWARF line information is available it will include file and
82/// line information.
83///
84/// # Errors
85///
86/// Returns an error if libunwind/ptrace operations fail or if address
87/// -> line resolution fails.
88pub fn do_backtrace(args: &[Value], state: &mut State) -> Result<()> {
89    stall_with(args, state, |_, state| {
90        let pstate = PtraceState::new(u32::try_from(state.pid().as_raw())?)?;
91        let mut aspace = AddressSpace::new(Accessors::ptrace(), Byteorder::Default)?;
92        let mut cursor = Cursor::ptrace(&mut aspace, &pstate)?;
93
94        let mut i = 0;
95        loop {
96            let ip = cursor.ip()?;
97            let name = cursor
98                .proc_name()
99                .unwrap_or_else(|_| "<unknown>".to_string());
100
101            let frame = format!("#{i} {ip:#018x} in {name} ()");
102            match state.addr2line(u64::try_from(ip)?)? {
103                Some(line) => println!("{frame} at {}:{}", line.path(), line.line()),
104                None => println!("{frame}"),
105            }
106
107            if !cursor.step()? {
108                break;
109            }
110
111            i += 1;
112        }
113
114        Ok(())
115    })
116}
117
118/// Set a permanent breakpoint at the given address or the previous RIP.
119///
120/// # Errors
121///
122/// Returns an error if argument parsing fails or if setting the
123/// breakpoint via the breakpoint manager fails.
124pub fn do_breakpoint(args: &[Value], state: &mut State) -> Result<()> {
125    stall_with(args, state, |args, state| {
126        do_breakpoint_common(args, state, false)
127    })
128}
129
130/// Continue execution of the traced process.
131///
132/// Transitions the tracer into `Continue` mode and resumes execution.
133///
134/// # Errors
135///
136/// Returns an error if state update fails (rare).
137pub fn do_continue(args: &[Value], state: &mut State) -> Result<()> {
138    proceed_with(args, state, |_, state| {
139        state.set_mode(Mode::Continue);
140        Ok(())
141    })
142}
143
144/// Delete a breakpoint by id.
145///
146/// # Errors
147///
148/// Returns an error if arguments are invalid or deletion fails.
149pub fn do_delete(args: &[Value], state: &mut State) -> Result<()> {
150    stall_with(args, state, |args, state| match args {
151        [Value::Id(id)] => {
152            if state.breakpoint_mgr().delete_breakpoint(*id).is_err() {
153                eprintln!("No breakpoint number {id}");
154            }
155            Ok(())
156        }
157        _ => Err(Error::from(Errno::EINVAL)),
158    })
159}
160
161/// Examine memory at a given address and format the bytes.
162///
163/// # Errors
164///
165/// Returns an error if arguments are invalid, memory cannot be read, or formatting fails.
166pub fn do_examine(args: &[Value], state: &mut State) -> Result<()> {
167    stall_with(args, state, |args, state| match args {
168        [Value::Format(format), Value::Size(size), Value::Address(addr)] => {
169            let word_size = std::mem::size_of::<usize>();
170            let mut buf = vec![0u8; usize::try_from(*size)?];
171            let mut offset = 0;
172
173            while offset < buf.len() {
174                let read_addr = *addr + offset as u64;
175                let word = if let Ok(val) =
176                    ptrace::read(state.pid(), read_addr as ptrace::AddressType)
177                {
178                    #[allow(
179                        clippy::cast_possible_truncation,
180                        clippy::cast_sign_loss
181                    )]
182                    let ret = val as usize;
183                    ret
184                } else {
185                    eprintln!("Failed to read memory at {read_addr:#x}");
186                    break;
187                };
188
189                let bytes_to_write = word_size.min(buf.len() - offset);
190                (0..bytes_to_write).try_for_each(|i| -> Result<()> {
191                    buf[offset + i] = u8::try_from((word >> (i * 8)) & 0xff)?;
192                    Ok(())
193                })?;
194                offset += bytes_to_write;
195            }
196
197            format.bytes(&buf, *addr)
198        }
199        _ => Err(Error::from(Errno::EINVAL)),
200    })
201}
202
203/// Print help text for available commands.
204///
205/// Accepts a list of strings to print; used by the REPL to display help.
206///
207/// # Errors
208///
209/// Returns an error if printing fails.
210pub fn do_help(args: &[Value], state: &mut State) -> Result<()> {
211    stall_with(args, state, |args, _| {
212        println!("{}", args.join("\n"));
213        Ok(())
214    })
215}
216
217/// Print information about currently set breakpoints.
218///
219/// # Errors
220///
221/// Returns an error if state access fails (rare).
222pub fn do_info_breakpoints(args: &[Value], state: &mut State) -> Result<()> {
223    stall_with(args, state, |_, state| {
224        state.print_breakpoints();
225        Ok(())
226    })
227}
228
229/// Print the memory map (`/proc/PID/maps`) of the traced process.
230///
231/// # Errors
232///
233/// Returns an error if reading `/proc/PID/maps` fails.
234pub fn do_info_memory(args: &[Value], state: &mut State) -> Result<()> {
235    stall_with(args, state, |_, state| {
236        print!("{}", read_to_string(format!("/proc/{}/maps", state.pid()))?);
237        Ok(())
238    })
239}
240
241/// Print the current register state of the traced process.
242///
243/// # Errors
244///
245/// Returns an error if reading registers via ptrace fails.
246pub fn do_info_registers(args: &[Value], state: &mut State) -> Result<()> {
247    stall_with(args, state, |_, state| {
248        let regs = Registers::read(state.pid())?;
249        println!("{regs}");
250        Ok(())
251    })
252}
253
254/// Handler for invalid argument errors.
255///
256/// Prints an error indicating the provided arguments were invalid.
257///
258/// # Errors
259///
260/// Returns an error if printing fails.
261pub fn do_invalid_arguments(args: &[Value], state: &mut State) -> Result<()> {
262    stall_with(args, state, |args, _| {
263        eprintln!("{}: invalid arguments", args.join(" "));
264        Ok(())
265    })
266}
267
268/// Switch the display layout to assembly mode.
269///
270/// If already in assembly layout, prints a message and does nothing.
271///
272/// # Errors
273///
274/// Returns an error on I/O failure while printing (unlikely).
275pub fn do_layout_asm(args: &[Value], state: &mut State) -> Result<()> {
276    stall_with(args, state, |_, state| {
277        if *state.layout() == Layout::Assembly {
278            eprintln!("Already in assembly layout mode");
279        } else {
280            println!("Switching to assembly layout mode");
281            state.set_requested_layout(Layout::Assembly);
282        }
283        Ok(())
284    })
285}
286
287/// Switch the display layout to source mode (when available).
288///
289/// If no source layout is available or already in source mode, prints a
290/// message accordingly.
291///
292/// # Errors
293///
294/// Returns an error if printing fails.
295pub fn do_layout_src(args: &[Value], state: &mut State) -> Result<()> {
296    stall_with(args, state, |_, state| {
297        if state.initial_layout() == Layout::Assembly {
298            eprintln!("No source layout available (DWARF symbols missing?)");
299        } else if *state.layout() == Layout::Source {
300            eprintln!("Already in source layout mode");
301        } else {
302            println!("Switching to source layout mode");
303            state.set_requested_layout(Layout::Source);
304        }
305        Ok(())
306    })
307}
308
309/// Re-print the last printed source or assembly chunk.
310///
311/// Useful after stepping when the REPL wants to show the previously
312/// displayed context again.
313///
314/// # Errors
315///
316/// Returns an error if printing fails.
317pub fn do_list(args: &[Value], state: &mut State) -> Result<()> {
318    stall_with(args, state, |_, state| {
319        if let Some(text) = state.printed() {
320            println!("{text}");
321        }
322        Ok(())
323    })
324}
325
326/// Step over the next instruction (implement stepping semantics).
327///
328/// Transitions the tracer into `StepOver` mode.
329///
330/// # Errors
331///
332/// Returns an error if state update fails.
333pub fn do_next(args: &[Value], state: &mut State) -> Result<()> {
334    proceed_with(args, state, |_, state| {
335        state.set_mode(Mode::StepOver);
336        Ok(())
337    })
338}
339
340/// A no-op handler that does nothing and stalls execution.
341///
342/// # Errors
343///
344/// Never returns an error.
345pub fn do_nothing(args: &[Value], state: &mut State) -> Result<()> {
346    stall_with(args, state, |_, _| Ok(()))
347}
348
349/// Quit the debugger and exit the tracer loop.
350///
351/// Marks the execution state as Exit and prints a message.
352///
353/// # Errors
354///
355/// Returns an error if the underlying exit action fails (unlikely).
356pub fn do_quit(args: &[Value], state: &mut State) -> Result<()> {
357    exit_with(args, state, |_, _| {
358        println!("Exiting...");
359        Ok(())
360    })
361}
362
363/// Step a single instruction (single-step execution).
364///
365/// # Errors
366///
367/// Returns an error if state update fails.
368pub fn do_step(args: &[Value], state: &mut State) -> Result<()> {
369    proceed_with(args, state, |_, _| Ok(()))
370}
371
372/// Set a temporary breakpoint at the given address or previous RIP.
373///
374/// Temporary breakpoints are removed after they are hit.
375///
376/// # Errors
377///
378/// Returns an error if argument parsing or breakpoint insertion fails.
379pub fn do_tbreakpoint(args: &[Value], state: &mut State) -> Result<()> {
380    stall_with(args, state, |args, state| {
381        do_breakpoint_common(args, state, true)
382    })
383}
384
385/// Handler for completely unknown commands.
386///
387/// Prints an error message indicating the command is unknown.
388///
389/// # Errors
390///
391/// Returns an error if printing fails.
392pub fn do_unknown(args: &[Value], state: &mut State) -> Result<()> {
393    stall_with(args, state, |args, _| {
394        eprintln!("{}: unknown command", args.join(" "));
395        Ok(())
396    })
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    use nix::unistd::Pid;
404
405    use crate::{
406        param::Value,
407        print::Layout,
408        progress::{Execution, State},
409    };
410
411    #[test]
412    fn test_do_help_sets_skip() {
413        let mut state = State::new(Pid::from_raw(1), None);
414        let cmds: Vec<Value> = vec![Value::String("one"), Value::String("two")];
415        let res = do_help(&cmds, &mut state);
416        assert!(res.is_ok());
417        assert!(matches!(state.execution(), Execution::Skip));
418    }
419
420    #[test]
421    fn test_do_invalid_arguments_sets_skip() {
422        let mut state = State::new(Pid::from_raw(1), None);
423        let args: Vec<Value> = vec![Value::String("bad")];
424        let res = do_invalid_arguments(&args, &mut state);
425        assert!(res.is_ok());
426        assert!(matches!(state.execution(), Execution::Skip));
427    }
428
429    #[test]
430    fn test_do_nothing_sets_skip() {
431        let mut state = State::new(Pid::from_raw(1), None);
432        let res = do_nothing(&[], &mut state);
433        assert!(res.is_ok());
434        assert!(matches!(state.execution(), Execution::Skip));
435    }
436
437    #[test]
438    fn test_do_layout_asm_switches_when_not_assembly() {
439        let mut state = State::new(Pid::from_raw(1), None);
440        state.set_layout(Layout::Source);
441        let res = do_layout_asm(&[], &mut state);
442        assert!(res.is_ok());
443        assert!(matches!(state.execution(), Execution::Skip));
444        let taken = state.take_requested_layout();
445        assert!(taken.is_some());
446        assert_eq!(taken.unwrap(), Layout::Assembly);
447    }
448
449    #[test]
450    fn test_do_layout_src_no_source_available() {
451        let mut state = State::new(Pid::from_raw(1), None);
452        let res = do_layout_src(&[], &mut state);
453        assert!(res.is_ok());
454        assert!(matches!(state.execution(), Execution::Skip));
455        assert!(state.take_requested_layout().is_none());
456    }
457
458    #[test]
459    fn test_do_continue_next_step_quit() {
460        let mut state = State::new(Pid::from_raw(1), None);
461
462        let res = do_continue(&[], &mut state);
463        assert!(res.is_ok());
464        assert!(matches!(state.execution(), Execution::Run));
465        assert!(matches!(state.mode(), crate::progress::Mode::Continue));
466
467        let mut state2 = State::new(Pid::from_raw(1), None);
468        let res = do_next(&[], &mut state2);
469        assert!(res.is_ok());
470        assert!(matches!(state2.execution(), Execution::Run));
471        assert!(matches!(state2.mode(), crate::progress::Mode::StepOver));
472
473        let mut state3 = State::new(Pid::from_raw(1), None);
474        let res = do_step(&[], &mut state3);
475        assert!(res.is_ok());
476        assert!(matches!(state3.execution(), Execution::Run));
477
478        let mut state4 = State::new(Pid::from_raw(1), None);
479        let res = do_quit(&[], &mut state4);
480        assert!(res.is_ok());
481        assert!(matches!(state4.execution(), Execution::Exit));
482    }
483
484    #[test]
485    fn test_do_list_printed() {
486        let mut state = State::new(Pid::from_raw(1), None);
487        state.set_printed(Some("hello".to_string()));
488        let res = do_list(&[], &mut state);
489        assert!(res.is_ok());
490        assert!(matches!(state.execution(), Execution::Skip));
491    }
492}