libsdb/
mod.rs

1#![cfg(target_os = "linux")]
2#![allow(dead_code)]
3#[cfg(not(target_arch = "x86_64"))]
4compile_error!("Not x86_64 arch");
5
6use breakpoint_site::IdType;
7use disassembler::print_disassembly;
8use ffi::sig_abbrev;
9use gimli::{DW_AT_location, DW_AT_type, DW_TAG_formal_parameter, DW_TAG_variable};
10use indoc::indoc;
11use nix::libc::SIGTRAP;
12use nix::sys::signal::Signal;
13use nix::unistd::Pid;
14use parse::{parse_register_value, parse_vector};
15use process::{Process, ProcessState, StopReason, StoppointId, SyscallCatchPolicy, TrapType};
16use register_info::{GRegisterInfos, RegisterType, register_info_by_name};
17use sdb_error::SdbError;
18use std::any::{Any, TypeId};
19use std::cell::Ref;
20use std::cmp::min;
21use std::collections::HashSet;
22use std::fs::File;
23use std::io::{BufRead, BufReader};
24use std::path::Path;
25use std::rc::Rc;
26use syscalls::{syscall_id_to_name, syscall_name_to_id};
27use target::Target;
28use traits::FromLowerHexStr;
29use traits::StoppointTrait;
30use types::{StoppointMode, VirtualAddress};
31mod breakpoint;
32mod breakpoint_site;
33mod disassembler;
34mod ffi;
35mod parse;
36mod stack;
37mod stoppoint_collection;
38mod utils;
39mod watchpoint;
40
41pub mod dwarf;
42pub mod elf;
43pub mod syscalls;
44pub mod target;
45pub mod traits;
46pub use utils::ResultLogExt;
47
48use watchpoint::WatchPoint;
49
50use breakpoint::AddressBreakpoint;
51use breakpoint::FunctionBreakpoint;
52use breakpoint::LineBreakpoint;
53
54use register_info::RegisterInfo;
55
56use types::TypedData;
57
58use dwarf::DwarfExpressionSimpleLocation;
59use register_info::register_info_by_dwarf;
60
61use dwarf::DwarfExpressionResult;
62
63pub mod bit;
64pub mod pipe;
65pub mod process;
66pub mod register_info;
67pub mod registers;
68pub(crate) mod sdb_error;
69pub mod types;
70
71/// Not async-signal-safe
72/// https://man7.org/linux/man-pages/man7/signal-safety.7.html
73pub fn attach(args: &[&str]) -> Result<Rc<Target>, SdbError> {
74    if args.len() == 3 && args[1] == "-p" {
75        let pid = Pid::from_raw(args[2].parse().unwrap());
76        return Target::attach(pid);
77    } else {
78        let program_path = args[1];
79        let target = Target::launch(Path::new(program_path), None)?;
80        let pid = target.get_process().pid();
81        println!("Launched process with PID {pid}");
82        return Ok(target);
83    }
84}
85
86fn print_stop_reason(target: &Target, reason: StopReason) -> Result<(), SdbError> {
87    let process = &target.get_process();
88    let pid = process.pid();
89    match reason.reason {
90        ProcessState::Exited => {
91            let info = reason.info;
92            println!("Process {pid} exited with status {info}");
93        }
94        ProcessState::Terminated => {
95            let sig: Signal = reason.info.try_into().unwrap();
96            println!("Process {pid} terminated with signal {}", sig.as_str());
97        }
98        ProcessState::Stopped => {
99            get_signal_stop_reason(target, reason)?;
100            println!(
101                "Thread {} {}",
102                reason.tid,
103                get_signal_stop_reason(target, reason)?
104            );
105        }
106        ProcessState::Running => {
107            eprintln!("Incorrect state");
108        }
109    };
110    Ok(())
111}
112
113fn get_signal_stop_reason(target: &Target, reason: StopReason) -> Result<String, SdbError> {
114    let process = target.get_process();
115    let pc = process.get_pc(Some(reason.tid));
116    let mut msg = format!(
117        "stopped with signal {} at {:#x}\n",
118        sig_abbrev(reason.info),
119        pc.addr()
120    );
121    let line = target.line_entry_at_pc(Some(reason.tid))?;
122    if !line.is_end() {
123        let file = line
124            .get_current()
125            .file_entry
126            .as_ref()
127            .unwrap()
128            .path
129            .file_name()
130            .unwrap()
131            .to_str()
132            .unwrap();
133        msg += &format!("    at {}:{}\n", file, line.get_current().line);
134    }
135    let func_name = target.function_name_at_address(pc)?;
136    if !func_name.is_empty() {
137        msg += &format!("    in {func_name}\n");
138    }
139    if reason.info == SIGTRAP {
140        msg += &get_sigtrap_info(&process, reason)?;
141    }
142    Ok(msg)
143}
144
145fn get_sigtrap_info(process: &Process, reason: StopReason) -> Result<String, SdbError> {
146    if reason.trap_reason == Some(TrapType::SoftwareBreak) {
147        let site = process
148            .breakpoint_sites()
149            .borrow()
150            .get_by_address(process.get_pc(Some(reason.tid)))?;
151        return Ok(format!(" (breakpoint {})", site.borrow().id()));
152    }
153    if reason.trap_reason == Some(TrapType::HardwareBreak) {
154        let id = process.get_current_hardware_stoppoint(Some(reason.tid))?;
155
156        match id {
157            StoppointId::BreakpointSite(id) => return Ok(format!(" (breakpoint {id})")),
158            StoppointId::Watchpoint(id) => {
159                let point = process.watchpoints().borrow().get_by_id(id)?;
160                let point = point.borrow() as Ref<dyn Any>;
161                let point = point.downcast_ref::<WatchPoint>().unwrap();
162                let mut msg = format!(" (watchpoint {})", point.id());
163                if point.data() == point.previous_data() {
164                    msg += &format!("\nValue: {:#x}", point.data());
165                } else {
166                    msg += &format!(
167                        "\nOld value: {:#x}\nNew value: {:#x}",
168                        point.previous_data(),
169                        point.data()
170                    );
171                }
172                return Ok(msg);
173            }
174        }
175    }
176
177    if reason.trap_reason == Some(TrapType::SingleStep) {
178        return Ok(" (single step)".to_string());
179    }
180    if reason.trap_reason == Some(TrapType::Syscall) {
181        let info = reason.syscall_info.as_ref().unwrap();
182        return match info.data {
183            process::SyscallData::Args(data) => Ok(format!(
184                " (syscall entry)\nsyscall: {}({})",
185                syscall_id_to_name(info.id as i64)?,
186                data.iter()
187                    .map(|d| { format!("{d:#x}") })
188                    .collect::<Vec<_>>()
189                    .join(",")
190            )),
191            process::SyscallData::Ret(data) => {
192                Ok(format!(" (syscall exit)\nsyscall returned {data:#x}"))
193            }
194        };
195    }
196
197    return Ok("".to_string());
198}
199
200pub fn handle_command(target: &Rc<Target>, line: &str) -> Result<(), SdbError> {
201    let args: Vec<&str> = line.split(" ").filter(|s| !s.is_empty()).collect();
202    let process = &target.get_process();
203    let cmd = args[0];
204    if cmd == "continue" {
205        process.resume_all_threads()?;
206        let reason = process.wait_on_signal(Pid::from_raw(-1))?;
207        handle_stop(target, reason)?;
208    } else if cmd == "help" {
209        print_help(&args);
210    } else if cmd == "register" {
211        handle_register_command(target, &args)?;
212    } else if cmd == "breakpoint" {
213        handle_breakpoint_command(target, &args)?;
214    } else if cmd == "memory" {
215        handle_memory_command(process, &args)?;
216    } else if cmd == "disassemble" {
217        handle_disassemble_command(process, &args)?;
218    } else if cmd == "watchpoint" {
219        handle_watchpoint_command(process, &args)?;
220    } else if cmd == "catchpoint" {
221        handle_catchpoint_command(process, &args)?;
222    } else if cmd == "next" {
223        let reason = target.step_over(None)?;
224        handle_stop(target, reason)?;
225    } else if cmd == "finish" {
226        let reason = target.step_out(None)?;
227        handle_stop(target, reason)?;
228    } else if cmd == "step" {
229        let reason = target.step_in(None)?;
230        handle_stop(target, reason)?;
231    } else if cmd == "stepi" {
232        let reason = process.step_instruction(None)?;
233        handle_stop(target, reason)?;
234    } else if cmd == "up" {
235        target.get_stack(None).borrow_mut().up();
236        print_code_location(target)?;
237    } else if cmd == "down" {
238        target.get_stack(None).borrow_mut().down();
239        print_code_location(target)?;
240    } else if cmd == "backtrace" {
241        print_backtrace(target)?;
242    } else if cmd == "thread" {
243        handle_thread_command(target, &args)?;
244    } else if cmd == "variable" {
245        handle_variable_command(target, &args)?;
246    } else if cmd == "expression" {
247        let expr = &line[line.find(' ').unwrap() + 1..];
248        let ret = target.evaluate_expression(expr, None)?;
249        if let Some(ret) = ret {
250            let str = ret.return_value.visualize(&target.get_process(), 0)?;
251            println!("${}: {}", ret.id, str);
252        }
253    } else {
254        eprintln!("Unknown command");
255    }
256    Ok(())
257}
258
259fn handle_variable_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
260    if args.len() < 2 {
261        print_help(&["help", "variable"]);
262        return Ok(());
263    }
264    if args[1] == "locals" {
265        handle_variable_locals_command(target)?;
266        return Ok(());
267    }
268
269    if args.len() < 3 {
270        print_help(&["help", "variable"]);
271        return Ok(());
272    }
273    if args[1] == "read" {
274        handle_variable_read_command(target, args)?;
275    } else if args[1] == "location" {
276        handle_variable_location_command(target, args)?;
277    }
278    Ok(())
279}
280
281fn handle_variable_location_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
282    let name = args[2];
283    let pc = target.get_pc_file_address(None);
284    let var = target.find_variable(name, &pc)?;
285    if var.is_none() {
286        eprintln!("Variable not found");
287        return Ok(());
288    }
289    let var = var.unwrap();
290    let loc = var.index(DW_AT_location.0 as u64)?.as_evaluated_location(
291        &target.get_process(),
292        &target.get_stack(None).borrow().current_frame().registers,
293        false,
294    )?;
295    let print_simple_location = |loc: &DwarfExpressionSimpleLocation| -> Result<(), SdbError> {
296        if let DwarfExpressionSimpleLocation::Register { reg_num } = loc {
297            let name = register_info_by_dwarf(*reg_num as i32)?.name;
298            println!("Register: {name}");
299        } else if let DwarfExpressionSimpleLocation::Address { address } = loc {
300            println!("Address: {:#x}", address.addr());
301        } else {
302            println!("None");
303        }
304        Ok(())
305    };
306
307    if let DwarfExpressionResult::SimpleLocation(loc) = loc {
308        print_simple_location(&loc)?;
309    } else if let DwarfExpressionResult::Pieces(pieces) = loc {
310        for piece in pieces.pieces {
311            print!(
312                "Piece: offset = {}, bit size = {}, location = ",
313                piece.offset, piece.bit_size
314            );
315            print_simple_location(&piece.location)?;
316        }
317    }
318    Ok(())
319}
320
321fn handle_variable_read_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
322    let name = args[2];
323    let pc = target.get_pc_file_address(None);
324    let data = target.resolve_indirect_name(name, &pc)?;
325    let str = data.variable.unwrap().visualize(&target.get_process(), 0)?;
326    println!("Value: {str}");
327    Ok(())
328}
329
330fn handle_variable_locals_command(target: &Rc<Target>) -> Result<(), SdbError> {
331    let pc = target.get_pc_file_address(None);
332    let scopes = pc.rc_elf_file().get_dwarf().scopes_at_address(&pc)?;
333    let mut seen = HashSet::new();
334    for scope in scopes {
335        for var in scope.children() {
336            let name = var.name()?.unwrap_or("".to_string());
337            let tag = var.abbrev_entry().tag;
338            if tag as u16 == DW_TAG_variable.0
339                || tag as u16 == DW_TAG_formal_parameter.0
340                    && !name.is_empty()
341                    && !seen.contains(&name)
342            {
343                let loc = var.index(DW_AT_location.0 as u64)?.as_evaluated_location(
344                    &target.get_process(),
345                    &target.get_stack(None).borrow().current_frame().registers,
346                    false,
347                )?;
348                let type_ = var.index(DW_AT_type.0 as u64)?.as_type();
349                let value = target.read_location_data(&loc, type_.byte_size()?, None)?;
350                let str = TypedData::builder()
351                    .data(value)
352                    .type_(type_)
353                    .build()
354                    .visualize(&target.get_process(), 0)?;
355                println!("{name}: {str}");
356                seen.insert(name);
357            }
358        }
359    }
360    Ok(())
361}
362
363fn handle_thread_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
364    if args.len() < 2 {
365        print_help(&["help", "thread"]);
366        return Ok(());
367    }
368    if args[1] == "list" {
369        for (tid, thread) in target.threads().borrow().iter() {
370            let prefix = if *tid == target.get_process().current_thread() {
371                "*"
372            } else {
373                " "
374            };
375            println!(
376                "{prefix}Thread {tid}: {}",
377                get_signal_stop_reason(target, thread.state.upgrade().unwrap().borrow().reason)?
378            );
379        }
380    } else if args[1] == "select" {
381        if args.len() != 3 {
382            print_help(&["help", "thread"]);
383            return Ok(());
384        }
385        let tid = i32::from_integral(args[2])?;
386        target.get_process().set_current_thread(Pid::from_raw(tid));
387    }
388    Ok(())
389}
390
391fn print_backtrace(target: &Rc<Target>) -> Result<(), SdbError> {
392    let stack = target.get_stack(None);
393    for (i, frame) in stack.borrow().frames().iter().enumerate() {
394        let pc = frame.backtrace_report_address;
395        let func_name = target.function_name_at_address(pc)?;
396
397        let mut message = format!(
398            "{}[{}]: {:#x} {}",
399            if i == stack.borrow().current_frame_index() {
400                "*"
401            } else {
402                " "
403            },
404            i,
405            pc.addr(),
406            func_name
407        );
408        if frame.inlined {
409            message += &format!(
410                " [inlined] {}",
411                frame.func_die.name()?.unwrap_or("".to_string())
412            );
413        }
414        println!("{message}");
415    }
416    Ok(())
417}
418
419fn handle_catchpoint_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
420    if args.len() < 2 {
421        print_help(&["help", "catchpoint"]);
422        return Ok(());
423    }
424    if args[1] == "syscall" {
425        handle_syscall_catchpoint_command(process, args)?;
426    }
427    Ok(())
428}
429
430fn handle_syscall_catchpoint_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
431    let mut policy = SyscallCatchPolicy::All;
432    if args.len() == 3 && args[2] == "none" {
433        policy = SyscallCatchPolicy::None;
434    } else if args.len() >= 3 {
435        let syscalls: Vec<_> = args[2]
436            .split(",")
437            .map(|s| s.trim())
438            .map(|s| {
439                if is_digits(s) {
440                    i32::from_integral(s)
441                } else {
442                    syscall_name_to_id(s).map(|d| d as i32)
443                }
444            })
445            .collect();
446        let to_catch: Result<Vec<_>, _> = syscalls.into_iter().collect();
447        policy = SyscallCatchPolicy::Some(to_catch?);
448    }
449    process.set_syscall_catch_policy(policy);
450    Ok(())
451}
452
453fn is_digits(s: &str) -> bool {
454    !s.is_empty() && s.chars().all(|c| c.is_ascii_digit())
455}
456
457fn handle_watchpoint_command(process: &Rc<Process>, args: &[&str]) -> Result<(), SdbError> {
458    if args.len() < 2 {
459        print_help(&["help", "watchpoint"]);
460        return Ok(());
461    }
462    let command = args[1];
463    if command == "list" {
464        handle_watchpoint_list(process)?;
465        return Ok(());
466    }
467
468    if command == "set" {
469        handle_watchpoint_set(process, args)?;
470        return Ok(());
471    }
472
473    if args.len() < 3 {
474        print_help(&["help", "watchpoint"]);
475        return Ok(());
476    }
477
478    let id = args[2]
479        .parse::<IdType>()
480        .map_err(|_| SdbError::new_err("Command expects watchpoint id"))?;
481    if command == "enable" {
482        process
483            .watchpoints()
484            .borrow()
485            .get_by_id(id)?
486            .borrow_mut()
487            .enable()?;
488    } else if command == "disable" {
489        process
490            .watchpoints()
491            .borrow()
492            .get_by_id(id)?
493            .borrow_mut()
494            .disable()?;
495    } else if command == "delete" {
496        process.watchpoints().borrow_mut().remove_by_id(id)?;
497    }
498    Ok(())
499}
500
501fn handle_watchpoint_list(process: &Process) -> Result<(), SdbError> {
502    let watchpoints = process.watchpoints();
503    let watchpoints = watchpoints.borrow();
504    if watchpoints.empty() {
505        println!("No watchpoints set");
506    } else {
507        println!("Current watchpoints:");
508        watchpoints.for_each(|w| {
509            let w = w.borrow() as Ref<dyn Any>;
510            let w = w.downcast_ref::<WatchPoint>().unwrap();
511            println!(
512                "{}: address = {:#x}, mode = {}, size = {}, {}",
513                w.id(),
514                w.address().addr(),
515                w.mode(),
516                w.size(),
517                if w.is_enabled() {
518                    "enabled"
519                } else {
520                    "disabled"
521                }
522            )
523        });
524    }
525    Ok(())
526}
527
528fn handle_watchpoint_set(process: &Rc<Process>, args: &[&str]) -> Result<(), SdbError> {
529    if args.len() != 5 {
530        print_help(&["help", "watchpoint"]);
531        return Ok(());
532    }
533    let address = u64::from_integral_lower_hex_radix(args[2], 16)?;
534    let mode_text = args[3];
535    let size = usize::from_integral(args[4])?;
536    if !(mode_text == "write" || mode_text == "rw" || mode_text == "execute") {
537        print_help(&["help", "watchpoint"]);
538        return Ok(());
539    }
540    let mode: StoppointMode = match mode_text {
541        "write" => StoppointMode::Write,
542        "rw" => StoppointMode::ReadWrite,
543        "execute" => StoppointMode::Execute,
544        _ => panic!(),
545    };
546    process
547        .create_watchpoint(address.into(), mode, size)?
548        .upgrade()
549        .unwrap()
550        .borrow_mut()
551        .enable()?;
552    Ok(())
553}
554
555fn handle_disassemble_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
556    let mut address = process.get_pc(None);
557    let mut n_instructions = 5usize;
558    let mut args_iter = args.iter();
559    args_iter.next();
560    while let Some(data) = args_iter.next() {
561        match *data {
562            "-a" => {
563                let opt_addr = args_iter
564                    .next()
565                    .ok_or(SdbError::new_err("Invalid address format"))?;
566                address = u64::from_integral_lower_hex_radix(opt_addr, 16)?.into();
567            }
568            "-c" => {
569                let instruction_count = args_iter
570                    .next()
571                    .ok_or(SdbError::new_err("Invalid instruction count"))?;
572                n_instructions = usize::from_integral(instruction_count)?;
573            }
574            _ => {
575                print_help(&["help", "disassemble"]);
576                return Ok(());
577            }
578        }
579    }
580
581    print_disassembly(process, address, n_instructions)?;
582    Ok(())
583}
584
585fn handle_stop(target: &Rc<Target>, reason: StopReason) -> Result<(), SdbError> {
586    print_stop_reason(target, reason)?;
587    if reason.reason == ProcessState::Stopped {
588        print_code_location(target)?;
589    }
590    Ok(())
591}
592
593fn print_code_location(target: &Rc<Target>) -> Result<(), SdbError> {
594    if target.get_stack(None).borrow().has_frames() {
595        let stack = target.get_stack(None);
596        let stack = stack.borrow();
597        let frame = stack.current_frame();
598        print_source(&frame.location.file.path, frame.location.line, 3)?;
599    } else {
600        print_disassembly(&target.get_process(), target.get_process().get_pc(None), 5)?;
601    }
602
603    Ok(())
604}
605
606fn print_source(path: &Path, line: u64, n_lines_context: u64) -> Result<(), SdbError> {
607    let file = File::open(path)
608        .map_err(|e| SdbError::new_err(&format!("Could not open source file, {e}")))?;
609    let reader = BufReader::new(file);
610
611    let start_line = if line <= n_lines_context {
612        1
613    } else {
614        line - n_lines_context
615    };
616    let end_line = line + n_lines_context + 1;
617    let fill_width = ((end_line as f64).log10().floor() as usize) + 1;
618
619    for (idx, line_text) in reader.lines().enumerate() {
620        let current_line = (idx + 1) as u64;
621        if current_line < start_line {
622            continue;
623        }
624        if current_line > end_line {
625            break;
626        }
627        let text = line_text.map_err(|_| SdbError::new_err("Could not read source file"))?;
628        let arrow = if current_line == line { ">" } else { " " };
629        println!("{arrow} {current_line:>fill_width$} {text}");
630    }
631
632    Ok(())
633}
634
635fn handle_memory_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
636    if args.len() < 3 {
637        print_help(&["help", "memory"]);
638        return Ok(());
639    }
640    if args[1] == "read" {
641        handle_memory_read_command(process, args)?;
642    } else if args[1] == "write" {
643        handle_memory_write_command(process, args)?;
644    } else {
645        print_help(&["help", "memory"]);
646    }
647
648    Ok(())
649}
650
651fn handle_memory_read_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
652    let address = u64::from_integral_lower_hex_radix(args[2], 16)?;
653    let mut n_bytes = 32usize;
654    if args.len() == 4 {
655        let bytes_args = usize::from_integral_lower_hex_radix(args[3], 16)?;
656        n_bytes = bytes_args;
657    }
658    let data = process.read_memory(address.into(), n_bytes)?;
659    for i in (0..data.len()).step_by(16) {
660        let bytes = &data[i..min(i + 16, data.len())];
661        let addr = VirtualAddress::from(address) + i as i64;
662        let data_msg = bytes
663            .iter()
664            .map(|b| format!("{b:02x}"))
665            .collect::<Vec<_>>()
666            .join(" ");
667        let msg = format!("{addr:#016x}: {data_msg}");
668        println!("{msg}");
669    }
670    Ok(())
671}
672
673fn handle_memory_write_command(process: &Process, args: &[&str]) -> Result<(), SdbError> {
674    if args.len() != 4 {
675        print_help(&["help", "memory"]);
676        return Ok(());
677    }
678    let address = u64::from_integral_lower_hex_radix(args[2], 16)?;
679    let data = parse_vector(args[3])?;
680    process.write_memory(address.into(), &data)?;
681    Ok(())
682}
683
684fn handle_breakpoint_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
685    if args.len() < 2 {
686        print_help(&["help", "register"]);
687        return Ok(());
688    }
689    let command = args[1];
690    if command == "list" {
691        handle_breakpoint_list_command(target)?;
692        return Ok(());
693    }
694
695    if args.len() < 3 {
696        print_help(&["help", "breakpoint"]);
697        return Ok(());
698    }
699
700    if command == "set" {
701        handle_breakpoint_set_command(target, args)?;
702        return Ok(());
703    }
704
705    handle_breakpoint_toggle(target, args)?;
706    Ok(())
707}
708
709fn handle_breakpoint_toggle(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
710    let command = args[1];
711    let dot_pos = args[2].find('.').unwrap_or(args[2].len());
712    let id_str = &args[2][..dot_pos];
713    let id = IdType::from_integral(id_str)
714        .map_err(|_| SdbError::new_err("Command expects breakpoint id"))?;
715    let bp = target.breakpoints().borrow().get_by_id(id)?;
716    if dot_pos != args[2].len() {
717        let site_id_str = &args[2][dot_pos + 1..];
718        let site_id = IdType::from_integral(site_id_str)
719            .map_err(|_| SdbError::new_err("Command expects breakpoint site id"))?;
720        let site = bp.borrow().breakpoint_sites().get_by_id(site_id)?;
721        if command == "enable" {
722            site.borrow_mut().enable()?;
723        } else if command == "disable" {
724            site.borrow_mut().disable()?;
725        }
726    } else if command == "enable" {
727        bp.borrow_mut().enable()?;
728    } else if command == "disable" {
729        bp.borrow_mut().disable()?;
730    } else if command == "delete" {
731        for site in bp.borrow_mut().breakpoint_sites().iter() {
732            target
733                .get_process()
734                .breakpoint_sites()
735                .borrow_mut()
736                .remove_by_address(site.borrow().address())?;
737        }
738        target.breakpoints().borrow_mut().remove_by_id(id)?;
739    }
740    Ok(())
741}
742
743fn handle_breakpoint_list_command(target: &Rc<Target>) -> Result<(), SdbError> {
744    let breakpoints = target.breakpoints().borrow();
745    if breakpoints.empty() {
746        println!("No breakpoints set");
747    } else {
748        println!("Current breakpoints:");
749        for bp in breakpoints.iter() {
750            if bp.borrow().is_internal() {
751                continue;
752            }
753            print!("{}: ", bp.borrow().id());
754
755            match (&*bp.borrow() as &dyn Any).type_id() {
756                id if id == TypeId::of::<AddressBreakpoint>() => {
757                    print!(
758                        "address = {:#x}",
759                        (bp.borrow() as Ref<dyn Any>)
760                            .downcast_ref::<AddressBreakpoint>()
761                            .unwrap()
762                            .address()
763                    );
764                }
765                id if id == TypeId::of::<FunctionBreakpoint>() => {
766                    print!(
767                        "function = {}",
768                        (bp.borrow() as Ref<dyn Any>)
769                            .downcast_ref::<FunctionBreakpoint>()
770                            .unwrap()
771                            .function_name()
772                    );
773                }
774                id if id == TypeId::of::<LineBreakpoint>() => {
775                    print!(
776                        "file = {}, line = {}",
777                        (bp.borrow() as Ref<dyn Any>)
778                            .downcast_ref::<LineBreakpoint>()
779                            .unwrap()
780                            .file()
781                            .to_str()
782                            .unwrap(),
783                        (bp.borrow() as Ref<dyn Any>)
784                            .downcast_ref::<LineBreakpoint>()
785                            .unwrap()
786                            .line()
787                    );
788                }
789                _ => {}
790            }
791            println!(
792                ", {}",
793                if bp.borrow().is_enabled() {
794                    "enabled"
795                } else {
796                    "disabled"
797                }
798            );
799            bp.borrow().breakpoint_sites().for_each(|site| {
800                println!(
801                    " .{}: address = {:#x}, {}",
802                    site.borrow().id(),
803                    site.borrow().address().addr(),
804                    if site.borrow().is_enabled() {
805                        "enabled"
806                    } else {
807                        "disabled"
808                    }
809                );
810            });
811        }
812    }
813    Ok(())
814}
815
816fn handle_breakpoint_set_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
817    let mut hardware = false;
818    if args.len() == 4 {
819        if args[3] == "-h" {
820            hardware = true;
821        } else {
822            return SdbError::err("Invalid breakpoint command argument");
823        }
824    }
825    if args[2].starts_with("0x") {
826        let address = u64::from_integral_lower_hex_radix(args[2], 16);
827        match address {
828            Ok(address) => {
829                target
830                    .create_address_breakpoint(address.into(), hardware, false)?
831                    .upgrade()
832                    .unwrap()
833                    .borrow_mut()
834                    .enable()?;
835            }
836            Err(_) => {
837                return SdbError::err(
838                    "Breakpoint command expects address in hexadecimal, prefixed with '0x'",
839                );
840            }
841        }
842    } else if args[2].contains(':') {
843        let mut data = args[2].split(':');
844        let path = data.next().unwrap();
845        let line = u64::from_integral(data.next().unwrap());
846        match line {
847            Ok(line) => {
848                target
849                    .create_line_breakpoint(Path::new(path), line as usize, hardware, false)?
850                    .upgrade()
851                    .unwrap()
852                    .borrow_mut()
853                    .enable()?;
854            }
855            Err(_) => {
856                return SdbError::err("Line number should be an integer");
857            }
858        }
859    } else {
860        target
861            .create_function_breakpoint(args[2], false, false)?
862            .upgrade()
863            .unwrap()
864            .borrow_mut()
865            .enable()?;
866    }
867    Ok(())
868}
869
870fn handle_register_command(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
871    if args.len() < 2 {
872        print_help(&["help", "register"]);
873        return Ok(());
874    }
875
876    if args[1] == "read" {
877        handle_register_read(target, args)?;
878    } else if args[1] == "write" {
879        handle_register_write(&target.get_process(), args);
880    } else {
881        print_help(&["help", "register"]);
882    }
883    Ok(())
884}
885
886fn handle_register_read(target: &Rc<Target>, args: &[&str]) -> Result<(), SdbError> {
887    let stack = target.get_stack(None);
888    let stack = stack.borrow();
889    let regs = stack.regs();
890
891    let print_reg_info = |info: &RegisterInfo| -> Result<(), SdbError> {
892        if regs.is_undefined(info.id)? {
893            println!("{}:\tundefined", info.name);
894        } else {
895            let value = regs.read(info)?;
896            println!("{}:\t{}", info.name, value);
897        }
898        Ok(())
899    };
900    if args.len() == 2 || (args.len() == 3 && args[2] == "all") {
901        for info in GRegisterInfos {
902            if args.len() == 3 || info.type_ == RegisterType::Gpr {
903                print_reg_info(info)?;
904            }
905        }
906    } else if args.len() == 3 {
907        let info_res = register_info_by_name(args[2]);
908        match info_res {
909            Ok(info) => {
910                print_reg_info(&info)?;
911            }
912            Err(_) => {
913                eprintln!("No such register")
914            }
915        }
916    } else {
917        print_help(&["help", "register"]);
918    }
919    Ok(())
920}
921
922fn handle_register_write(process: &Process, args: &[&str]) {
923    if args.len() != 4 {
924        print_help(&["help", "register"]);
925        return;
926    }
927    if let Err(e) = (|| -> Result<(), SdbError> {
928        let info = register_info_by_name(args[2])?;
929        let value = parse_register_value(&info, args[3])?;
930        process
931            .get_registers(None)
932            .borrow_mut()
933            .write(&info, value, true)?;
934        Ok(())
935    })() {
936        eprintln!("{e}");
937    }
938}
939
940fn print_help(args: &[&str]) {
941    if args.len() == 1 {
942        eprintln!(indoc! {"
943            Available commands:
944            breakpoint - Commands for operating on breakpoints
945            catchpoint - Commands for operating on catchpoints
946            continue - Resume the process
947            disassemble - Disassemble machine code to assembly
948            finish - Step-out
949            memory - Commands for operating on memory
950            next - Step-over
951            register - Commands for operating on registers
952            step - Step-in
953            stepi - Single instruction step
954            watchpoint - Commands for operating on watchpoints
955            down        - Select the stack frame below the current one
956            up          - Select the stack frame above the current one
957            thread      - Commands for operating on threads
958            variable    - Commands for operating on variables
959        "
960        });
961    } else if args[1] == "register" {
962        eprintln!(indoc! {"
963            Available commands:
964            read
965            read <register>
966            read all
967            write <register> <value>
968        "});
969    } else if args[1] == "breakpoint" {
970        eprintln!(indoc! {"
971            Available commands:
972            list
973            delete <id>
974            disable <id>
975            enable <id>
976            set <address>
977            set <address> -h
978        "});
979    } else if args[1] == "memory" {
980        eprintln!(indoc! {"
981            Available commands:
982            read <address>
983            read <address> <number of bytes>
984            write <address> <bytes>
985        "});
986    } else if args[1] == "disassemble" {
987        eprintln!(indoc! {"
988            Available options:
989            -c <number of instructions>
990            -a <start address>
991        "});
992    } else if args[1] == "watchpoint" {
993        eprintln!(indoc! {"
994            Available commands:
995            list
996            delete <id>
997            disable <id>
998            enable <id>
999            set <address> <write|rw|execute> <size>
1000        "})
1001    } else if args[1] == "catchpoint" {
1002        eprintln!(indoc! {"
1003            Available commands:
1004            syscall
1005            syscall none
1006            syscall <list of syscall IDs or names>
1007        "})
1008    } else if args[1] == "thread" {
1009        eprintln!(indoc! {"
1010            Available commands:
1011            list
1012            select <thread ID>
1013        "})
1014    } else if args[1] == "variable" {
1015        eprintln!(indoc! {"
1016            Available commands:
1017            read <variable>
1018        "})
1019    }
1020}