Skip to main content

sbpf_debugger/
runner.rs

1use {
2    crate::{
3        debugger::Debugger,
4        error::{DebuggerError, DebuggerResult},
5        execution_cost::ExecutionCost,
6        parser::{LineMap, rodata_from_section},
7        syscalls::DebuggerSyscallHandler,
8    },
9    either::Either,
10    sbpf_assembler::{Assembler, AssemblerOption, DebugMode, SbpfArch},
11    sbpf_common::{inst_param::Number, opcode::Opcode},
12    sbpf_disassembler::program::Program,
13    sbpf_vm::{
14        compute::ComputeMeter,
15        memory::Memory,
16        vm::{SbpfVm, SbpfVmConfig},
17    },
18    solana_address::Address,
19    std::{
20        fs::File,
21        io::Read,
22        path::{Path, PathBuf},
23    },
24};
25
26pub struct DebuggerSession {
27    pub debugger: Debugger<DebuggerSyscallHandler>,
28    pub line_map: Option<LineMap>,
29    pub elf_bytes: Vec<u8>,
30    pub elf_path: PathBuf,
31}
32
33impl DebuggerSession {
34    pub fn build_vm(
35        instructions: Vec<sbpf_common::instruction::Instruction>,
36        input: Vec<u8>,
37        rodata_bytes: Vec<u8>,
38        config: SbpfVmConfig,
39        program_id: Address,
40    ) -> SbpfVm<DebuggerSyscallHandler> {
41        let compute_meter = ComputeMeter::new(config.compute_unit_limit);
42        let handler = DebuggerSyscallHandler::new(ExecutionCost::default(), program_id);
43
44        let mut vm = SbpfVm::new_with_config(instructions, input, rodata_bytes, handler, config);
45        vm.compute_meter = compute_meter;
46        vm
47    }
48}
49
50pub fn load_session_from_asm(
51    asm_path: &str,
52    input: Vec<u8>,
53    config: SbpfVmConfig,
54    program_id: Address,
55) -> DebuggerResult<DebuggerSession> {
56    let asm_path = Path::new(asm_path);
57    if !asm_path.exists() {
58        return Err(DebuggerError::InvalidInput(format!(
59            "Assembly file not found: {}",
60            asm_path.display()
61        )));
62    }
63
64    let source_code = std::fs::read_to_string(asm_path)?;
65    let filename = asm_path
66        .file_name()
67        .and_then(|n| n.to_str())
68        .unwrap_or("unknown.s")
69        .to_string();
70    let directory = asm_path
71        .parent()
72        .and_then(|p| p.canonicalize().ok())
73        .map(|p| p.to_string_lossy().to_string())
74        .unwrap_or_else(|| ".".to_string());
75
76    let options = AssemblerOption {
77        arch: SbpfArch::V0,
78        debug_mode: Some(DebugMode {
79            filename,
80            directory,
81        }),
82    };
83    let assembler = Assembler::new(options);
84    let bytecode = assembler
85        .assemble(&source_code)
86        .map_err(|errors| DebuggerError::Assembler(format!("{:?}", errors)))?;
87
88    load_session_from_bytes(bytecode, input, config, None, program_id)
89}
90
91pub fn load_session_from_elf(
92    elf_path: &str,
93    input: Vec<u8>,
94    config: SbpfVmConfig,
95    program_id: Address,
96) -> DebuggerResult<DebuggerSession> {
97    let mut file = File::open(elf_path)?;
98    let mut elf_bytes = Vec::new();
99    file.read_to_end(&mut elf_bytes)?;
100    load_session_from_bytes(elf_bytes, input, config, Some(elf_path.into()), program_id)
101}
102
103pub fn load_session_from_bytes(
104    elf_bytes: Vec<u8>,
105    input: Vec<u8>,
106    config: SbpfVmConfig,
107    elf_path: Option<PathBuf>,
108    program_id: Address,
109) -> DebuggerResult<DebuggerSession> {
110    let program = Program::from_bytes(&elf_bytes)?;
111    let entrypoint = program.get_entrypoint_offset().unwrap_or(0);
112    let (mut instructions, rodata_section) = program.to_ixs()?;
113    let rodata_bytes = rodata_section
114        .as_ref()
115        .map(|section| section.data.clone())
116        .unwrap_or_default();
117
118    // Remap rodata addresses from ELF addresses to VM addresses
119    if let Some(ref section) = rodata_section {
120        let elf_rodata_base = section.base_address;
121        let elf_rodata_end = elf_rodata_base + section.data.len() as u64;
122
123        for ix in &mut instructions {
124            if ix.opcode == Opcode::Lddw
125                && let Some(Either::Right(Number::Int(imm))) = &ix.imm
126            {
127                let addr = *imm as u64;
128                if addr >= elf_rodata_base && addr < elf_rodata_end {
129                    let offset = addr - elf_rodata_base;
130                    let vm_addr = Memory::RODATA_START + offset;
131                    ix.imm = Some(Either::Right(Number::Int(vm_addr as i64)));
132                }
133            }
134        }
135    }
136
137    let mut vm = DebuggerSession::build_vm(instructions, input, rodata_bytes, config, program_id);
138    vm.set_entrypoint(entrypoint as usize);
139
140    let mut debugger = Debugger::new(vm);
141    if let Ok(line_map) = LineMap::from_elf_data(&elf_bytes) {
142        debugger.set_dwarf_line_map(line_map.clone());
143    }
144
145    // Set rodata symbols from the disassembler's parsed section
146    if let Some(ref section) = rodata_section {
147        let mut rodata_symbols = rodata_from_section(section);
148        // Replace generated labels with actual labels from DWARF info (if available).
149        if let Some(ref line_map) = debugger.dwarf_line_map {
150            let text_offset = line_map.get_text_offset();
151            for sym in &mut rodata_symbols {
152                let rodata_offset = sym.address - Memory::RODATA_START;
153                let addr = rodata_offset + text_offset;
154                if let Some(name) = line_map.get_label_for_address(addr) {
155                    sym.name = name.to_string();
156                }
157            }
158        }
159        if !rodata_symbols.is_empty() {
160            debugger.set_rodata(rodata_symbols);
161        }
162    }
163
164    Ok(DebuggerSession {
165        line_map: debugger.dwarf_line_map.clone(),
166        debugger,
167        elf_bytes,
168        elf_path: elf_path.unwrap_or_else(|| PathBuf::from("<memory>")),
169    })
170}