Skip to main content

sbpf_debugger/
parser.rs

1use {
2    crate::error::{DebuggerError, DebuggerResult},
3    gimli::{EndianSlice, RunTimeEndian, SectionId},
4    object::{Object, ObjectSection},
5    sbpf_vm::memory::Memory,
6    std::{borrow::Cow, collections::HashMap, path::PathBuf},
7};
8
9#[derive(Debug, Clone)]
10pub struct RODataSymbol {
11    pub name: String,
12    pub address: u64,
13    pub content: String,
14}
15
16pub fn rodata_from_section(
17    section: &sbpf_disassembler::rodata::RodataSection,
18) -> Vec<RODataSymbol> {
19    section
20        .items
21        .iter()
22        .map(|item| RODataSymbol {
23            name: item.label.clone(),
24            address: Memory::RODATA_START + item.offset,
25            content: item.data_type.to_asm(),
26        })
27        .collect()
28}
29
30#[derive(Debug, Clone)]
31pub struct SourceLocation {
32    pub file: String,
33    pub line: u32,
34    pub column: u32,
35    _address: u64,
36}
37
38#[derive(Clone)]
39pub struct LineMap {
40    address_to_line: HashMap<u64, usize>,
41    line_to_addresses: HashMap<usize, Vec<u64>>,
42    source_locations: HashMap<u64, SourceLocation>,
43    labels: HashMap<u64, String>,
44    files: Vec<String>,
45    text_offset: u64,
46}
47
48impl Default for LineMap {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl LineMap {
55    pub fn new() -> Self {
56        Self {
57            address_to_line: HashMap::new(),
58            line_to_addresses: HashMap::new(),
59            source_locations: HashMap::new(),
60            labels: HashMap::new(),
61            files: Vec::new(),
62            text_offset: 0,
63        }
64    }
65
66    pub fn from_elf_file(file_path: &str) -> DebuggerResult<Self> {
67        let file_data = std::fs::read(file_path)?;
68        Self::from_elf_data(&file_data)
69    }
70
71    pub fn from_elf_data(file_data: &[u8]) -> DebuggerResult<Self> {
72        let object = object::File::parse(file_data)?;
73        let mut line_map = Self::new();
74        line_map.text_offset = object
75            .section_by_name(".text")
76            .map(|section| section.address())
77            .unwrap_or(0);
78        line_map.parse_debug_info_from_object(&object)?;
79        Ok(line_map)
80    }
81
82    fn parse_debug_info_from_object(&mut self, obj_file: &object::File) -> DebuggerResult<()> {
83        let endian = if obj_file.is_little_endian() {
84            RunTimeEndian::Little
85        } else {
86            RunTimeEndian::Big
87        };
88
89        let load_section = |id: SectionId| -> Result<Cow<[u8]>, gimli::Error> {
90            match obj_file.section_by_name(id.name()) {
91                Some(section) => match section.uncompressed_data() {
92                    Ok(data) => Ok(data),
93                    Err(_) => Ok(Cow::Borrowed(&[])),
94                },
95                None => Ok(Cow::Borrowed(&[])),
96            }
97        };
98
99        let borrow_section = |section| EndianSlice::new(Cow::as_ref(section), endian);
100
101        let dwarf_sections =
102            gimli::DwarfSections::load(&load_section).map_err(DebuggerError::Dwarf)?;
103        let dwarf = dwarf_sections.borrow(borrow_section);
104
105        let mut iter = dwarf.units();
106        while let Some(header) = iter.next().map_err(DebuggerError::Dwarf)? {
107            let unit = dwarf.unit(header).map_err(DebuggerError::Dwarf)?;
108            let unit = unit.unit_ref(&dwarf);
109
110            if let Some(program) = unit.line_program.clone() {
111                let comp_dir = if let Some(ref dir) = unit.comp_dir {
112                    PathBuf::from(dir.to_string_lossy().into_owned())
113                } else {
114                    PathBuf::new()
115                };
116
117                let mut rows = program.rows();
118                while let Some((header, row)) = rows.next_row().map_err(DebuggerError::Dwarf)? {
119                    if !row.end_sequence() {
120                        let mut file_path = String::new();
121                        if let Some(file) = row.file(header) {
122                            let mut path = PathBuf::new();
123                            path.clone_from(&comp_dir);
124
125                            if file.directory_index() != 0
126                                && let Some(dir) = file.directory(header)
127                            {
128                                path.push(
129                                    unit.attr_string(dir)
130                                        .map_err(DebuggerError::Dwarf)?
131                                        .to_string_lossy()
132                                        .as_ref(),
133                                );
134                            }
135
136                            path.push(
137                                unit.attr_string(file.path_name())
138                                    .map_err(DebuggerError::Dwarf)?
139                                    .to_string_lossy()
140                                    .as_ref(),
141                            );
142                            file_path = path.to_string_lossy().to_string();
143                        }
144
145                        let line = match row.line() {
146                            Some(line) => line.get() as u32,
147                            None => 0,
148                        };
149                        let column = match row.column() {
150                            gimli::ColumnType::LeftEdge => 0,
151                            gimli::ColumnType::Column(column) => column.get() as u32,
152                        };
153
154                        let address = row.address();
155
156                        self.address_to_line.insert(address, line as usize);
157                        self.line_to_addresses
158                            .entry(line as usize)
159                            .or_default()
160                            .push(address);
161
162                        let source_loc = SourceLocation {
163                            file: file_path.clone(),
164                            line,
165                            column,
166                            _address: address,
167                        };
168                        self.source_locations.insert(address, source_loc);
169
170                        if !file_path.is_empty() && !self.files.contains(&file_path) {
171                            self.files.push(file_path);
172                        }
173                    }
174                }
175            }
176
177            // Parse labels.
178            let mut entries = unit.entries();
179            while let Some(entry) = entries.next_dfs().map_err(DebuggerError::Dwarf)? {
180                if entry.tag() == gimli::DW_TAG_label
181                    && let (Some(name_attr), Some(pc_attr)) = (
182                        entry.attr_value(gimli::DW_AT_name),
183                        entry.attr_value(gimli::DW_AT_low_pc),
184                    )
185                    && let Ok(name) = unit.attr_string(name_attr)
186                    && let gimli::AttributeValue::Addr(addr) = pc_attr
187                {
188                    self.labels.insert(addr, name.to_string_lossy().to_string());
189                }
190            }
191        }
192
193        Ok(())
194    }
195
196    pub fn get_label_for_address(&self, address: u64) -> Option<&str> {
197        self.labels.get(&address).map(|s| s.as_str())
198    }
199
200    pub fn get_text_offset(&self) -> u64 {
201        self.text_offset
202    }
203
204    pub fn get_line_for_address(&self, address: u64) -> Option<usize> {
205        self.address_to_line.get(&address).copied()
206    }
207
208    pub fn get_addresses_for_line(&self, line: usize) -> Option<&[u64]> {
209        self.line_to_addresses.get(&line).map(|v| v.as_slice())
210    }
211
212    pub fn get_line_for_pc(&self, pc: u64) -> Option<usize> {
213        let address = pc.saturating_add(self.text_offset);
214        self.get_line_for_address(address)
215    }
216
217    pub fn get_pcs_for_line(&self, line: usize) -> Vec<u64> {
218        if let Some(addresses) = self.get_addresses_for_line(line) {
219            addresses
220                .iter()
221                .map(|addr| addr.saturating_sub(self.text_offset))
222                .collect()
223        } else {
224            Vec::new()
225        }
226    }
227
228    pub fn get_source_location(&self, pc: u64) -> Option<&SourceLocation> {
229        let address = pc.saturating_add(self.text_offset);
230        self.source_locations.get(&address)
231    }
232
233    pub fn get_line_to_addresses(&self) -> &HashMap<usize, Vec<u64>> {
234        &self.line_to_addresses
235    }
236
237    pub fn get_line_to_pcs(&self) -> HashMap<usize, Vec<u64>> {
238        self.line_to_addresses
239            .iter()
240            .map(|(line, addrs)| {
241                (
242                    *line,
243                    addrs
244                        .iter()
245                        .map(|addr| addr.saturating_sub(self.text_offset))
246                        .collect(),
247                )
248            })
249            .collect()
250    }
251}