1use std::{borrow::Cow, collections::HashMap, io::Cursor, rc::Rc};
2
3use addr2line::gimli;
4use object::Object;
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum LoadDwarfError {
9 #[error("Gba file is empty")]
10 GbaFileEmpty,
11 #[error("Failed to load debug information from ROM file, it might not have been included?")]
12 NoDebugInformation,
13 #[error("Failed to load debug information: {0}")]
14 DeserializationError(#[from] rmp_serde::decode::Error),
15 #[error(transparent)]
16 GimliError(#[from] gimli::Error),
17}
18
19pub type GimliDwarf = gimli::Dwarf<gimli::EndianRcSlice<gimli::RunTimeEndian>>;
20
21pub fn load_dwarf(file_content: &[u8]) -> Result<GimliDwarf, LoadDwarfError> {
22 if let Ok(object) = object::File::parse(file_content) {
23 return Ok(load_from_object(&object)?);
24 }
25
26 let last_non_zero_byte = file_content
28 .iter()
29 .rposition(|&b| b != 0)
30 .ok_or(LoadDwarfError::GbaFileEmpty)?;
31
32 let file_content = &file_content[..last_non_zero_byte + 1];
33
34 let last_8_bytes = &file_content[file_content.len() - 8..];
35 let len = u32::from_le_bytes(
36 last_8_bytes[0..4]
37 .try_into()
38 .or(Err(LoadDwarfError::NoDebugInformation))?,
39 ) as usize;
40 let version = &last_8_bytes[4..];
41
42 if version != b"agb1" {
43 return Err(LoadDwarfError::NoDebugInformation);
44 }
45
46 let compressed_debug_data = &file_content[file_content.len() - len - 8..file_content.len() - 8];
47
48 let decompressing_reader =
49 lz4_flex::frame::FrameDecoder::new(Cursor::new(compressed_debug_data));
50 let debug_info: HashMap<String, Vec<u8>> = rmp_serde::decode::from_read(decompressing_reader)?;
51
52 let dwarf = gimli::Dwarf::load(|id| {
53 let data = debug_info
54 .get(id.name())
55 .map(|data| Cow::Borrowed(data.as_slice()))
56 .unwrap_or(Cow::Borrowed(&[]));
57
58 Result::<_, gimli::Error>::Ok(gimli::EndianRcSlice::new(
59 Rc::from(&*data),
60 gimli::RunTimeEndian::Little,
61 ))
62 })?;
63
64 Ok(dwarf)
65}
66
67fn load_from_object<'file>(
68 object: &object::File<'file, &'file [u8]>,
69) -> Result<GimliDwarf, gimli::Error> {
70 let endian = if object.is_little_endian() {
71 gimli::RunTimeEndian::Little
72 } else {
73 gimli::RunTimeEndian::Big
74 };
75
76 fn load_section<'data, Endian>(
77 id: gimli::SectionId,
78 file: &impl object::Object<'data>,
79 endian: Endian,
80 ) -> Result<gimli::EndianRcSlice<Endian>, gimli::Error>
81 where
82 Endian: gimli::Endianity,
83 {
84 use object::ObjectSection;
85
86 let data = file
87 .section_by_name(id.name())
88 .and_then(|section| section.uncompressed_data().ok())
89 .unwrap_or(Cow::Borrowed(&[]));
90 Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian))
91 }
92
93 let dwarf = gimli::Dwarf::load(|id| load_section(id, object, endian))?;
94 Ok(dwarf)
95}