mod arch;
mod target_os;
pub mod blob;
pub mod elf;
pub mod pe;
pub use arch::Arch;
pub use target_os::TargetOs;
pub const fn printable_byte(b: u8) -> u8 {
if b >= 0x20 && b <= 0x7e { b } else { b'.' }
}
pub const fn is_printable_ascii_byte(b: u8) -> bool {
b >= 0x20 && b <= 0x7e
}
pub trait BinaryFormat: Send + Sync {
fn load_address(&self) -> u64;
fn byte_at(&self, addr: u64) -> Option<u8>;
fn bytes_at(&self, addr: u64) -> Option<&[u8]>;
fn entry_points(&self) -> Vec<u64>;
fn entrypoint(&self) -> Option<u64> {
None
}
fn architecture(&self) -> Arch;
fn os(&self) -> TargetOs {
TargetOs::Unknown
}
fn linked_libraries(&self) -> Vec<String> {
Vec::new()
}
fn symbol_name(&self, _addr: u64) -> Option<&str> {
None
}
fn is_external_symbol(&self, _addr: u64) -> bool {
false
}
fn import_library(&self, _addr: u64) -> Option<&str> {
None
}
fn import_symbol_name(&self, _addr: u64) -> Option<&str> {
None
}
fn hex_rows(&self, addr: u64, len: usize, width: usize) -> Vec<(u64, Vec<Option<u8>>)> {
assert!(width > 0, "hex row width must be non-zero");
let row_start = addr - addr % width as u64;
let end = addr.saturating_add(len as u64);
let mut rows = Vec::new();
let mut cur = row_start;
while cur < end {
let bytes = (0..width as u64).map(|i| self.byte_at(cur + i)).collect();
rows.push((cur, bytes));
cur = cur.saturating_add(width as u64);
}
rows
}
fn contains(&self, addr: u64) -> bool {
self.byte_at(addr).is_some()
}
fn mapped_regions(&self) -> Vec<(u64, Vec<u8>, bool, bool)> {
Vec::new()
}
fn is_executable(&self, addr: u64) -> bool {
self.contains(addr)
}
fn segment_bounds(&self, _addr: u64) -> Option<(u64, u64)> {
None
}
fn is_known_writable(&self, _addr: u64) -> bool {
false
}
fn is_known_read_only(&self, _addr: u64) -> bool {
false
}
fn read_bytes(&self, addr: u64, n: usize) -> Option<Vec<u8>> {
let mut out = Vec::with_capacity(n);
for offset in 0..n {
out.push(self.byte_at(addr.checked_add(offset as u64)?)?);
}
Some(out)
}
fn read_uint(&self, addr: u64, size: usize) -> Option<u64> {
if size == 0 || size > 8 {
return None;
}
let bytes = self.read_bytes(addr, size)?;
let mut value = 0u64;
for (i, &b) in bytes.iter().enumerate() {
value |= (b as u64) << (i * 8);
}
Some(value)
}
fn read_cstring(&self, addr: u64, max_len: Option<usize>) -> Option<Vec<u8>> {
let limit = max_len.unwrap_or(usize::MAX);
let mut out = Vec::new();
for offset in 0..limit {
let byte = self.byte_at(addr.checked_add(offset as u64)?)?;
if byte == 0 {
return Some(out);
}
out.push(byte);
}
None
}
fn read_printable_cstring(&self, addr: u64, max_len: Option<usize>) -> Option<Vec<u8>> {
let limit = max_len.unwrap_or(usize::MAX);
let mut out = Vec::new();
for offset in 0..limit {
let byte = self.byte_at(addr.checked_add(offset as u64)?)?;
if byte == 0 {
return Some(out);
}
if !is_printable_ascii_byte(byte) {
return None;
}
out.push(byte);
}
None
}
}
#[cfg(test)]
mod tests {
use super::{BinaryFormat, blob::Blob};
#[test]
fn read_printable_cstring_accepts_printable_ascii() {
let blob = Blob::new(0x1000, b"hello, world!\0next".to_vec());
assert_eq!(
blob.read_printable_cstring(0x1000, None),
Some(b"hello, world!".to_vec())
);
}
#[test]
fn read_printable_cstring_rejects_control_bytes() {
let blob = Blob::new(0x1000, b"line\nbreak\0".to_vec());
assert_eq!(
blob.read_cstring(0x1000, None),
Some(b"line\nbreak".to_vec())
);
assert_eq!(blob.read_printable_cstring(0x1000, None), None);
}
#[test]
fn read_printable_cstring_rejects_non_ascii_bytes() {
let blob = Blob::new(0x1000, b"caf\xe9\0".to_vec());
assert_eq!(blob.read_printable_cstring(0x1000, None), None);
}
#[test]
fn read_printable_cstring_honors_max_len() {
let blob = Blob::new(0x1000, b"hello\0".to_vec());
assert_eq!(blob.read_printable_cstring(0x1000, Some(3)), None);
assert_eq!(
blob.read_printable_cstring(0x1000, Some(6)),
Some(b"hello".to_vec())
);
}
}