Skip to main content

sbpf_assembler/
debug.rs

1use {
2    crate::section::{DebugSection, SectionType},
3    gimli::{
4        DW_AT_comp_dir, DW_AT_decl_file, DW_AT_decl_line, DW_AT_high_pc, DW_AT_language,
5        DW_AT_low_pc, DW_AT_name, DW_AT_producer, DW_AT_stmt_list, DW_LANG_Mips_Assembler,
6        DW_TAG_label, Encoding, Format, LineEncoding, LittleEndian, SectionId,
7        write::{Address, AttributeValue, DwarfUnit, EndianVec, LineProgram, LineString, Sections},
8    },
9};
10
11const SBPF_INSTRUCTION_LENGTH: u8 = 8;
12
13#[derive(Debug, Clone)]
14pub struct DebugData {
15    pub filename: String,
16    pub directory: String,
17    pub lines: Vec<(u64, u32)>,
18    pub labels: Vec<(String, u64, u32)>,
19    pub code_start: u64,
20    pub code_end: u64,
21}
22
23fn calc_name_offset(names: &[String]) -> u32 {
24    (names
25        .iter()
26        .filter(|n| !n.is_empty())
27        .map(|n| n.len() + 1)
28        .sum::<usize>()
29        + 1) as u32
30}
31
32/// Generate DebugSections from debug data
33pub fn generate_debug_sections(
34    data: &DebugData,
35    text_offset: u64,
36    section_names: &mut Vec<String>,
37    current_offset: &mut u64,
38) -> Vec<DebugSection> {
39    let code_start = data.code_start + text_offset;
40    let code_end = data.code_end + text_offset;
41    let mut dwarf = generate_dwarf_sections(data, text_offset, code_start, code_end);
42
43    let mut sections = Vec::new();
44
45    // .debug_abbrev section
46    let mut abbrev = DebugSection::new(
47        SectionId::DebugAbbrev.name(),
48        calc_name_offset(section_names),
49        dwarf.debug_abbrev.take(),
50    );
51    section_names.push(SectionId::DebugAbbrev.name().to_string());
52    abbrev.set_offset(*current_offset);
53    *current_offset += abbrev.size();
54    sections.push(abbrev);
55
56    // .debug_info section
57    let mut info = DebugSection::new(
58        SectionId::DebugInfo.name(),
59        calc_name_offset(section_names),
60        dwarf.debug_info.take(),
61    );
62    section_names.push(SectionId::DebugInfo.name().to_string());
63    info.set_offset(*current_offset);
64    *current_offset += info.size();
65    sections.push(info);
66
67    // .debug_line section
68    let mut line = DebugSection::new(
69        SectionId::DebugLine.name(),
70        calc_name_offset(section_names),
71        dwarf.debug_line.take(),
72    );
73    section_names.push(SectionId::DebugLine.name().to_string());
74    line.set_offset(*current_offset);
75    *current_offset += line.size();
76    sections.push(line);
77
78    // .debug_line_str section
79    let mut line_str = DebugSection::new(
80        SectionId::DebugLineStr.name(),
81        calc_name_offset(section_names),
82        dwarf.debug_line_str.take(),
83    );
84    section_names.push(SectionId::DebugLineStr.name().to_string());
85    line_str.set_offset(*current_offset);
86    *current_offset += line_str.size();
87    sections.push(line_str);
88
89    sections
90}
91
92// Generate DWARF sections using gimli
93fn generate_dwarf_sections(
94    data: &DebugData,
95    text_offset: u64,
96    code_start: u64,
97    code_end: u64,
98) -> Sections<EndianVec<LittleEndian>> {
99    let encoding = Encoding {
100        format: Format::Dwarf32,
101        version: 5,
102        address_size: 8,
103    };
104
105    let line_encoding = LineEncoding {
106        minimum_instruction_length: SBPF_INSTRUCTION_LENGTH,
107        ..LineEncoding::default()
108    };
109
110    let mut dwarf = DwarfUnit::new(encoding);
111
112    // Add strings.
113    let dir_string_id = dwarf.line_strings.add(data.directory.clone().into_bytes());
114    let file_string_id = dwarf.line_strings.add(data.filename.clone().into_bytes());
115
116    // Create line program.
117    let mut line_program = LineProgram::new(
118        encoding,
119        line_encoding,
120        LineString::LineStringRef(dir_string_id),
121        None,
122        LineString::LineStringRef(file_string_id),
123        None,
124    );
125
126    let dir_id = line_program.default_directory();
127    let file_id = line_program.add_file(LineString::LineStringRef(file_string_id), dir_id, None);
128
129    // Add line entries.
130    line_program.begin_sequence(Some(Address::Constant(code_start)));
131    for (address, line) in &data.lines {
132        let adjusted_addr = address + text_offset;
133        line_program.row().file = file_id;
134        line_program.row().address_offset = adjusted_addr - code_start;
135        line_program.row().line = *line as u64;
136        line_program.generate_row();
137    }
138    line_program.end_sequence(code_end);
139
140    dwarf.unit.line_program = line_program;
141
142    // Set compile unit attributes.
143    let root_id = dwarf.unit.root();
144    let root = dwarf.unit.get_mut(root_id);
145
146    root.set(
147        DW_AT_name,
148        AttributeValue::String(data.filename.clone().into_bytes()),
149    );
150    root.set(
151        DW_AT_comp_dir,
152        AttributeValue::String(data.directory.clone().into_bytes()),
153    );
154    root.set(
155        DW_AT_producer,
156        AttributeValue::String(b"sbpf-assembler".to_vec()),
157    );
158    root.set(
159        DW_AT_language,
160        AttributeValue::Language(DW_LANG_Mips_Assembler),
161    );
162    root.set(
163        DW_AT_low_pc,
164        AttributeValue::Address(Address::Constant(code_start)),
165    );
166    root.set(
167        DW_AT_high_pc,
168        AttributeValue::Address(Address::Constant(code_end)),
169    );
170    root.set(DW_AT_stmt_list, AttributeValue::LineProgramRef);
171
172    // Set labels.
173    for (name, address, line) in &data.labels {
174        let adjusted_addr = address + text_offset;
175        let label_id = dwarf.unit.add(root_id, DW_TAG_label);
176        let label_die = dwarf.unit.get_mut(label_id);
177
178        label_die.set(
179            DW_AT_name,
180            AttributeValue::String(name.clone().into_bytes()),
181        );
182        label_die.set(DW_AT_decl_file, AttributeValue::Data4(0));
183        label_die.set(DW_AT_decl_line, AttributeValue::Data4(*line));
184        label_die.set(
185            DW_AT_low_pc,
186            AttributeValue::Address(Address::Constant(adjusted_addr)),
187        );
188    }
189
190    // Write sections.
191    let mut sections = Sections::new(EndianVec::new(LittleEndian));
192    dwarf.write(&mut sections).expect("Failed to write DWARF");
193    sections
194}
195
196/// Reuse debug sections we came across while byteparsing
197pub fn reuse_debug_sections(
198    parsed_debug_sections: Vec<DebugSection>,
199    section_names: &mut Vec<String>,
200    current_offset: &mut u64,
201) -> Vec<SectionType> {
202    // reuse debug sections that came from byteparsing
203    let mut sections = Vec::default();
204    for mut debug_section in parsed_debug_sections.into_iter() {
205        debug_section.set_name_offset(calc_name_offset(section_names));
206        section_names.push(debug_section.name().to_string());
207        debug_section.set_offset(*current_offset);
208        *current_offset += debug_section.size();
209        if debug_section.name() == SectionId::DebugAbbrev.name() {
210            sections.push(SectionType::DebugAbbrev(debug_section));
211        } else if debug_section.name() == SectionId::DebugInfo.name() {
212            sections.push(SectionType::DebugInfo(debug_section));
213        } else if debug_section.name() == SectionId::DebugLine.name() {
214            sections.push(SectionType::DebugLine(debug_section));
215        } else if debug_section.name() == SectionId::DebugLineStr.name() {
216            sections.push(SectionType::DebugLineStr(debug_section));
217        } else if debug_section.name() == SectionId::DebugStr.name() {
218            sections.push(SectionType::DebugStr(debug_section));
219        } else if debug_section.name() == SectionId::DebugFrame.name() {
220            sections.push(SectionType::DebugFrame(debug_section));
221        } else if debug_section.name() == SectionId::DebugLoc.name() {
222            sections.push(SectionType::DebugLoc(debug_section));
223        } else if debug_section.name() == SectionId::DebugRanges.name() {
224            sections.push(SectionType::DebugRanges(debug_section));
225        } else {
226            eprintln!(
227                "Unimplemented debug section: {}, consider adding it",
228                debug_section.name()
229            );
230            continue;
231        }
232    }
233    sections
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn test_generate_debug_sections() {
242        let mut section_names = vec![".text".to_string()];
243        let mut offset = 100u64;
244
245        let data = DebugData {
246            filename: "test.s".to_string(),
247            directory: "/tmp".to_string(),
248            lines: vec![(0, 5), (8, 6)],
249            labels: vec![("entrypoint".to_string(), 0, 4)],
250            code_start: 0,
251            code_end: 16,
252        };
253
254        let sections = generate_debug_sections(&data, 0x100, &mut section_names, &mut offset);
255
256        assert_eq!(sections.len(), 4);
257        assert_eq!(sections[0].name(), SectionId::DebugAbbrev.name());
258        assert_eq!(sections[1].name(), SectionId::DebugInfo.name());
259        assert_eq!(sections[2].name(), SectionId::DebugLine.name());
260        assert_eq!(sections[3].name(), SectionId::DebugLineStr.name());
261
262        for section in &sections {
263            assert!(!section.bytecode().is_empty());
264        }
265    }
266}