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
32pub 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 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 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 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 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
92fn 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 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 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 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 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 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 let mut sections = Sections::new(EndianVec::new(LittleEndian));
192 dwarf.write(&mut sections).expect("Failed to write DWARF");
193 sections
194}
195
196pub 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 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 debug_section.set_offset(*current_offset);
207 let section = if debug_section.name() == SectionId::DebugAbbrev.name() {
208 SectionType::DebugAbbrev(debug_section)
209 } else if debug_section.name() == SectionId::DebugInfo.name() {
210 SectionType::DebugInfo(debug_section)
211 } else if debug_section.name() == SectionId::DebugLine.name() {
212 SectionType::DebugLine(debug_section)
213 } else if debug_section.name() == SectionId::DebugLineStr.name() {
214 SectionType::DebugLineStr(debug_section)
215 } else if debug_section.name() == SectionId::DebugStr.name() {
216 SectionType::DebugStr(debug_section)
217 } else if debug_section.name() == SectionId::DebugFrame.name() {
218 SectionType::DebugFrame(debug_section)
219 } else if debug_section.name() == SectionId::DebugLoc.name() {
220 SectionType::DebugLoc(debug_section)
221 } else if debug_section.name() == SectionId::DebugRanges.name() {
222 SectionType::DebugRanges(debug_section)
223 } else if debug_section.name() == ".debug_str_offsets" {
224 eprintln!(
225 ".debug_str_offsets is not supported by the SVM; the section will be skipped and \
226 omitted from the bytecode."
227 );
228 continue;
229 } else {
230 eprintln!(
231 "Unimplemented debug section: {}, consider adding it",
232 debug_section.name()
233 );
234 continue;
235 };
236
237 section_names.push(section.name().to_string());
238 *current_offset += section.size();
239 sections.push(section);
240 }
241 sections
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_generate_debug_sections() {
250 let mut section_names = vec![".text".to_string()];
251 let mut offset = 100u64;
252
253 let data = DebugData {
254 filename: "test.s".to_string(),
255 directory: "/tmp".to_string(),
256 lines: vec![(0, 5), (8, 6)],
257 labels: vec![("entrypoint".to_string(), 0, 4)],
258 code_start: 0,
259 code_end: 16,
260 };
261
262 let sections = generate_debug_sections(&data, 0x100, &mut section_names, &mut offset);
263
264 assert_eq!(sections.len(), 4);
265 assert_eq!(sections[0].name(), SectionId::DebugAbbrev.name());
266 assert_eq!(sections[1].name(), SectionId::DebugInfo.name());
267 assert_eq!(sections[2].name(), SectionId::DebugLine.name());
268 assert_eq!(sections[3].name(), SectionId::DebugLineStr.name());
269
270 for section in §ions {
271 assert!(!section.bytecode().is_empty());
272 }
273 }
274
275 #[test]
276 fn test_reuse_debug_sections_skips_unknown_without_offset_drift() {
277 let mut section_names = vec![".text".to_string()];
278 let mut offset = 100u64;
279
280 let parsed = vec![
282 DebugSection::new(SectionId::DebugAbbrev.name(), 0, vec![1; 10]),
283 DebugSection::new(".debug_str_offsets", 0, vec![2; 20]), DebugSection::new(SectionId::DebugInfo.name(), 0, vec![3; 30]),
285 ];
286
287 let abbrev_size = DebugSection::new("", 0, vec![0; 10]).size(); let info_size = DebugSection::new("", 0, vec![0; 30]).size(); let result = reuse_debug_sections(parsed, &mut section_names, &mut offset);
291
292 assert_eq!(result.len(), 2, "unknown section should be dropped");
294 assert_eq!(result[0].name(), ".debug_abbrev");
295 assert_eq!(result[1].name(), ".debug_info");
296
297 assert_eq!(
298 result[0].offset(),
299 100,
300 "first section starts at initial offset"
301 );
302 assert_eq!(
303 result[1].offset(),
304 100 + abbrev_size,
305 "second section must follow first with no gap from dropped section"
306 );
307
308 assert_eq!(
309 offset,
310 100 + abbrev_size + info_size,
311 "current_offset must not include dropped section's size"
312 );
313
314 assert!(
315 !section_names.contains(&".debug_str_offsets".to_string()),
316 "dropped section name must not pollute section_names"
317 );
318 }
319}