mod error;
mod num_display;
pub mod opcodes;
pub mod raw;
mod reader;
pub use error::*;
use raw::*;
pub struct UnwindInfo<'a> {
data: &'a [u8],
global_opcodes: &'a [Opcode],
pages: &'a [PageEntry],
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Function {
pub start_address: u32,
pub end_address: u32,
pub opcode: u32,
}
impl<'a> UnwindInfo<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let header = CompactUnwindInfoHeader::parse(data)?;
let global_opcodes = header.global_opcodes(data)?;
let pages = header.pages(data)?;
Ok(Self {
data,
global_opcodes,
pages,
})
}
pub fn functions(&self) -> FunctionIter<'a> {
FunctionIter {
data: self.data,
global_opcodes: self.global_opcodes,
pages: self.pages,
cur_page: None,
}
}
pub fn address_range(&self) -> core::ops::Range<u32> {
if self.pages.is_empty() {
return 0..0;
}
let first_page = self.pages.first().unwrap();
let last_page = self.pages.last().unwrap();
first_page.first_address()..last_page.first_address()
}
pub fn lookup(&self, pc: u32) -> Result<Option<Function>, Error> {
let Self {
pages,
data,
global_opcodes,
} = self;
let page_index = match pages.binary_search_by_key(&pc, PageEntry::first_address) {
Ok(i) => i,
Err(insertion_index) => {
if insertion_index == 0 {
return Ok(None);
}
insertion_index - 1
}
};
if page_index == pages.len() - 1 {
return Ok(None);
}
let page_entry = &pages[page_index];
let next_page_entry = &pages[page_index + 1];
let page_offset = page_entry.page_offset();
match page_entry.page_kind(data)? {
consts::PAGE_KIND_REGULAR => {
let page = RegularPage::parse(data, page_offset.into())?;
let functions = page.functions(data, page_offset)?;
let function_index =
match functions.binary_search_by_key(&pc, RegularFunctionEntry::address) {
Ok(i) => i,
Err(insertion_index) => {
if insertion_index == 0 {
return Err(Error::InvalidPageEntryFirstAddress);
}
insertion_index - 1
}
};
let entry = &functions[function_index];
let fun_address = entry.address();
let next_fun_address = if let Some(next_entry) = functions.get(function_index + 1) {
next_entry.address()
} else {
next_page_entry.first_address()
};
Ok(Some(Function {
start_address: fun_address,
end_address: next_fun_address,
opcode: entry.opcode(),
}))
}
consts::PAGE_KIND_COMPRESSED => {
let page = CompressedPage::parse(data, page_offset.into())?;
let functions = page.functions(data, page_offset)?;
let page_address = page_entry.first_address();
let rel_pc = pc - page_address;
let function_index = match functions.binary_search_by_key(&rel_pc, |&entry| {
CompressedFunctionEntry::new(entry.into()).relative_address()
}) {
Ok(i) => i,
Err(insertion_index) => {
if insertion_index == 0 {
return Err(Error::InvalidPageEntryFirstAddress);
}
insertion_index - 1
}
};
let entry = CompressedFunctionEntry::new(functions[function_index].into());
let fun_address = page_address + entry.relative_address();
let next_fun_address = if let Some(next_entry) = functions.get(function_index + 1) {
let next_entry = CompressedFunctionEntry::new((*next_entry).into());
page_address + next_entry.relative_address()
} else {
next_page_entry.first_address()
};
let opcode_index: usize = entry.opcode_index().into();
let opcode = if opcode_index < global_opcodes.len() {
global_opcodes[opcode_index].opcode()
} else {
let local_opcodes = page.local_opcodes(data, page_offset)?;
let local_index = opcode_index - global_opcodes.len();
local_opcodes[local_index].opcode()
};
Ok(Some(Function {
start_address: fun_address,
end_address: next_fun_address,
opcode,
}))
}
consts::PAGE_KIND_SENTINEL => {
Err(Error::UnexpectedSentinelPage)
}
_ => Err(Error::InvalidPageKind),
}
}
}
pub struct FunctionIter<'a> {
data: &'a [u8],
global_opcodes: &'a [Opcode],
pages: &'a [PageEntry],
cur_page: Option<PageWithPartialFunctions<'a>>,
}
#[derive(Clone, Copy)]
enum PageWithPartialFunctions<'a> {
Regular {
next_page_address: u32,
functions: &'a [RegularFunctionEntry],
},
Compressed {
page_address: u32,
next_page_address: u32,
local_opcodes: &'a [Opcode],
functions: &'a [U32],
},
}
impl<'a> FunctionIter<'a> {
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Option<Function>, Error> {
loop {
let cur_page = if let Some(cur_page) = self.cur_page.as_mut() {
cur_page
} else {
let cur_page = match self.next_page()? {
Some(page) => page,
None => return Ok(None),
};
self.cur_page.insert(cur_page)
};
match cur_page {
PageWithPartialFunctions::Regular {
next_page_address,
functions,
} => {
if let Some((entry, remainder)) = functions.split_first() {
*functions = remainder;
let start_address = entry.address();
let end_address = remainder
.first()
.map(RegularFunctionEntry::address)
.unwrap_or(*next_page_address);
return Ok(Some(Function {
start_address,
end_address,
opcode: entry.opcode(),
}));
}
}
PageWithPartialFunctions::Compressed {
page_address,
functions,
next_page_address,
local_opcodes,
} => {
if let Some((entry, remainder)) = functions.split_first() {
*functions = remainder;
let entry = CompressedFunctionEntry::new((*entry).into());
let start_address = *page_address + entry.relative_address();
let end_address = match remainder.first() {
Some(next_entry) => {
let next_entry = CompressedFunctionEntry::new((*next_entry).into());
*page_address + next_entry.relative_address()
}
None => *next_page_address,
};
let opcode_index: usize = entry.opcode_index().into();
let opcode = if opcode_index < self.global_opcodes.len() {
self.global_opcodes[opcode_index].opcode()
} else {
let local_index = opcode_index - self.global_opcodes.len();
local_opcodes[local_index].opcode()
};
return Ok(Some(Function {
start_address,
end_address,
opcode,
}));
}
}
}
self.cur_page = None;
}
}
fn next_page(&mut self) -> Result<Option<PageWithPartialFunctions<'a>>, Error> {
let (page_entry, remainder) = match self.pages.split_first() {
Some(split) => split,
None => return Ok(None),
};
self.pages = remainder;
let next_page_entry = match remainder.first() {
Some(entry) => entry,
None => return Ok(None),
};
let page_offset = page_entry.page_offset();
let page_address = page_entry.first_address();
let next_page_address = next_page_entry.first_address();
let data = self.data;
let cur_page = match page_entry.page_kind(data)? {
consts::PAGE_KIND_REGULAR => {
let page = RegularPage::parse(data, page_offset.into())?;
PageWithPartialFunctions::Regular {
functions: page.functions(data, page_offset)?,
next_page_address,
}
}
consts::PAGE_KIND_COMPRESSED => {
let page = CompressedPage::parse(data, page_offset.into())?;
PageWithPartialFunctions::Compressed {
page_address,
next_page_address,
functions: page.functions(data, page_offset)?,
local_opcodes: page.local_opcodes(data, page_offset)?,
}
}
consts::PAGE_KIND_SENTINEL => return Err(Error::UnexpectedSentinelPage),
_ => return Err(Error::InvalidPageKind),
};
Ok(Some(cur_page))
}
}