Skip to main content

sbpf_debugger/
runner.rs

1use {
2    crate::{
3        debugger::Debugger,
4        error::DebuggerResult,
5        input::ParsedInput,
6        parser::{LineMap, rodata_from_section},
7    },
8    sbpf_assembler::{Assembler, AssemblerOption, DebugMode},
9    sbpf_disassembler::program::Program,
10    sbpf_runtime::{Runtime, config::RuntimeConfig},
11    sbpf_vm::memory::Memory,
12    std::path::{Path, PathBuf},
13};
14
15pub struct DebuggerSession {
16    pub debugger: Debugger,
17    pub line_map: Option<LineMap>,
18    pub elf_bytes: Vec<u8>,
19    pub elf_path: PathBuf,
20}
21
22pub fn load_session_from_asm(
23    asm_path: &str,
24    parsed: ParsedInput,
25    config: RuntimeConfig,
26) -> DebuggerResult<DebuggerSession> {
27    let asm_path = Path::new(asm_path);
28    if !asm_path.exists() {
29        return Err(crate::error::DebuggerError::InvalidInput(format!(
30            "Assembly file not found: {}",
31            asm_path.display()
32        )));
33    }
34
35    let source_code = std::fs::read_to_string(asm_path)?;
36    let filename = asm_path
37        .file_name()
38        .and_then(|n| n.to_str())
39        .unwrap_or("unknown.s")
40        .to_string();
41    let directory = asm_path
42        .parent()
43        .and_then(|p| p.canonicalize().ok())
44        .map(|p| p.to_string_lossy().to_string())
45        .unwrap_or_else(|| ".".to_string());
46
47    let options = AssemblerOption::default().with_debug_mode(DebugMode {
48        filename,
49        directory,
50    });
51    let assembler = Assembler::new(options);
52    let bytecode = assembler
53        .assemble(&source_code)
54        .map_err(|errors| crate::error::DebuggerError::Assembler(format!("{:?}", errors)))?;
55
56    load_session_from_bytes(bytecode, parsed, config, None)
57}
58
59pub fn load_session_from_elf(
60    elf_path: &str,
61    parsed: ParsedInput,
62    config: RuntimeConfig,
63) -> DebuggerResult<DebuggerSession> {
64    let elf_bytes = std::fs::read(elf_path)?;
65    load_session_from_bytes(elf_bytes, parsed, config, Some(elf_path.into()))
66}
67
68fn load_session_from_bytes(
69    elf_bytes: Vec<u8>,
70    parsed: ParsedInput,
71    config: RuntimeConfig,
72    elf_path: Option<PathBuf>,
73) -> DebuggerResult<DebuggerSession> {
74    let mut runtime = Runtime::new(parsed.instruction.program_id, elf_bytes.clone(), config)?;
75    for (program_id, elf) in &parsed.programs {
76        runtime.add_program(program_id, elf.clone());
77    }
78    runtime.prepare(&parsed.instruction, &parsed.accounts)?;
79
80    let mut debugger = Debugger::new(runtime);
81    if let Ok(line_map) = LineMap::from_elf_data(&elf_bytes) {
82        debugger.set_dwarf_line_map(line_map);
83    }
84
85    // Best effort rodata symbol extraction: decode errors are ignored here
86    // since the runtime already validated the program.
87    if let Ok(program) = Program::from_bytes(&elf_bytes)
88        && let Ok(disassembled) = program.to_ixs()
89        && let Some(ref section) = disassembled.value.rodata
90    {
91        let mut rodata_symbols = rodata_from_section(section);
92        // Replace generated labels with actual labels from DWARF info (if available).
93        if let Some(ref line_map) = debugger.dwarf_line_map {
94            let text_offset = line_map.get_text_offset();
95            for sym in &mut rodata_symbols {
96                let rodata_offset = sym.address - Memory::RODATA_START;
97                let addr = rodata_offset + text_offset;
98                if let Some(name) = line_map.get_label_for_address(addr) {
99                    sym.name = name.to_string();
100                }
101            }
102        }
103        if !rodata_symbols.is_empty() {
104            debugger.set_rodata(rodata_symbols);
105        }
106    }
107
108    Ok(DebuggerSession {
109        line_map: debugger.dwarf_line_map.clone(),
110        debugger,
111        elf_bytes,
112        elf_path: elf_path.unwrap_or_else(|| PathBuf::from("<memory>")),
113    })
114}