1use {
2 crate::{
3 elf_header::{E_MACHINE, E_MACHINE_SBPF, ELFHeader},
4 errors::DisassemblerError,
5 program_header::ProgramHeader,
6 relocation::Relocation,
7 rodata::RodataSection,
8 section_header::SectionHeader,
9 section_header_entry::SectionHeaderEntry,
10 },
11 either::Either,
12 object::{Endianness, read::elf::ElfFile64},
13 sbpf_common::{inst_param::Number, instruction::Instruction, opcode::Opcode},
14 serde::{Deserialize, Serialize},
15 std::collections::{BTreeSet, HashMap},
16};
17
18pub type DisassembleResult =
19 Result<(Vec<Instruction>, Option<RodataSection>, Option<usize>), DisassemblerError>;
20
21#[derive(Debug, Serialize, Deserialize)]
22pub struct Program {
23 pub elf_header: ELFHeader,
24 pub program_headers: Vec<ProgramHeader>,
25 pub section_headers: Vec<SectionHeader>,
26 pub section_header_entries: Vec<SectionHeaderEntry>,
27 pub relocations: Vec<Relocation>,
28}
29
30impl Program {
31 pub fn from_bytes(b: &[u8]) -> Result<Self, DisassemblerError> {
32 let elf_file = ElfFile64::<Endianness>::parse(b).map_err(|e| {
33 eprintln!("ELF parse error: {}", e);
34 DisassemblerError::NonStandardElfHeader
35 })?;
36
37 let elf_header = ELFHeader::from_elf_file(&elf_file)?;
39
40 let program_headers = ProgramHeader::from_elf_file(&elf_file)?;
42
43 let (section_headers, section_header_entries) = SectionHeader::from_elf_file(&elf_file)?;
45
46 let relocations = Relocation::from_elf_file(&elf_file)?;
48
49 Ok(Self {
50 elf_header,
51 program_headers,
52 section_headers,
53 section_header_entries,
54 relocations,
55 })
56 }
57
58 pub fn to_ixs(self) -> DisassembleResult {
59 self.into_ixs_inner(true)
60 }
61
62 pub fn to_ixs_raw(self) -> DisassembleResult {
63 self.into_ixs_inner(false)
64 }
65
66 fn into_ixs_inner(self, resolve_offsets: bool) -> DisassembleResult {
67 let text_section = self
69 .section_header_entries
70 .iter()
71 .find(|e| e.label.eq(".text\0"))
72 .ok_or(DisassemblerError::MissingTextSection)?;
73 let text_section_offset = text_section.offset as u64;
74
75 let syscall_map = self.build_syscall_map(text_section_offset);
77
78 let data = &text_section.data;
79 if !data.len().is_multiple_of(8) {
80 return Err(DisassemblerError::InvalidDataLength);
81 }
82
83 let is_sbpf_v2 =
84 self.elf_header.e_flags == 0x02 && self.elf_header.e_machine == E_MACHINE_SBPF;
85
86 let rodata_info = self.get_rodata_info();
88 let (rodata_base, rodata_end) = rodata_info
89 .as_ref()
90 .map(|(d, addr)| (*addr, *addr + d.len() as u64))
91 .unwrap_or((0, 0));
92
93 let mut ixs: Vec<Instruction> = Vec::new();
95 let mut idx_to_slot: Vec<usize> = Vec::new();
96 let mut pos: usize = 0;
97 let mut slot: usize = 0;
98
99 while pos < data.len() {
100 let remaining = &data[pos..];
101 if remaining.len() < 8 {
102 break;
103 }
104
105 let mut ix = if is_sbpf_v2 {
107 Instruction::from_bytes_sbpf_v2(remaining)?
108 } else {
109 Instruction::from_bytes(remaining)?
110 };
111
112 if ix.opcode == Opcode::Call
114 && let Some(Either::Right(Number::Int(-1))) = ix.imm
115 && let Some(syscall_name) = syscall_map.get(&(pos as u64))
116 {
117 ix.imm = Some(Either::Left(syscall_name.clone()));
118 }
119
120 idx_to_slot.push(slot);
121
122 if ix.opcode == Opcode::Lddw {
123 pos += 16;
124 slot += 2;
125 } else {
126 pos += 8;
127 slot += 1;
128 }
129
130 ixs.push(ix);
131 }
132
133 let mut slot_to_idx = vec![0usize; slot];
134 for (idx, &slot) in idx_to_slot.iter().enumerate() {
135 slot_to_idx[slot] = idx;
136 }
137
138 let text_sh_addr = self
139 .section_headers
140 .iter()
141 .find(|h| {
142 self.section_header_entries
143 .iter()
144 .any(|e| e.label.eq(".text\0") && e.offset == h.sh_offset as usize)
145 })
146 .map(|h| h.sh_addr)
147 .unwrap_or(0);
148 let text_end_addr = text_sh_addr + text_section.data.len() as u64;
149
150 let mut rodata_refs = BTreeSet::new();
151
152 if resolve_offsets {
153 for (idx, ix) in ixs.iter_mut().enumerate() {
156 let is_lddw = ix.opcode == Opcode::Lddw;
157
158 if ix.is_jump()
160 && let Some(Either::Right(off)) = &ix.off
161 {
162 let current_slot = idx_to_slot[idx] as i64;
163 let target_slot = current_slot + 1 + (*off as i64);
164 if target_slot >= 0
165 && let Some(&target_idx) = slot_to_idx.get(target_slot as usize)
166 {
167 let new_off = target_idx as i64 - (idx as i64 + 1);
168 ix.off = Some(Either::Right(new_off as i16));
169 }
170 }
171
172 if ix.opcode == Opcode::Call
174 && let Some(Either::Right(Number::Int(imm))) = &ix.imm
175 {
176 let current_slot = idx_to_slot[idx] as i64;
177 let target_slot = current_slot + 1 + *imm;
178 if target_slot >= 0
179 && let Some(&target_idx) = slot_to_idx.get(target_slot as usize)
180 {
181 let new_rel = target_idx as i64 - (idx as i64 + 1);
182 ix.imm = Some(Either::Right(Number::Int(new_rel)));
183 }
184 }
185
186 if is_lddw && let Some(Either::Right(Number::Int(imm))) = &ix.imm {
188 let addr = *imm as u64;
189 if rodata_info.is_some() && addr >= rodata_base && addr < rodata_end {
190 rodata_refs.insert(addr);
191 } else if addr >= text_sh_addr && addr < text_end_addr {
192 let byte_offset = addr - text_sh_addr;
194 let target_slot = (byte_offset / 8) as usize;
195 if target_slot < slot_to_idx.len() {
196 let ix_idx = slot_to_idx[target_slot];
197 ix.imm = Some(Either::Right(Number::Int(ix_idx as i64)));
198 }
199 }
200 }
201 }
202 }
203
204 let rodata = if let Some((data, base_addr)) = rodata_info {
206 let mut section = RodataSection::parse(data, base_addr, &rodata_refs);
207 let (data_relocs, text_relocs) = self.classify_relocations(
208 §ion.data,
209 base_addr,
210 text_section_offset,
211 text_section.data.len() as u64,
212 text_sh_addr,
213 &slot_to_idx,
214 );
215 section.data_relocations = data_relocs;
216 section.text_relocations = text_relocs;
217 Some(section)
218 } else {
219 None
220 };
221
222 let entrypoint_idx = self.get_entrypoint_offset().map(|byte_offset| {
224 let entrypoint_slot = (byte_offset / 8) as usize;
225 if entrypoint_slot < slot_to_idx.len() {
226 slot_to_idx[entrypoint_slot]
227 } else {
228 0
229 }
230 });
231
232 Ok((ixs, rodata, entrypoint_idx))
233 }
234
235 fn build_syscall_map(&self, text_section_offset: u64) -> HashMap<u64, String> {
239 self.relocations
240 .iter()
241 .filter(|r| r.is_syscall())
242 .filter_map(|r| {
243 r.symbol_name.as_ref().map(|name| {
244 let relative_pos = r.relative_offset(text_section_offset);
246 (relative_pos, name.clone())
247 })
248 })
249 .collect()
250 }
251
252 fn get_rodata_info(&self) -> Option<(Vec<u8>, u64)> {
254 let rodata_entry = self
255 .section_header_entries
256 .iter()
257 .find(|e| e.label.starts_with(".rodata"))?;
258
259 let vaddr = if self.is_v3() {
262 self.program_headers
263 .iter()
264 .find(|ph| {
265 let rodata_offset = rodata_entry.offset as u64;
266 rodata_offset >= ph.p_offset && rodata_offset < ph.p_offset + ph.p_filesz
267 })
268 .map(|ph| ph.p_vaddr)
269 .unwrap_or(0)
270 } else {
271 let rodata_header = self
272 .section_headers
273 .iter()
274 .find(|h| h.sh_offset as usize == rodata_entry.offset)?;
275 rodata_header.sh_addr
276 };
277
278 if let Some(data_rel_ro_entry) = self
280 .section_header_entries
281 .iter()
282 .find(|e| e.label.starts_with(".data.rel.ro"))
283 {
284 let data_rel_ro_header = self
285 .section_headers
286 .iter()
287 .find(|h| h.sh_offset as usize == data_rel_ro_entry.offset);
288
289 if let Some(drr_header) = data_rel_ro_header {
290 let drr_end = drr_header.sh_addr + drr_header.sh_size;
291 let total_size = (drr_end - vaddr) as usize;
292
293 let mut combined = vec![0u8; total_size];
295
296 let rodata_len = rodata_entry.data.len().min(total_size);
298 combined[..rodata_len].copy_from_slice(&rodata_entry.data[..rodata_len]);
299
300 let drr_offset = (drr_header.sh_addr - vaddr) as usize;
302 let drr_len = data_rel_ro_entry.data.len().min(total_size - drr_offset);
303 combined[drr_offset..drr_offset + drr_len]
304 .copy_from_slice(&data_rel_ro_entry.data[..drr_len]);
305
306 return Some((combined, vaddr));
307 }
308 }
309
310 Some((rodata_entry.data.clone(), vaddr))
311 }
312
313 fn classify_relocations(
315 &self,
316 rodata_data: &[u8],
317 rodata_base: u64,
318 text_offset: u64,
319 text_len: u64,
320 text_sh_addr: u64,
321 slot_to_idx: &[usize],
322 ) -> (Vec<usize>, Vec<(usize, usize)>) {
323 let rodata_len = rodata_data.len();
324 let text_end_addr = text_sh_addr + text_len;
325 let mut data_relocs = Vec::new();
326 let mut text_relocs = Vec::new();
327
328 for r in &self.relocations {
329 if r.rel_type != crate::relocation::RelocationType::R_BPF_64_RELATIVE {
330 continue;
331 }
332 if r.offset >= text_offset && r.offset < text_offset + text_len {
333 continue;
334 }
335 if r.offset < rodata_base || r.offset + 8 > rodata_base + rodata_len as u64 {
336 continue;
337 }
338 let offset_in_blob = (r.offset - rodata_base) as usize;
339 data_relocs.push(offset_in_blob);
340
341 let imm_offset = offset_in_blob + 4;
342 if imm_offset + 4 <= rodata_len {
343 let ptr =
344 u32::from_le_bytes(rodata_data[imm_offset..imm_offset + 4].try_into().unwrap())
345 as u64;
346 if ptr >= text_sh_addr && ptr < text_end_addr {
347 let target_slot = ((ptr - text_sh_addr) / 8) as usize;
348 if target_slot < slot_to_idx.len() {
349 text_relocs.push((offset_in_blob, slot_to_idx[target_slot]));
350 }
351 }
352 }
353 }
354
355 (data_relocs, text_relocs)
356 }
357
358 pub fn get_entrypoint_offset(&self) -> Option<u64> {
360 let e_entry = self.elf_header.e_entry;
361
362 if self.is_v3() {
363 const V3_BYTECODE_VADDR: u64 = 1 << 32;
364 if e_entry >= V3_BYTECODE_VADDR {
365 Some(e_entry - V3_BYTECODE_VADDR)
366 } else {
367 None
368 }
369 } else {
370 let text_header = self.section_headers.iter().find(|h| {
371 self.section_header_entries
372 .iter()
373 .any(|e| e.label.eq(".text\0") && e.offset == h.sh_offset as usize)
374 })?;
375 let text_sh_addr = text_header.sh_addr;
376
377 if e_entry >= text_sh_addr {
378 Some(e_entry - text_sh_addr)
379 } else {
380 None
381 }
382 }
383 }
384
385 fn is_v3(&self) -> bool {
386 self.elf_header.e_flags == 0x03 && self.elf_header.e_machine == E_MACHINE
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use {
393 crate::{
394 elf_header::{E_MACHINE_SBPF, ELFHeader},
395 program::Program,
396 section_header_entry::SectionHeaderEntry,
397 },
398 hex_literal::hex,
399 };
400
401 #[test]
402 fn try_deserialize_program() {
403 let program = Program::from_bytes(&hex!("7F454C460201010000000000000000000300F700010000002001000000000000400000000000000028020000000000000000000040003800030040000600050001000000050000002001000000000000200100000000000020010000000000003000000000000000300000000000000000100000000000000100000004000000C001000000000000C001000000000000C0010000000000003C000000000000003C000000000000000010000000000000020000000600000050010000000000005001000000000000500100000000000070000000000000007000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007912A000000000007911182900000000B7000000010000002D21010000000000B70000000000000095000000000000001E0000000000000004000000000000000600000000000000C0010000000000000B0000000000000018000000000000000500000000000000F0010000000000000A000000000000000C00000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000120001002001000000000000300000000000000000656E747279706F696E7400002E74657874002E64796E737472002E64796E73796D002E64796E616D6963002E73687374727461620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000200100000000000020010000000000003000000000000000000000000000000008000000000000000000000000000000170000000600000003000000000000005001000000000000500100000000000070000000000000000400000000000000080000000000000010000000000000000F0000000B0000000200000000000000C001000000000000C001000000000000300000000000000004000000010000000800000000000000180000000000000007000000030000000200000000000000F001000000000000F0010000000000000C00000000000000000000000000000001000000000000000000000000000000200000000300000000000000000000000000000000000000FC010000000000002A00000000000000000000000000000001000000000000000000000000000000")).unwrap();
404 println!("{:?}", program.section_header_entries);
405 }
406
407 #[test]
408 fn test_to_ixs_invalid_data_length() {
409 let program = Program {
411 elf_header: ELFHeader {
412 ei_magic: [127, 69, 76, 70],
413 ei_class: 2,
414 ei_data: 1,
415 ei_version: 1,
416 ei_osabi: 0,
417 ei_abiversion: 0,
418 ei_pad: [0; 7],
419 e_type: 0,
420 e_machine: 0,
421 e_version: 0,
422 e_entry: 0,
423 e_phoff: 0,
424 e_shoff: 0,
425 e_flags: 0,
426 e_ehsize: 0,
427 e_phentsize: 0,
428 e_phnum: 0,
429 e_shentsize: 0,
430 e_shnum: 0,
431 e_shstrndx: 0,
432 },
433 program_headers: vec![],
434 section_headers: vec![],
435 section_header_entries: vec![
436 SectionHeaderEntry::new(".text\0".to_string(), 0, vec![0x95, 0x00, 0x00]).unwrap(), ],
438 relocations: vec![],
439 };
440
441 let result = program.to_ixs();
442 assert!(result.is_err());
443 assert!(matches!(
444 result.unwrap_err(),
445 crate::errors::DisassemblerError::InvalidDataLength
446 ));
447 }
448
449 #[test]
450 fn test_to_ixs_with_lddw() {
451 let mut lddw_bytes = vec![0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
454 lddw_bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
455 lddw_bytes.extend_from_slice(&[0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); let program = Program {
458 elf_header: ELFHeader {
459 ei_magic: [127, 69, 76, 70],
460 ei_class: 2,
461 ei_data: 1,
462 ei_version: 1,
463 ei_osabi: 0,
464 ei_abiversion: 0,
465 ei_pad: [0; 7],
466 e_type: 0,
467 e_machine: E_MACHINE_SBPF,
468 e_version: 0,
469 e_entry: 0,
470 e_phoff: 0,
471 e_shoff: 0,
472 e_flags: 0,
473 e_ehsize: 0,
474 e_phentsize: 0,
475 e_phnum: 0,
476 e_shentsize: 0,
477 e_shnum: 0,
478 e_shstrndx: 0,
479 },
480 program_headers: vec![],
481 section_headers: vec![],
482 section_header_entries: vec![
483 SectionHeaderEntry::new(".text\0".to_string(), 0, lddw_bytes).unwrap(),
484 ],
485 relocations: vec![],
486 };
487
488 let (ixs, _, _) = program.to_ixs().unwrap();
489 assert_eq!(ixs.len(), 2); assert_eq!(ixs[0].opcode, sbpf_common::opcode::Opcode::Lddw);
491 }
492
493 #[test]
494 fn test_to_ixs_sbpf_v2() {
495 let v2_bytes = vec![0x8c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
497
498 let program = Program {
499 elf_header: ELFHeader {
500 ei_magic: [127, 69, 76, 70],
501 ei_class: 2,
502 ei_data: 1,
503 ei_version: 1,
504 ei_osabi: 0,
505 ei_abiversion: 0,
506 ei_pad: [0; 7],
507 e_type: 0,
508 e_machine: E_MACHINE_SBPF,
509 e_version: 0,
510 e_entry: 0,
511 e_phoff: 0,
512 e_shoff: 0,
513 e_flags: 0x02, e_ehsize: 0,
515 e_phentsize: 0,
516 e_phnum: 0,
517 e_shentsize: 0,
518 e_shnum: 0,
519 e_shstrndx: 0,
520 },
521 program_headers: vec![],
522 section_headers: vec![],
523 section_header_entries: vec![
524 SectionHeaderEntry::new(".text\0".to_string(), 0, v2_bytes).unwrap(),
525 ],
526 relocations: vec![],
527 };
528
529 let (ixs, _, _) = program.to_ixs().unwrap();
530 assert_eq!(ixs.len(), 1);
531 assert_eq!(ixs[0].opcode, sbpf_common::opcode::Opcode::Ldxw);
532 }
533}