Skip to main content

sbpf_debugger/
debugger.rs

1use {
2    crate::{
3        adapter::DebuggerInterface,
4        error::DebuggerResult,
5        parser::{LineMap, RODataSymbol},
6    },
7    either::Either,
8    sbpf_common::{inst_param::Number, instruction::Instruction, opcode::Opcode},
9    sbpf_vm::{syscalls::SyscallHandler, vm::SbpfVm},
10    serde_json::{Value, json},
11    std::collections::HashSet,
12};
13
14pub struct StackFrame<'a> {
15    pub index: usize,
16    pub pc: u64,
17    pub file: Option<&'a str>,
18    pub line: Option<usize>,
19    pub column: Option<usize>,
20}
21
22#[derive(Debug)]
23pub enum DebugMode {
24    Next,
25    Continue,
26}
27
28#[derive(Debug)]
29pub enum DebugEvent {
30    Breakpoint(u64, Option<usize>),
31    Next(u64, Option<usize>),
32    Exit(u64),
33    Error(String),
34}
35
36pub struct Debugger<H: SyscallHandler> {
37    pub vm: SbpfVm<H>,
38    pub breakpoints: HashSet<u64>,
39    pub line_breakpoints: HashSet<usize>,
40    pub dwarf_line_map: Option<LineMap>,
41    pub rodata: Option<Vec<RODataSymbol>>,
42    pub last_breakpoint: Option<u64>,
43    pub debug_mode: DebugMode,
44    pub stopped: bool,
45    pub exit_code: u64,
46    pub at_breakpoint: bool,
47    pub last_breakpoint_pc: Option<u64>,
48    pub initial_compute_budget: u64,
49    pub instruction_offsets: Vec<u64>,
50}
51
52impl<H: SyscallHandler> Debugger<H> {
53    pub fn new(vm: SbpfVm<H>) -> Self {
54        let initial_compute_budget = vm.config.compute_unit_limit;
55
56        let instruction_offsets: Vec<u64> = vm
57            .program
58            .iter()
59            .scan(0u64, |offset, inst| {
60                let current = *offset;
61                *offset += inst.get_size();
62                Some(current)
63            })
64            .collect();
65
66        Self {
67            vm,
68            breakpoints: HashSet::new(),
69            line_breakpoints: HashSet::new(),
70            dwarf_line_map: None,
71            rodata: None,
72            last_breakpoint: None,
73            debug_mode: DebugMode::Continue,
74            stopped: false,
75            exit_code: 0,
76            at_breakpoint: false,
77            last_breakpoint_pc: None,
78            initial_compute_budget,
79            instruction_offsets,
80        }
81    }
82
83    pub fn set_dwarf_line_map(&mut self, dwarf_map: LineMap) {
84        self.dwarf_line_map = Some(dwarf_map);
85    }
86
87    pub fn set_rodata(&mut self, rodata: Vec<RODataSymbol>) {
88        self.rodata = Some(rodata);
89    }
90
91    pub fn set_breakpoint(&mut self, pc: u64) {
92        self.breakpoints.insert(pc);
93    }
94
95    pub fn set_breakpoint_at_line(&mut self, line: usize) -> Result<(), String> {
96        if let Some(dwarf_map) = &self.dwarf_line_map {
97            let pcs = dwarf_map.get_pcs_for_line(line);
98            if pcs.is_empty() {
99                return Err(format!("No code at line {}", line));
100            }
101            self.line_breakpoints.insert(line);
102            for &pc in &pcs {
103                self.breakpoints.insert(pc);
104            }
105            Ok(())
106        } else {
107            Err("No debug info available".to_string())
108        }
109    }
110
111    pub fn remove_breakpoint_at_line(&mut self, line: usize) -> Result<(), String> {
112        if let Some(dwarf_map) = &self.dwarf_line_map {
113            let pcs = dwarf_map.get_pcs_for_line(line);
114            if !pcs.is_empty() {
115                self.line_breakpoints.remove(&line);
116                for &pc in &pcs {
117                    self.breakpoints.remove(&pc);
118                }
119            }
120        }
121        Ok(())
122    }
123
124    pub fn get_current_line(&self) -> Option<usize> {
125        let pc = self.get_pc();
126        self.get_line_for_pc(pc)
127    }
128
129    pub fn get_line_for_pc(&self, pc: u64) -> Option<usize> {
130        if let Some(dwarf_map) = &self.dwarf_line_map {
131            dwarf_map.get_line_for_pc(pc)
132        } else {
133            None
134        }
135    }
136
137    pub fn get_pcs_for_line(&self, line: usize) -> Vec<u64> {
138        if let Some(dwarf_map) = &self.dwarf_line_map {
139            dwarf_map.get_pcs_for_line(line)
140        } else {
141            Vec::new()
142        }
143    }
144
145    pub fn get_breakpoints_info(&self) -> String {
146        if self.line_breakpoints.is_empty() {
147            return "No breakpoints set".to_string();
148        }
149
150        let mut lines: Vec<_> = self.line_breakpoints.iter().copied().collect();
151        lines.sort();
152        let lines_str = lines
153            .iter()
154            .map(|l| l.to_string())
155            .collect::<Vec<_>>()
156            .join(", ");
157        format!("Breakpoints: {}", lines_str)
158    }
159
160    pub fn set_debug_mode(&mut self, debug_mode: DebugMode) {
161        self.debug_mode = debug_mode;
162    }
163
164    pub fn run(&mut self) -> DebuggerResult<DebugEvent> {
165        match self.debug_mode {
166            DebugMode::Next => {
167                let current_pc = self.get_pc();
168
169                if self.at_breakpoint {
170                    match self.vm.step() {
171                        Ok(()) => {
172                            self.at_breakpoint = false;
173                            self.last_breakpoint_pc = None;
174
175                            if self.vm.halted {
176                                let exit_code = self.vm.exit_code.unwrap_or(0);
177                                return Ok(DebugEvent::Exit(exit_code));
178                            }
179
180                            let new_pc = self.get_pc();
181                            if self.breakpoints.contains(&new_pc) {
182                                self.at_breakpoint = true;
183                                self.last_breakpoint_pc = Some(new_pc);
184                                let line_number = self.get_line_for_pc(new_pc);
185                                return Ok(DebugEvent::Breakpoint(new_pc, line_number));
186                            }
187                            let line_number = self.get_line_for_pc(new_pc);
188                            return Ok(DebugEvent::Next(new_pc, line_number));
189                        }
190                        Err(e) => return Ok(DebugEvent::Error(format!("{e}"))),
191                    }
192                }
193
194                if self.breakpoints.contains(&current_pc)
195                    && self.last_breakpoint_pc != Some(current_pc)
196                {
197                    self.at_breakpoint = true;
198                    self.last_breakpoint_pc = Some(current_pc);
199                    let line_number = self.get_line_for_pc(current_pc);
200                    return Ok(DebugEvent::Breakpoint(current_pc, line_number));
201                }
202
203                let event = match self.vm.step() {
204                    Ok(()) => {
205                        if self.vm.halted {
206                            let exit_code = self.vm.exit_code.unwrap_or(0);
207                            DebugEvent::Exit(exit_code)
208                        } else {
209                            let new_pc = self.get_pc();
210                            let line_number = self.get_line_for_pc(new_pc);
211                            DebugEvent::Next(new_pc, line_number)
212                        }
213                    }
214                    Err(e) => DebugEvent::Error(format!("{e}")),
215                };
216                Ok(event)
217            }
218            DebugMode::Continue => loop {
219                let current_pc = self.get_pc();
220
221                if self.at_breakpoint {
222                    match self.vm.step() {
223                        Ok(()) => {
224                            self.at_breakpoint = false;
225                            self.last_breakpoint_pc = None;
226
227                            if self.vm.halted {
228                                let exit_code = self.vm.exit_code.unwrap_or(0);
229                                return Ok(DebugEvent::Exit(exit_code));
230                            }
231                        }
232                        Err(e) => return Ok(DebugEvent::Error(format!("{e}"))),
233                    }
234                    continue;
235                }
236
237                if self.breakpoints.contains(&current_pc)
238                    && self.last_breakpoint_pc != Some(current_pc)
239                {
240                    self.at_breakpoint = true;
241                    self.last_breakpoint_pc = Some(current_pc);
242                    let line_number = self.get_line_for_pc(current_pc);
243                    return Ok(DebugEvent::Breakpoint(current_pc, line_number));
244                }
245
246                match self.vm.step() {
247                    Ok(()) => {
248                        if self.vm.halted {
249                            let exit_code = self.vm.exit_code.unwrap_or(0);
250                            return Ok(DebugEvent::Exit(exit_code));
251                        }
252                    }
253                    Err(e) => return Ok(DebugEvent::Error(format!("{e}"))),
254                }
255            },
256        }
257    }
258
259    pub fn get_pc(&self) -> u64 {
260        self.instruction_offsets
261            .get(self.vm.pc)
262            .copied()
263            .unwrap_or(self.vm.pc as u64)
264    }
265
266    fn instruction_index_to_byte_offset(&self, idx: usize) -> u64 {
267        self.instruction_offsets
268            .get(idx)
269            .copied()
270            .unwrap_or(idx as u64)
271    }
272
273    pub fn get_registers(&self) -> &[u64] {
274        &self.vm.registers
275    }
276
277    pub fn get_register(&self, idx: usize) -> Option<u64> {
278        self.vm.registers.get(idx).copied()
279    }
280
281    pub fn set_register_value(&mut self, idx: usize, value: u64) -> Result<(), String> {
282        if let Some(reg) = self.vm.registers.get_mut(idx) {
283            *reg = value;
284            Ok(())
285        } else {
286            Err(format!("Register index {} out of range", idx))
287        }
288    }
289
290    pub fn get_rodata(&self) -> Option<&Vec<RODataSymbol>> {
291        self.rodata.as_ref()
292    }
293
294    pub fn get_compute_units(&self) -> u64 {
295        self.vm.compute_meter.get_consumed()
296    }
297
298    pub fn get_instruction(&self) -> Option<&Instruction> {
299        self.vm.program.get(self.vm.pc)
300    }
301
302    pub fn get_instruction_asm(&self) -> Option<String> {
303        let inst = self.get_instruction()?;
304        let mut asm = inst.to_asm().ok()?;
305
306        // Resolve rodata label.
307        if inst.opcode == Opcode::Lddw
308            && let Some(Either::Right(Number::Int(imm))) = &inst.imm
309            && let Some(ref rodata_symbols) = self.rodata
310        {
311            let addr = *imm as u64;
312            for sym in rodata_symbols {
313                if sym.address == addr {
314                    asm = asm.replace(&imm.to_string(), &sym.name);
315                    break;
316                }
317            }
318        }
319
320        Some(asm)
321    }
322
323    pub fn get_source_location(&self, pc: u64) -> Option<(&str, usize, usize)> {
324        if let Some(dwarf_map) = &self.dwarf_line_map
325            && let Some(loc) = dwarf_map.get_source_location(pc)
326        {
327            return Some((&loc.file, loc.line as usize, loc.column as usize));
328        }
329        None
330    }
331
332    pub fn clear_breakpoints(&mut self) {
333        if let Some(dwarf_map) = &self.dwarf_line_map {
334            let lines: Vec<usize> = self.line_breakpoints.iter().copied().collect();
335            for line in lines {
336                let pcs = dwarf_map.get_pcs_for_line(line);
337                for pc in pcs {
338                    self.breakpoints.remove(&pc);
339                }
340                self.line_breakpoints.remove(&line);
341            }
342        } else {
343            self.breakpoints.clear();
344            self.line_breakpoints.clear();
345        }
346    }
347
348    pub fn get_memory(&self, address: u64, size: usize) -> Option<Vec<u8>> {
349        self.vm
350            .memory
351            .read_bytes(address, size)
352            .map(|slice| slice.to_vec())
353            .ok()
354    }
355
356    fn make_stack_frame(&self, index: usize, pc: u64) -> StackFrame<'_> {
357        let loc = self.get_source_location(pc);
358        StackFrame {
359            index,
360            pc,
361            file: loc.map(|(f, _, _)| f),
362            line: loc.map(|(_, l, _)| l),
363            column: loc.map(|(_, _, c)| c),
364        }
365    }
366
367    pub fn get_stack_frames(&self) -> Vec<StackFrame<'_>> {
368        let mut frames = Vec::new();
369
370        // Current frame
371        let current_pc = self.get_pc();
372        frames.push(self.make_stack_frame(0, current_pc));
373
374        // Call stack frames
375        for (i, frame) in self.vm.call_stack.iter().rev().enumerate() {
376            let pc = self.instruction_index_to_byte_offset(frame.return_pc);
377            frames.push(self.make_stack_frame(i + 1, pc));
378        }
379
380        frames
381    }
382}
383
384impl<H: SyscallHandler> DebuggerInterface for Debugger<H> {
385    fn next(&mut self) -> Value {
386        self.set_debug_mode(DebugMode::Next);
387        self.run_to_json()
388    }
389
390    fn r#continue(&mut self) -> Value {
391        self.set_debug_mode(DebugMode::Continue);
392        self.run_to_json()
393    }
394
395    fn set_breakpoint(&mut self, file: String, line: usize) -> Value {
396        match self.set_breakpoint_at_line(line) {
397            Ok(()) => json!({
398                "type": "setBreakpoint",
399                "file": file,
400                "line": line,
401                "verified": true
402            }),
403            Err(e) => json!({
404                "type": "setBreakpoint",
405                "file": file,
406                "line": line,
407                "verified": false,
408                "error": e
409            }),
410        }
411    }
412
413    fn remove_breakpoint(&mut self, file: String, line: usize) -> Value {
414        match self.remove_breakpoint_at_line(line) {
415            Ok(()) => json!({
416                "type": "removeBreakpoint",
417                "file": file,
418                "line": line,
419                "success": true
420            }),
421            Err(e) => json!({
422                "type": "removeBreakpoint",
423                "file": file,
424                "line": line,
425                "success": false,
426                "error": e
427            }),
428        }
429    }
430
431    fn get_stack_frames(&self) -> Value {
432        let frames: Vec<Value> = self
433            .get_stack_frames()
434            .iter()
435            .map(|frame| {
436                let name = frame.file.unwrap_or("?").to_string();
437                let file = frame.file.unwrap_or("?").to_string();
438                let line = frame.line.unwrap_or(0);
439                let column = frame.column.unwrap_or(0);
440                json!({
441                    "index": frame.index,
442                    "name": name,
443                    "file": file,
444                    "line": line,
445                    "column": column,
446                    "instruction": frame.pc
447                })
448            })
449            .collect();
450        json!({ "frames": frames })
451    }
452
453    fn get_registers(&self) -> Value {
454        let regs: Vec<Value> = self
455            .get_registers()
456            .iter()
457            .enumerate()
458            .map(|(i, &value)| {
459                json!({
460                    "name": format!("r{}", i),
461                    "value": format!("0x{:016x}", value),
462                    "type": "u64"
463                })
464            })
465            .collect();
466        json!({ "registers": regs })
467    }
468
469    fn get_memory(&self, address: u64, size: usize) -> Value {
470        match self.get_memory(address, size) {
471            Some(data) => json!({
472                "address": address,
473                "size": size,
474                "data": data
475            }),
476            None => json!({
477                "address": address,
478                "size": size,
479                "data": []
480            }),
481        }
482    }
483
484    fn set_register(&mut self, index: usize, value: u64) -> Value {
485        match self.set_register_value(index, value) {
486            Ok(()) => json!({
487                "type": "setRegister",
488                "index": index,
489                "value": value,
490                "success": true
491            }),
492            Err(e) => json!({
493                "type": "setRegister",
494                "index": index,
495                "value": value,
496                "success": false,
497                "error": e
498            }),
499        }
500    }
501
502    fn get_rodata(&self) -> Value {
503        match self.get_rodata() {
504            Some(symbols) => {
505                let arr: Vec<Value> = symbols
506                    .iter()
507                    .map(|sym| {
508                        json!({
509                            "name": sym.name,
510                            "address": format!("0x{:016x}", sym.address),
511                            "value": sym.content
512                        })
513                    })
514                    .collect();
515                json!({ "rodata": arr })
516            }
517            None => json!({ "rodata": [] }),
518        }
519    }
520
521    fn clear_breakpoints(&mut self, _file: String) -> Value {
522        self.clear_breakpoints();
523        json!({"result": "ok"})
524    }
525
526    fn quit(&mut self) -> Value {
527        json!({ "type": "quit" })
528    }
529
530    fn get_compute_units(&self) -> Value {
531        let used = self.get_compute_units();
532        let total = self.initial_compute_budget;
533        let remaining = total.saturating_sub(used);
534        json!({
535            "total": total,
536            "used": used,
537            "remaining": remaining
538        })
539    }
540
541    fn run_to_json(&mut self) -> Value {
542        match self.run() {
543            Ok(event) => match event {
544                DebugEvent::Next(pc, line) => json!({
545                    "type": "next",
546                    "pc": pc,
547                    "line": line
548                }),
549                DebugEvent::Breakpoint(pc, line) => json!({
550                    "type": "breakpoint",
551                    "pc": pc,
552                    "line": line
553                }),
554                DebugEvent::Exit(code) => {
555                    let cu = DebuggerInterface::get_compute_units(self);
556                    json!({
557                        "type": "exit",
558                        "code": code,
559                        "compute_units": cu
560                    })
561                }
562                DebugEvent::Error(msg) => json!({
563                    "type": "error",
564                    "message": msg
565                }),
566            },
567            Err(e) => json!({
568                "type": "error",
569                "message": format!("{:?}", e)
570            }),
571        }
572    }
573}