2024-04-04 00:03:42 +11:00
|
|
|
use std::{borrow::Cow, collections::HashMap, io::Cursor, rc::Rc};
|
|
|
|
|
|
|
|
use addr2line::{
|
|
|
|
gimli,
|
|
|
|
object::{self, Object},
|
|
|
|
};
|
2024-04-18 09:43:47 +10:00
|
|
|
use thiserror::Error;
|
2024-04-04 00:03:42 +11:00
|
|
|
|
2024-04-18 09:43:47 +10:00
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum LoadDwarfError {
|
|
|
|
#[error("Gba file is empty")]
|
|
|
|
GbaFileEmpty,
|
|
|
|
#[error("Failed to load debug information from ROM file, it might not have been included?")]
|
|
|
|
NoDebugInformation,
|
|
|
|
#[error("Failed to load debug information: {0}")]
|
|
|
|
DeserializationError(#[from] rmp_serde::decode::Error),
|
|
|
|
#[error(transparent)]
|
|
|
|
GimliError(#[from] gimli::Error),
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type GimliDwarf = gimli::Dwarf<gimli::EndianRcSlice<gimli::RunTimeEndian>>;
|
|
|
|
|
|
|
|
pub fn load_dwarf(file_content: &[u8]) -> Result<GimliDwarf, LoadDwarfError> {
|
2024-04-04 00:03:42 +11:00
|
|
|
if let Ok(object) = object::File::parse(file_content) {
|
2024-04-18 09:43:47 +10:00
|
|
|
return Ok(load_from_object(&object)?);
|
2024-04-04 00:03:42 +11:00
|
|
|
}
|
|
|
|
|
2024-04-04 00:19:28 +11:00
|
|
|
// the file might have been padded, so ensure we skip any padding before continuing
|
|
|
|
let last_non_zero_byte = file_content
|
|
|
|
.iter()
|
|
|
|
.rposition(|&b| b != 0)
|
2024-04-18 09:43:47 +10:00
|
|
|
.ok_or_else(|| LoadDwarfError::GbaFileEmpty)?;
|
2024-04-04 00:19:28 +11:00
|
|
|
|
|
|
|
let file_content = &file_content[..last_non_zero_byte + 1];
|
|
|
|
|
2024-04-04 00:03:42 +11:00
|
|
|
let last_8_bytes = &file_content[file_content.len() - 8..];
|
2024-04-18 09:43:47 +10:00
|
|
|
let len = u32::from_le_bytes(
|
|
|
|
last_8_bytes[0..4]
|
|
|
|
.try_into()
|
|
|
|
.or(Err(LoadDwarfError::NoDebugInformation))?,
|
|
|
|
) as usize;
|
2024-04-04 00:19:28 +11:00
|
|
|
let version = &last_8_bytes[4..];
|
2024-04-04 00:03:42 +11:00
|
|
|
|
2024-04-04 00:19:28 +11:00
|
|
|
if version != b"agb1" {
|
2024-04-18 09:43:47 +10:00
|
|
|
return Err(LoadDwarfError::NoDebugInformation);
|
2024-04-04 00:03:42 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
let compressed_debug_data = &file_content[file_content.len() - len - 8..file_content.len() - 8];
|
|
|
|
|
|
|
|
let decompressing_reader =
|
|
|
|
lz4_flex::frame::FrameDecoder::new(Cursor::new(compressed_debug_data));
|
2024-04-18 09:43:47 +10:00
|
|
|
let debug_info: HashMap<String, Vec<u8>> = rmp_serde::decode::from_read(decompressing_reader)?;
|
2024-04-04 00:03:42 +11:00
|
|
|
|
|
|
|
let dwarf = gimli::Dwarf::load(|id| {
|
|
|
|
let data = debug_info
|
|
|
|
.get(id.name())
|
|
|
|
.map(|data| Cow::Borrowed(data.as_slice()))
|
|
|
|
.unwrap_or(Cow::Borrowed(&[]));
|
|
|
|
|
|
|
|
Result::<_, gimli::Error>::Ok(gimli::EndianRcSlice::new(
|
|
|
|
Rc::from(&*data),
|
|
|
|
gimli::RunTimeEndian::Little,
|
|
|
|
))
|
|
|
|
})?;
|
|
|
|
|
|
|
|
Ok(dwarf)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_from_object<'file>(
|
|
|
|
object: &object::File<'file, &'file [u8]>,
|
2024-04-18 09:43:47 +10:00
|
|
|
) -> Result<GimliDwarf, gimli::Error> {
|
2024-04-04 00:03:42 +11:00
|
|
|
let endian = if object.is_little_endian() {
|
|
|
|
gimli::RunTimeEndian::Little
|
|
|
|
} else {
|
|
|
|
gimli::RunTimeEndian::Big
|
|
|
|
};
|
|
|
|
|
2024-04-14 00:58:30 +10:00
|
|
|
fn load_section<'data, Endian>(
|
2024-04-04 00:03:42 +11:00
|
|
|
id: gimli::SectionId,
|
2024-04-14 00:58:30 +10:00
|
|
|
file: &impl object::Object<'data>,
|
2024-04-04 00:03:42 +11:00
|
|
|
endian: Endian,
|
|
|
|
) -> Result<gimli::EndianRcSlice<Endian>, gimli::Error>
|
|
|
|
where
|
|
|
|
Endian: gimli::Endianity,
|
|
|
|
{
|
|
|
|
use object::ObjectSection;
|
|
|
|
|
|
|
|
let data = file
|
|
|
|
.section_by_name(id.name())
|
|
|
|
.and_then(|section| section.uncompressed_data().ok())
|
|
|
|
.unwrap_or(Cow::Borrowed(&[]));
|
|
|
|
Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian))
|
|
|
|
}
|
|
|
|
|
|
|
|
let dwarf = gimli::Dwarf::load(|id| load_section(id, object, endian))?;
|
|
|
|
Ok(dwarf)
|
|
|
|
}
|