use std::ops::Range;
use crate::misc::Rva;
#[derive(Debug, Default, Clone)]
pub struct Module {
pub at: Range<u64>,
pub name: String,
}
impl Module {
pub fn new(name: impl Into<String>, start: u64, end: u64) -> Self {
Module {
name: name.into(),
at: start..end,
}
}
#[expect(clippy::cast_possible_truncation)]
#[must_use]
pub fn rva(&self, addr: u64) -> Rva {
debug_assert!(self.at.contains(&addr));
let offset = addr - self.at.start;
assert!(offset <= u32::MAX.into());
offset as Rva
}
}
#[derive(Debug, Default)]
pub struct Modules(Vec<Module>);
impl Modules {
#[must_use]
pub fn new(mut modules: Vec<Module>) -> Self {
modules.sort_unstable_by_key(|e| e.at.end);
Self(modules)
}
#[must_use]
pub fn by_addr(&self, addr: u64) -> Option<&Module> {
let idx = self.0.partition_point(|m| m.at.end <= addr);
if idx == self.0.len() {
return None;
}
let module = &self.0[idx];
if module.at.contains(&addr) {
Some(module)
} else {
None
}
}
#[must_use]
pub fn by_name(&self, name: &str) -> Option<&Module> {
self.0.iter().find(|module| module.name == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basics() {
let modules = Modules::new(vec![
Module::new("foo".to_string(), 0x1_000, 0x2_000),
Module::new("foobar".to_string(), 0x2_000, 0x3_000),
Module::new("bar".to_string(), 0x4_000, 0x5_000),
]);
assert!(modules.by_addr(1).is_none());
assert_eq!(modules.by_addr(0x1_000).unwrap().name, "foo");
assert_eq!(modules.by_name("foo").unwrap().at.start, 0x1_000);
assert_eq!(modules.by_addr(0x2_000).unwrap().name, "foobar");
assert_eq!(modules.by_name("foobar").unwrap().at.start, 0x2_000);
assert!(modules.by_addr(0x3_000).is_none());
assert!(modules.by_name("bleh").is_none());
assert_eq!(modules.by_addr(0x4_fff).unwrap().name, "bar");
assert_eq!(modules.by_name("bar").unwrap().at.start, 0x4_000);
assert!(modules.by_addr(0x6_000).is_none());
}
}