use crate::{
formats::{BinaryContext, BinaryFormat},
structures::moduledata::{LayoutHints, Moduledata, ModuledataVersion},
};
pub struct ModuledataLocator<'a> {
ctx: &'a BinaryContext<'a>,
ptr_size: u8,
pclntab_offset: usize,
hints: LayoutHints,
}
impl<'a> ModuledataLocator<'a> {
pub fn new(
ctx: &'a BinaryContext<'a>,
ptr_size: u8,
pclntab_offset: usize,
hints: LayoutHints,
) -> Self {
Self {
ctx,
ptr_size,
pclntab_offset,
hints,
}
}
pub fn locate(&self) -> Option<Moduledata> {
if !self.ctx.has_va_mapping() || self.ptr_size == 0 {
return None;
}
self.in_section().or_else(|| self.by_scan())
}
fn in_section(&self) -> Option<Moduledata> {
let range = self.ctx.sections().go_module.as_ref()?;
let data = self.ctx.structure_search_data();
let end = range.offset.checked_add(range.size)?;
Moduledata::parse(data.get(range.offset..end)?, self.ptr_size, self.hints)
}
fn by_scan(&self) -> Option<Moduledata> {
let data = self.ctx.structure_search_data();
let target = self.scan_target()?;
if self.ctx.format() != BinaryFormat::Wasm {
let sections = self.ctx.sections();
for range in [sections.noptrdata.as_ref(), sections.data_section.as_ref()]
.into_iter()
.flatten()
{
if let Some(found) = self.scan_region(data, range.offset, range.size, target) {
return Some(found);
}
}
}
self.scan_region(data, 0, data.len(), target)
}
fn scan_target(&self) -> Option<u64> {
if self.ctx.format() == BinaryFormat::Wasm {
Some(self.pclntab_offset as u64)
} else {
self.ctx.file_to_va(self.pclntab_offset)
}
}
fn scan_region(
&self,
data: &[u8],
start: usize,
len: usize,
target: u64,
) -> Option<Moduledata> {
let p = self.ptr_size as usize;
let start = start.checked_next_multiple_of(p)?;
let end = start.checked_add(len)?.min(data.len());
let region = data.get(start..end)?;
for (i, word) in region.chunks_exact(p).enumerate() {
let value = match p {
4 => u32::from_le_bytes(word.try_into().ok()?) as u64,
8 => u64::from_le_bytes(word.try_into().ok()?),
_ => return None,
};
if value != target {
continue;
}
let Some(at) = i.checked_mul(p).and_then(|o| o.checked_add(start)) else {
continue;
};
if let Some(md) = data
.get(at..)
.and_then(|rest| Moduledata::parse(rest, self.ptr_size, self.hints))
&& self.accept(&md)
{
return Some(md);
}
}
None
}
fn accept(&self, md: &Moduledata) -> bool {
if md.minpc >= md.maxpc {
return false;
}
match md.version {
ModuledataVersion::V1 => md.text != 0 && self.ctx.va_to_file(md.text).is_some(),
_ => md.types != 0 && self.ctx.va_to_file(md.funcnametab.ptr).is_some(),
}
}
}