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::{
14 errors::SBPFError, inst_param::Number, instruction::Instruction, opcode::Opcode,
15 },
16 serde::{Deserialize, Serialize},
17 std::collections::{BTreeSet, HashMap},
18};
19
20#[derive(Debug)]
22#[must_use]
23pub struct Parsed<T> {
24 pub value: T,
25 pub errors: Vec<DisassemblerError>,
26}
27
28impl<T> Parsed<T> {
29 pub fn into_strict(self) -> Result<T, Vec<DisassemblerError>> {
32 if self.errors.is_empty() {
33 Ok(self.value)
34 } else {
35 Err(self.errors)
36 }
37 }
38}
39
40#[derive(Debug)]
41pub struct Disassembly {
42 pub instructions: Vec<Either<Instruction, DisassemblerError>>,
43 pub rodata: Option<RodataSection>,
44 pub entrypoint: Option<usize>,
45}
46
47pub type DisassembleResult = Result<Parsed<Disassembly>, Vec<DisassemblerError>>;
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct Program {
51 pub elf_header: ELFHeader,
52 pub program_headers: Vec<ProgramHeader>,
53 pub section_headers: Vec<SectionHeader>,
54 pub section_header_entries: Vec<SectionHeaderEntry>,
55 pub relocations: Vec<Relocation>,
56}
57
58impl Program {
59 pub fn from_bytes(b: &[u8]) -> Result<Self, Vec<DisassemblerError>> {
60 let mut errors = Vec::new();
61
62 let elf_file = match ElfFile64::<Endianness>::parse(b) {
63 Ok(elf_file) => elf_file,
64 Err(source) => {
65 errors.push(DisassemblerError::InvalidElfFile {
66 first_bytes: b.get(..16).unwrap_or(b).to_vec(),
67 source,
68 });
69 return Err(errors);
71 }
72 };
73
74 let elf_header = ELFHeader::from_elf_file(&elf_file)?;
76
77 let program_headers = ProgramHeader::from_elf_file(&elf_file)?;
79
80 let (section_headers, section_header_entries) = SectionHeader::from_elf_file(&elf_file)?;
82
83 let relocations = Relocation::from_elf_file(&elf_file)?;
85
86 let (section_headers, section_header_entries) = if section_header_entries.is_empty() {
90 Self::synthesize_sections_from_segments(b, &program_headers).map_err(|e| vec![e])?
91 } else {
92 (section_headers, section_header_entries)
93 };
94
95 Ok(Self {
96 elf_header,
97 program_headers,
98 section_headers,
99 section_header_entries,
100 relocations,
101 })
102 }
103
104 fn synthesize_sections_from_segments(
111 data: &[u8],
112 program_headers: &[ProgramHeader],
113 ) -> Result<(Vec<SectionHeader>, Vec<SectionHeaderEntry>), DisassemblerError> {
114 use crate::{
115 program_header::{PF_X, ProgramType},
116 section_header::SectionHeaderType,
117 };
118
119 let segment_bytes = |offset: u64, size: u64| -> Vec<u8> {
120 let start = offset as usize;
121 let end = start.saturating_add(size as usize).min(data.len());
122 if start >= data.len() {
123 Vec::new()
124 } else {
125 data[start..end].to_vec()
126 }
127 };
128
129 let make_header = |offset: u64, size: u64, executable: bool| SectionHeader {
130 sh_name: 0,
131 sh_type: SectionHeaderType::SHT_PROGBITS,
132 sh_flags: if executable { 0x6 } else { 0x2 }, sh_addr: offset,
134 sh_offset: offset,
135 sh_size: size,
136 sh_link: 0,
137 sh_info: 0,
138 sh_addralign: if executable { 8 } else { 1 },
139 sh_entsize: 0,
140 };
141
142 let mut headers = Vec::new();
143 let mut entries = Vec::new();
144
145 let is_load = |ph: &&ProgramHeader| matches!(ph.p_type, ProgramType::PT_LOAD);
146 let is_exec = |ph: &ProgramHeader| ph.p_flags.0 & PF_X as u32 == PF_X as u32;
147
148 if let Some(ph) = program_headers
150 .iter()
151 .filter(is_load)
152 .find(|ph| !is_exec(ph))
153 {
154 headers.push(make_header(ph.p_offset, ph.p_filesz, false));
155 entries.push(SectionHeaderEntry::new(
156 ".rodata\0".to_string(),
157 ph.p_offset as usize,
158 segment_bytes(ph.p_offset, ph.p_filesz),
159 )?);
160 }
161
162 if let Some(ph) = program_headers
164 .iter()
165 .filter(is_load)
166 .find(|ph| is_exec(ph))
167 {
168 headers.push(make_header(ph.p_offset, ph.p_filesz, true));
169 entries.push(SectionHeaderEntry::new(
170 ".text\0".to_string(),
171 ph.p_offset as usize,
172 segment_bytes(ph.p_offset, ph.p_filesz),
173 )?);
174 }
175
176 Ok((headers, entries))
177 }
178
179 pub fn to_ixs(self) -> DisassembleResult {
180 self.into_ixs_inner(true)
181 }
182
183 pub fn to_ixs_raw(self) -> DisassembleResult {
184 self.into_ixs_inner(false)
185 }
186
187 fn into_ixs_inner(self, resolve_offsets: bool) -> DisassembleResult {
188 let text_section = self
190 .section_header_entries
191 .iter()
192 .find(|e| e.label.eq(".text\0"))
193 .ok_or_else(|| {
194 vec![DisassemblerError::MissingTextSection {
195 sections: self
196 .section_header_entries
197 .iter()
198 .map(|e| e.label.trim_end_matches('\0').to_string())
199 .collect(),
200 }]
201 })?;
202
203 let mut errors = Vec::new();
204
205 let text_section_offset = text_section.offset as u64;
206
207 let syscall_map = self.build_syscall_map(text_section_offset);
209
210 let data = &text_section.data;
211 if !data.len().is_multiple_of(8) {
212 errors.push(DisassemblerError::InvalidDataLength(data.len()));
213 }
214
215 let is_sbpf_v2 =
216 self.elf_header.e_flags == 0x02 && self.elf_header.e_machine == E_MACHINE_SBPF;
217 let is_sbpf_v3 = self.elf_header.e_flags == 0x03 && self.elf_header.e_machine == E_MACHINE;
218
219 let rodata_info = self.get_rodata_info();
221 let (rodata_base, rodata_end) = rodata_info
222 .as_ref()
223 .map(|(d, addr)| (*addr, *addr + d.len() as u64))
224 .unwrap_or((0, 0));
225
226 let mut ixs: Vec<Either<Instruction, DisassemblerError>> = Vec::new();
228 let mut idx_to_slot: Vec<usize> = Vec::new();
229 let mut pos: usize = 0;
230 let mut slot: usize = 0;
231
232 while pos < data.len() {
233 let remaining = &data[pos..];
234 if remaining.len() < 8 {
235 break;
236 }
237
238 let decoded = if remaining.len() < 16 && remaining[0] == 0x18 {
242 Err(SBPFError::BytecodeError {
243 error: format!("lddw needs 16 bytes but only {} remain", remaining.len()),
244 span: 0..8, custom_label: None,
246 })
247 } else if is_sbpf_v2 {
248 Instruction::from_bytes_sbpf_v2(remaining)
250 } else if is_sbpf_v3 {
251 Instruction::from_bytes_sbpf_v3(remaining)
252 } else {
253 Instruction::from_bytes(remaining)
254 };
255
256 let mut ix = match decoded {
257 Ok(ix) => ix,
258 Err(e) => {
262 let e = match e {
265 SBPFError::BytecodeError { error, span, .. } => {
266 DisassemblerError::BytecodeError {
267 error,
268 span: span.start + pos..span.end + pos,
269 }
270 }
271 };
272 errors.push(e.clone());
273 idx_to_slot.push(slot);
274 ixs.push(Either::Right(e));
275 pos += 8;
276 slot += 1;
277 continue;
278 }
279 };
280
281 if ix.opcode == Opcode::Call
283 && let Some(Either::Right(Number::Int(-1))) = ix.imm
284 && let Some(syscall_name) = syscall_map.get(&(pos as u64))
285 {
286 ix.imm = Some(Either::Left(syscall_name.clone()));
287 }
288
289 idx_to_slot.push(slot);
290
291 if ix.opcode == Opcode::Lddw {
292 pos += 16;
293 slot += 2;
294 } else {
295 pos += 8;
296 slot += 1;
297 }
298
299 ixs.push(Either::Left(ix));
300 }
301
302 let mut slot_to_idx = vec![0usize; slot];
303 for (idx, &slot) in idx_to_slot.iter().enumerate() {
304 slot_to_idx[slot] = idx;
305 }
306
307 let text_sh_addr = self
308 .section_headers
309 .iter()
310 .find(|h| {
311 self.section_header_entries
312 .iter()
313 .any(|e| e.label.eq(".text\0") && e.offset == h.sh_offset as usize)
314 })
315 .map(|h| h.sh_addr)
316 .unwrap_or(0);
317 let text_end_addr = text_sh_addr + text_section.data.len() as u64;
318
319 let mut rodata_refs = BTreeSet::new();
320
321 if resolve_offsets {
322 for (idx, ix) in ixs.iter_mut().enumerate() {
325 let Either::Left(ix) = ix else { continue };
326 let is_lddw = ix.opcode == Opcode::Lddw;
327
328 if ix.is_jump()
330 && let Some(Either::Right(off)) = &ix.off
331 {
332 let current_slot = idx_to_slot[idx] as i64;
333 let target_slot = current_slot + 1 + (*off as i64);
334 if target_slot >= 0
335 && let Some(&target_idx) = slot_to_idx.get(target_slot as usize)
336 {
337 let new_off = target_idx as i64 - (idx as i64 + 1);
338 ix.off = Some(Either::Right(new_off as i16));
339 }
340 }
341
342 if ix.opcode == Opcode::Call
344 && let Some(Either::Right(Number::Int(imm))) = &ix.imm
345 {
346 let current_slot = idx_to_slot[idx] as i64;
347 let target_slot = current_slot + 1 + *imm;
348 if target_slot >= 0
349 && let Some(&target_idx) = slot_to_idx.get(target_slot as usize)
350 {
351 let new_rel = target_idx as i64 - (idx as i64 + 1);
352 ix.imm = Some(Either::Right(Number::Int(new_rel)));
353 }
354 }
355
356 if is_lddw && let Some(Either::Right(Number::Int(imm))) = &ix.imm {
358 let addr = *imm as u64;
359 if rodata_info.is_some() && addr >= rodata_base && addr < rodata_end {
360 rodata_refs.insert(addr);
361 } else if addr >= text_sh_addr && addr < text_end_addr {
362 let byte_offset = addr - text_sh_addr;
364 let target_slot = (byte_offset / 8) as usize;
365 if target_slot < slot_to_idx.len() {
366 let ix_idx = slot_to_idx[target_slot];
367 ix.imm = Some(Either::Right(Number::Int(ix_idx as i64)));
368 }
369 }
370 }
371 }
372 }
373
374 let rodata = if let Some((data, base_addr)) = rodata_info {
376 let mut section = RodataSection::parse(data, base_addr, &rodata_refs);
377 let (data_relocs, text_relocs) = self.classify_relocations(
378 §ion.data,
379 base_addr,
380 text_section_offset,
381 text_section.data.len() as u64,
382 text_sh_addr,
383 &slot_to_idx,
384 );
385 section.data_relocations = data_relocs;
386 section.text_relocations = text_relocs;
387 Some(section)
388 } else {
389 None
390 };
391
392 let entrypoint_idx = self.get_entrypoint_offset().map(|byte_offset| {
394 let entrypoint_slot = (byte_offset / 8) as usize;
395 if entrypoint_slot < slot_to_idx.len() {
396 slot_to_idx[entrypoint_slot]
397 } else {
398 0
399 }
400 });
401
402 Ok(Parsed {
403 value: Disassembly {
404 instructions: ixs,
405 rodata,
406 entrypoint: entrypoint_idx,
407 },
408 errors,
409 })
410 }
411
412 fn build_syscall_map(&self, text_section_offset: u64) -> HashMap<u64, String> {
416 self.relocations
417 .iter()
418 .filter(|r| r.is_syscall())
419 .filter_map(|r| {
420 r.symbol_name.as_ref().map(|name| {
421 let relative_pos = r.relative_offset(text_section_offset);
423 (relative_pos, name.clone())
424 })
425 })
426 .collect()
427 }
428
429 fn get_rodata_info(&self) -> Option<(Vec<u8>, u64)> {
431 let rodata_entry = self
432 .section_header_entries
433 .iter()
434 .find(|e| e.label.starts_with(".rodata"))?;
435
436 let vaddr = if self.is_v3() {
439 self.program_headers
440 .iter()
441 .find(|ph| {
442 let rodata_offset = rodata_entry.offset as u64;
443 rodata_offset >= ph.p_offset && rodata_offset < ph.p_offset + ph.p_filesz
444 })
445 .map(|ph| ph.p_vaddr)
446 .unwrap_or(0)
447 } else {
448 let rodata_header = self
449 .section_headers
450 .iter()
451 .find(|h| h.sh_offset as usize == rodata_entry.offset)?;
452 rodata_header.sh_addr
453 };
454
455 if let Some(data_rel_ro_entry) = self
457 .section_header_entries
458 .iter()
459 .find(|e| e.label.starts_with(".data.rel.ro"))
460 {
461 let data_rel_ro_header = self
462 .section_headers
463 .iter()
464 .find(|h| h.sh_offset as usize == data_rel_ro_entry.offset);
465
466 if let Some(drr_header) = data_rel_ro_header {
467 let drr_end = drr_header.sh_addr + drr_header.sh_size;
468 let total_size = (drr_end - vaddr) as usize;
469
470 let mut combined = vec![0u8; total_size];
472
473 let rodata_len = rodata_entry.data.len().min(total_size);
475 combined[..rodata_len].copy_from_slice(&rodata_entry.data[..rodata_len]);
476
477 let drr_offset = (drr_header.sh_addr - vaddr) as usize;
479 let drr_len = data_rel_ro_entry.data.len().min(total_size - drr_offset);
480 combined[drr_offset..drr_offset + drr_len]
481 .copy_from_slice(&data_rel_ro_entry.data[..drr_len]);
482
483 return Some((combined, vaddr));
484 }
485 }
486
487 Some((rodata_entry.data.clone(), vaddr))
488 }
489
490 fn classify_relocations(
492 &self,
493 rodata_data: &[u8],
494 rodata_base: u64,
495 text_offset: u64,
496 text_len: u64,
497 text_sh_addr: u64,
498 slot_to_idx: &[usize],
499 ) -> (Vec<usize>, Vec<(usize, usize)>) {
500 let rodata_len = rodata_data.len();
501 let text_end_addr = text_sh_addr + text_len;
502 let mut data_relocs = Vec::new();
503 let mut text_relocs = Vec::new();
504
505 for r in &self.relocations {
506 if r.rel_type != crate::relocation::RelocationType::R_BPF_64_RELATIVE {
507 continue;
508 }
509 if r.offset >= text_offset && r.offset < text_offset + text_len {
510 continue;
511 }
512 if r.offset < rodata_base || r.offset + 8 > rodata_base + rodata_len as u64 {
513 continue;
514 }
515 let offset_in_blob = (r.offset - rodata_base) as usize;
516 data_relocs.push(offset_in_blob);
517
518 let imm_offset = offset_in_blob + 4;
519 if imm_offset + 4 <= rodata_len {
520 let ptr =
521 u32::from_le_bytes(rodata_data[imm_offset..imm_offset + 4].try_into().unwrap())
522 as u64;
523 if ptr >= text_sh_addr && ptr < text_end_addr {
524 let target_slot = ((ptr - text_sh_addr) / 8) as usize;
525 if target_slot < slot_to_idx.len() {
526 text_relocs.push((offset_in_blob, slot_to_idx[target_slot]));
527 }
528 }
529 }
530 }
531
532 (data_relocs, text_relocs)
533 }
534
535 pub fn get_entrypoint_offset(&self) -> Option<u64> {
537 let e_entry = self.elf_header.e_entry;
538
539 if self.is_v3() {
540 const V3_BYTECODE_VADDR: u64 = 1 << 32;
541 if e_entry >= V3_BYTECODE_VADDR {
542 Some(e_entry - V3_BYTECODE_VADDR)
543 } else {
544 None
545 }
546 } else {
547 let text_header = self.section_headers.iter().find(|h| {
548 self.section_header_entries
549 .iter()
550 .any(|e| e.label.eq(".text\0") && e.offset == h.sh_offset as usize)
551 })?;
552 let text_sh_addr = text_header.sh_addr;
553
554 if e_entry >= text_sh_addr {
555 Some(e_entry - text_sh_addr)
556 } else {
557 None
558 }
559 }
560 }
561
562 fn is_v3(&self) -> bool {
563 self.elf_header.e_flags == 0x03 && self.elf_header.e_machine == E_MACHINE
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use {
570 crate::{
571 elf_header::{E_MACHINE, E_MACHINE_SBPF, EI_OSABI_LINUX, ELFHeader},
572 program::Program,
573 section_header_entry::SectionHeaderEntry,
574 },
575 hex_literal::hex,
576 };
577
578 #[test]
579 fn try_deserialize_program() {
580 let mut bytes = hex!("7F454C460201010000000000000000000300F700010000002001000000000000400000000000000028020000000000000000000040003800030040000600050001000000050000002001000000000000200100000000000020010000000000003000000000000000300000000000000000100000000000000100000004000000C001000000000000C001000000000000C0010000000000003C000000000000003C000000000000000010000000000000020000000600000050010000000000005001000000000000500100000000000070000000000000007000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007912A000000000007911182900000000B7000000010000002D21010000000000B70000000000000095000000000000001E0000000000000004000000000000000600000000000000C0010000000000000B0000000000000018000000000000000500000000000000F0010000000000000A000000000000000C00000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000120001002001000000000000300000000000000000656E747279706F696E7400002E74657874002E64796E737472002E64796E73796D002E64796E616D6963002E73687374727461620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000200100000000000020010000000000003000000000000000000000000000000008000000000000000000000000000000170000000600000003000000000000005001000000000000500100000000000070000000000000000400000000000000080000000000000010000000000000000F0000000B0000000200000000000000C001000000000000C001000000000000300000000000000004000000010000000800000000000000180000000000000007000000030000000200000000000000F001000000000000F0010000000000000C00000000000000000000000000000001000000000000000000000000000000200000000300000000000000000000000000000000000000FC010000000000002A00000000000000000000000000000001000000000000000000000000000000").to_vec();
581 let program = Program::from_bytes(&bytes).unwrap();
582 println!("{:?}", program.section_header_entries);
583
584 bytes[7] = EI_OSABI_LINUX;
585 let program = Program::from_bytes(&bytes).unwrap();
586 assert_eq!(program.elf_header.ei_osabi, EI_OSABI_LINUX);
587
588 bytes[18] = 0x00;
592 let errors = Program::from_bytes(&bytes).unwrap_err();
593 match errors.as_slice() {
594 [
595 crate::errors::DisassemblerError::NonStandardElfHeader {
596 field,
597 expected,
598 found,
599 },
600 ] => {
601 assert_eq!(*field, "machine");
602 assert_eq!(*expected, vec![E_MACHINE as u64, E_MACHINE_SBPF as u64]);
603 assert_eq!(*found, 0);
604 }
605 other => panic!("expected NonStandardElfHeader for machine, got {other:?}"),
606 }
607 }
608
609 #[test]
610 fn test_from_bytes_reports_all_header_errors() {
611 let mut bytes = hex!("7F454C460201010000000000000000000300F700010000002001000000000000400000000000000028020000000000000000000040003800030040000600050001000000050000002001000000000000200100000000000020010000000000003000000000000000300000000000000000100000000000000100000004000000C001000000000000C001000000000000C0010000000000003C000000000000003C000000000000000010000000000000020000000600000050010000000000005001000000000000500100000000000070000000000000007000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007912A000000000007911182900000000B7000000010000002D21010000000000B70000000000000095000000000000001E0000000000000004000000000000000600000000000000C0010000000000000B0000000000000018000000000000000500000000000000F0010000000000000A000000000000000C00000000000000160000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000120001002001000000000000300000000000000000656E747279706F696E7400002E74657874002E64796E737472002E64796E73796D002E64796E616D6963002E73687374727461620000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010000000600000000000000200100000000000020010000000000003000000000000000000000000000000008000000000000000000000000000000170000000600000003000000000000005001000000000000500100000000000070000000000000000400000000000000080000000000000010000000000000000F0000000B0000000200000000000000C001000000000000C001000000000000300000000000000004000000010000000800000000000000180000000000000007000000030000000200000000000000F001000000000000F0010000000000000C00000000000000000000000000000001000000000000000000000000000000200000000300000000000000000000000000000000000000FC010000000000002A00000000000000000000000000000001000000000000000000000000000000").to_vec();
612
613 bytes[7] = 0x05;
617 bytes[20] = 0x02;
618
619 let errors = Program::from_bytes(&bytes).unwrap_err();
620 let fields: Vec<&str> = errors
621 .iter()
622 .map(|e| match e {
623 crate::errors::DisassemblerError::NonStandardElfHeader { field, .. } => *field,
624 other => panic!("expected NonStandardElfHeader, got {other:?}"),
625 })
626 .collect();
627 assert_eq!(fields, vec!["os abi", "version"]);
628 }
629
630 #[test]
631 fn test_to_ixs_invalid_data_length() {
632 let program = Program {
634 elf_header: ELFHeader {
635 ei_magic: [127, 69, 76, 70],
636 ei_class: 2,
637 ei_data: 1,
638 ei_version: 1,
639 ei_osabi: 0,
640 ei_abiversion: 0,
641 ei_pad: [0; 7],
642 e_type: 0,
643 e_machine: 0,
644 e_version: 0,
645 e_entry: 0,
646 e_phoff: 0,
647 e_shoff: 0,
648 e_flags: 0,
649 e_ehsize: 0,
650 e_phentsize: 0,
651 e_phnum: 0,
652 e_shentsize: 0,
653 e_shnum: 0,
654 e_shstrndx: 0,
655 },
656 program_headers: vec![],
657 section_headers: vec![],
658 section_header_entries: vec![
659 SectionHeaderEntry::new(".text\0".to_string(), 0, vec![0x95, 0x00, 0x00]).unwrap(), ],
661 relocations: vec![],
662 };
663
664 let parsed = program.to_ixs().unwrap();
665 assert!(parsed.value.instructions.is_empty());
666 assert!(matches!(
667 parsed.errors.as_slice(),
668 [crate::errors::DisassemblerError::InvalidDataLength(3)]
669 ));
670 }
671
672 #[test]
673 fn test_to_ixs_with_lddw() {
674 let mut lddw_bytes = vec![0x18, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
677 lddw_bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
678 lddw_bytes.extend_from_slice(&[0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); let program = Program {
681 elf_header: ELFHeader {
682 ei_magic: [127, 69, 76, 70],
683 ei_class: 2,
684 ei_data: 1,
685 ei_version: 1,
686 ei_osabi: 0,
687 ei_abiversion: 0,
688 ei_pad: [0; 7],
689 e_type: 0,
690 e_machine: E_MACHINE_SBPF,
691 e_version: 0,
692 e_entry: 0,
693 e_phoff: 0,
694 e_shoff: 0,
695 e_flags: 0,
696 e_ehsize: 0,
697 e_phentsize: 0,
698 e_phnum: 0,
699 e_shentsize: 0,
700 e_shnum: 0,
701 e_shstrndx: 0,
702 },
703 program_headers: vec![],
704 section_headers: vec![],
705 section_header_entries: vec![
706 SectionHeaderEntry::new(".text\0".to_string(), 0, lddw_bytes).unwrap(),
707 ],
708 relocations: vec![],
709 };
710
711 let parsed = program.to_ixs().unwrap();
712 assert!(parsed.errors.is_empty());
713 let ixs = parsed.value.instructions;
714 assert_eq!(ixs.len(), 2); assert_eq!(
716 ixs[0].as_ref().unwrap_left().opcode,
717 sbpf_common::opcode::Opcode::Lddw
718 );
719 }
720
721 #[test]
722 fn test_to_ixs_sbpf_v2() {
723 let v2_bytes = vec![0x8c, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
725
726 let program = Program {
727 elf_header: ELFHeader {
728 ei_magic: [127, 69, 76, 70],
729 ei_class: 2,
730 ei_data: 1,
731 ei_version: 1,
732 ei_osabi: 0,
733 ei_abiversion: 0,
734 ei_pad: [0; 7],
735 e_type: 0,
736 e_machine: E_MACHINE_SBPF,
737 e_version: 0,
738 e_entry: 0,
739 e_phoff: 0,
740 e_shoff: 0,
741 e_flags: 0x02, e_ehsize: 0,
743 e_phentsize: 0,
744 e_phnum: 0,
745 e_shentsize: 0,
746 e_shnum: 0,
747 e_shstrndx: 0,
748 },
749 program_headers: vec![],
750 section_headers: vec![],
751 section_header_entries: vec![
752 SectionHeaderEntry::new(".text\0".to_string(), 0, v2_bytes).unwrap(),
753 ],
754 relocations: vec![],
755 };
756
757 let parsed = program.to_ixs().unwrap();
758 assert!(parsed.errors.is_empty());
759 let ixs = parsed.value.instructions;
760 assert_eq!(ixs.len(), 1);
761 assert_eq!(
762 ixs[0].as_ref().unwrap_left().opcode,
763 sbpf_common::opcode::Opcode::Ldxw
764 );
765 }
766
767 #[test]
768 fn test_to_ixs_sbpf_v3() {
769 let v3_bytes = vec![0x46, 0x01, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00];
770
771 let program = Program {
772 elf_header: ELFHeader {
773 ei_magic: [127, 69, 76, 70],
774 ei_class: 2,
775 ei_data: 1,
776 ei_version: 1,
777 ei_osabi: 0,
778 ei_abiversion: 0,
779 ei_pad: [0; 7],
780 e_type: 0,
781 e_machine: E_MACHINE,
782 e_version: 0,
783 e_entry: 0,
784 e_phoff: 0,
785 e_shoff: 0,
786 e_flags: 0x03, e_ehsize: 0,
788 e_phentsize: 0,
789 e_phnum: 0,
790 e_shentsize: 0,
791 e_shnum: 0,
792 e_shstrndx: 0,
793 },
794 program_headers: vec![],
795 section_headers: vec![],
796 section_header_entries: vec![
797 SectionHeaderEntry::new(".text\0".to_string(), 0, v3_bytes).unwrap(),
798 ],
799 relocations: vec![],
800 };
801
802 let parsed = program.to_ixs().unwrap();
803 assert!(parsed.errors.is_empty());
804 let ixs = parsed.value.instructions;
805 assert_eq!(ixs.len(), 1);
806 assert_eq!(
807 ixs[0].as_ref().unwrap_left().opcode,
808 sbpf_common::opcode::Opcode::Jset32Imm
809 );
810 }
811
812 #[test]
813 fn test_to_ixs_skips_undecodable_instruction() {
814 let mut text = vec![0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
818 text.extend_from_slice(&[0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
819
820 let program = Program {
821 elf_header: ELFHeader {
822 ei_magic: [127, 69, 76, 70],
823 ei_class: 2,
824 ei_data: 1,
825 ei_version: 1,
826 ei_osabi: 0,
827 ei_abiversion: 0,
828 ei_pad: [0; 7],
829 e_type: 0,
830 e_machine: E_MACHINE_SBPF,
831 e_version: 0,
832 e_entry: 0,
833 e_phoff: 0,
834 e_shoff: 0,
835 e_flags: 0,
836 e_ehsize: 0,
837 e_phentsize: 0,
838 e_phnum: 0,
839 e_shentsize: 0,
840 e_shnum: 0,
841 e_shstrndx: 0,
842 },
843 program_headers: vec![],
844 section_headers: vec![],
845 section_header_entries: vec![
846 SectionHeaderEntry::new(".text\0".to_string(), 0, text).unwrap(),
847 ],
848 relocations: vec![],
849 };
850
851 let parsed = program.to_ixs().unwrap();
852 assert_eq!(parsed.value.instructions.len(), 2);
853 match &parsed.value.instructions[0] {
854 either::Either::Right(crate::errors::DisassemblerError::BytecodeError {
855 span, ..
856 }) => assert_eq!(span.start, 0),
857 other => panic!("expected inline BytecodeError, got {other:?}"),
858 }
859 assert_eq!(
860 parsed.value.instructions[1].as_ref().unwrap_left().opcode,
861 sbpf_common::opcode::Opcode::Exit
862 );
863 match parsed.errors.as_slice() {
864 [crate::errors::DisassemblerError::BytecodeError { span, .. }] => {
865 assert_eq!(span.start, 0);
866 }
867 other => panic!("expected one BytecodeError, got {other:?}"),
868 }
869 }
870
871 #[test]
872 fn test_to_ixs_reports_truncated_lddw() {
873 let mut text = vec![0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
876 text.extend_from_slice(&[0x18, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00]);
877
878 let program = Program {
879 elf_header: ELFHeader {
880 ei_magic: [127, 69, 76, 70],
881 ei_class: 2,
882 ei_data: 1,
883 ei_version: 1,
884 ei_osabi: 0,
885 ei_abiversion: 0,
886 ei_pad: [0; 7],
887 e_type: 0,
888 e_machine: E_MACHINE_SBPF,
889 e_version: 0,
890 e_entry: 0,
891 e_phoff: 0,
892 e_shoff: 0,
893 e_flags: 0,
894 e_ehsize: 0,
895 e_phentsize: 0,
896 e_phnum: 0,
897 e_shentsize: 0,
898 e_shnum: 0,
899 e_shstrndx: 0,
900 },
901 program_headers: vec![],
902 section_headers: vec![],
903 section_header_entries: vec![
904 SectionHeaderEntry::new(".text\0".to_string(), 0, text).unwrap(),
905 ],
906 relocations: vec![],
907 };
908
909 let parsed = program.to_ixs().unwrap();
910 assert_eq!(parsed.value.instructions.len(), 2);
911 assert_eq!(
912 parsed.value.instructions[0].as_ref().unwrap_left().opcode,
913 sbpf_common::opcode::Opcode::Exit
914 );
915 assert!(parsed.value.instructions[1].is_right());
916 match parsed.errors.as_slice() {
917 [crate::errors::DisassemblerError::BytecodeError { error, span }] => {
918 assert_eq!(*span, (8..16));
919 assert_eq!(error, "lddw needs 16 bytes but only 8 remain");
920 }
921 other => panic!("expected one BytecodeError, got {other:?}"),
922 }
923 }
924
925 #[test]
926 fn test_to_ixs_decodes_words_before_trailing_bytes() {
927 let text = vec![
930 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
931 ];
932
933 let program = Program {
934 elf_header: ELFHeader {
935 ei_magic: [127, 69, 76, 70],
936 ei_class: 2,
937 ei_data: 1,
938 ei_version: 1,
939 ei_osabi: 0,
940 ei_abiversion: 0,
941 ei_pad: [0; 7],
942 e_type: 0,
943 e_machine: E_MACHINE_SBPF,
944 e_version: 0,
945 e_entry: 0,
946 e_phoff: 0,
947 e_shoff: 0,
948 e_flags: 0,
949 e_ehsize: 0,
950 e_phentsize: 0,
951 e_phnum: 0,
952 e_shentsize: 0,
953 e_shnum: 0,
954 e_shstrndx: 0,
955 },
956 program_headers: vec![],
957 section_headers: vec![],
958 section_header_entries: vec![
959 SectionHeaderEntry::new(".text\0".to_string(), 0, text).unwrap(),
960 ],
961 relocations: vec![],
962 };
963
964 let parsed = program.to_ixs().unwrap();
965 assert_eq!(parsed.value.instructions.len(), 1);
966 assert!(matches!(
967 parsed.errors.as_slice(),
968 [crate::errors::DisassemblerError::InvalidDataLength(11)]
969 ));
970 }
971}