agb_debug/
load_dwarf.rs

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    // the file might have been padded, so ensure we skip any padding before continuing
27    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}