pub fn read_uleb128(bytes: &[u8]) -> Option<(u64, usize)> {
let mut result: u64 = 0;
let mut shift: u32 = 0;
let mut consumed: usize = 0;
for &byte in bytes {
consumed = consumed.checked_add(1)?;
let payload = (byte & 0x7f) as u64;
if shift >= 64 {
if payload != 0 || (byte & 0x80) != 0 {
return None;
}
return Some((result, consumed));
}
let chunk = payload.checked_shl(shift)?;
result |= chunk;
if (byte & 0x80) == 0 {
return Some((result, consumed));
}
shift = shift.checked_add(7)?;
}
None
}
pub fn read_sleb128(bytes: &[u8]) -> Option<(i64, usize)> {
let mut result: i64 = 0;
let mut shift: u32 = 0;
let mut consumed: usize = 0;
for &byte in bytes {
consumed = consumed.checked_add(1)?;
let payload = (byte & 0x7f) as i64;
if shift >= 64 {
if (byte & 0x80) != 0 {
return None;
}
let sign_bit = (result < 0) as i64;
let expected = if sign_bit == 1 { 0x7f } else { 0 };
if payload != expected {
return None;
}
return Some((result, consumed));
}
let chunk = payload.checked_shl(shift)?;
result |= chunk;
let high_bit = byte & 0x80;
let sign_bit = byte & 0x40;
let next_shift = shift.checked_add(7)?;
if high_bit == 0 {
if sign_bit != 0 && next_shift < 64 {
let mask = i64::wrapping_shl(-1, next_shift);
result |= mask;
}
return Some((result, consumed));
}
shift = next_shift;
}
None
}
pub fn read_u16_le_at(data: &[u8], off: usize) -> Option<u16> {
let end = off.checked_add(2)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 2]>::try_from(bytes).ok()?;
Some(u16::from_le_bytes(arr))
}
pub fn read_u32_le_at(data: &[u8], off: usize) -> Option<u32> {
let end = off.checked_add(4)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 4]>::try_from(bytes).ok()?;
Some(u32::from_le_bytes(arr))
}
pub fn read_u64_le_at(data: &[u8], off: usize) -> Option<u64> {
let end = off.checked_add(8)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 8]>::try_from(bytes).ok()?;
Some(u64::from_le_bytes(arr))
}
pub fn read_i32_le_at(data: &[u8], off: usize) -> Option<i32> {
let end = off.checked_add(4)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 4]>::try_from(bytes).ok()?;
Some(i32::from_le_bytes(arr))
}
pub fn relative_pointer(base_va: u64, offset: i32) -> u64 {
base_va.wrapping_add(offset as i64 as u64)
}
pub fn read_cstr_at(data: &[u8], off: usize) -> Option<&str> {
let tail = data.get(off..)?;
let len = tail.iter().position(|&b| b == 0).unwrap_or(tail.len());
let body = tail.get(..len)?;
core::str::from_utf8(body).ok()
}
pub fn cstr_from_fixed<const N: usize>(bytes: &[u8; N]) -> &str {
let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
bytes
.get(..len)
.and_then(|s| core::str::from_utf8(s).ok())
.unwrap_or("")
}
pub fn read_u32_be_at(data: &[u8], off: usize) -> Option<u32> {
let end = off.checked_add(4)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 4]>::try_from(bytes).ok()?;
Some(u32::from_be_bytes(arr))
}
pub fn read_u64_be_at(data: &[u8], off: usize) -> Option<u64> {
let end = off.checked_add(8)?;
let bytes = data.get(off..end)?;
let arr = <[u8; 8]>::try_from(bytes).ok()?;
Some(u64::from_be_bytes(arr))
}
pub(crate) fn vm_to_file_offset_in(
segments: impl IntoIterator<Item = (u64, u64, u64, u64)>,
vmaddr: u64,
) -> Option<u64> {
for (seg_vmaddr, seg_vmsize, seg_fileoff, seg_filesize) in segments {
let seg_end = seg_vmaddr.checked_add(seg_vmsize)?;
if (seg_vmaddr..seg_end).contains(&vmaddr) && seg_filesize > 0 {
let delta = vmaddr.checked_sub(seg_vmaddr)?;
if delta >= seg_filesize {
return None;
}
return seg_fileoff.checked_add(delta);
}
}
None
}
pub(crate) fn file_offset_to_vm_in(
segments: impl IntoIterator<Item = (u64, u64, u64, u64)>,
offset: u64,
) -> Option<u64> {
for (seg_vmaddr, seg_vmsize, seg_fileoff, seg_filesize) in segments {
let mapped = seg_filesize.min(seg_vmsize);
let seg_end = seg_fileoff.checked_add(mapped)?;
if (seg_fileoff..seg_end).contains(&offset) {
return seg_vmaddr.checked_add(offset.checked_sub(seg_fileoff)?);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn file_offset_to_vm_inverts_vm_to_file_offset() {
let segments = [
(0, 0x1_0000_0000, 0, 0),
(0x1_0000_0000, 0x8000, 0, 0x4000),
(0x1_0000_8000, 0x4000, 0x4000, 0x1000),
];
assert_eq!(file_offset_to_vm_in(segments, 0x460), Some(0x1_0000_0460));
assert_eq!(file_offset_to_vm_in(segments, 0x4010), Some(0x1_0000_8010));
assert_eq!(file_offset_to_vm_in(segments, 0x5000), None);
assert_eq!(vm_to_file_offset_in(segments, 0x1_0000_8010), Some(0x4010));
}
#[test]
fn uleb128_simple() {
assert_eq!(read_uleb128(&[0x00]), Some((0, 1)));
assert_eq!(read_uleb128(&[0x7f]), Some((127, 1)));
assert_eq!(read_uleb128(&[0x80, 0x01]), Some((128, 2)));
assert_eq!(read_uleb128(&[0xe5, 0x8e, 0x26]), Some((624_485, 3)));
}
#[test]
fn uleb128_truncated_returns_none() {
assert_eq!(read_uleb128(&[0x80]), None);
}
#[test]
fn uleb128_max_u64() {
let max = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01];
assert_eq!(read_uleb128(&max), Some((u64::MAX, 10)));
}
#[test]
fn sleb128_positive() {
assert_eq!(read_sleb128(&[0x00]), Some((0, 1)));
assert_eq!(read_sleb128(&[0x40]), Some((-64, 1)));
assert_eq!(read_sleb128(&[0x3f]), Some((63, 1)));
}
#[test]
fn sleb128_negative_two_byte() {
assert_eq!(read_sleb128(&[0xc0, 0xbb, 0x78]), Some((-123_456, 3)));
}
#[test]
fn sleb128_truncated() {
assert_eq!(read_sleb128(&[0x80]), None);
}
#[test]
fn vm_to_file_offset_translates_within_text() {
let segs = [
(0u64, 0x1_0000_0000u64, 0u64, 0u64), (0x1_0000_0000, 0x4000, 0, 0x4000), ];
assert_eq!(vm_to_file_offset_in(segs, 0x1_0000_0460), Some(0x460));
}
#[test]
fn vm_to_file_offset_pagezero_is_none() {
let segs = [(0u64, 0x1_0000_0000u64, 0u64, 0u64)];
assert_eq!(vm_to_file_offset_in(segs, 0x10), None);
}
#[test]
fn vm_to_file_offset_outside_any_segment() {
let segs = [(0x1_0000_0000u64, 0x4000u64, 0u64, 0x4000u64)];
assert_eq!(vm_to_file_offset_in(segs, 0xdead_beef), None);
}
#[test]
fn read_u16_le_at_basic_and_truncation() {
let buf = [0x34, 0x12, 0xff];
assert_eq!(read_u16_le_at(&buf, 0), Some(0x1234));
assert_eq!(read_u16_le_at(&buf, 1), Some(0xff12));
assert_eq!(read_u16_le_at(&buf, 2), None); }
#[test]
fn read_u32_le_at_basic_and_truncation() {
let buf = [0x78, 0x56, 0x34, 0x12, 0xab];
assert_eq!(read_u32_le_at(&buf, 0), Some(0x1234_5678));
assert_eq!(read_u32_le_at(&buf, 2), None); }
#[test]
fn read_u64_le_at_basic_and_truncation() {
let buf = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
assert_eq!(read_u64_le_at(&buf, 0), Some(0x0807_0605_0403_0201));
assert_eq!(read_u64_le_at(&buf, 1), None);
}
#[test]
fn read_u32_be_at_basic_and_truncation() {
let buf = [0xfa, 0xde, 0x0c, 0xc0, 0x00, 0x00, 0x00, 0x10];
assert_eq!(read_u32_be_at(&buf, 0), Some(0xfade_0cc0));
assert_eq!(read_u32_be_at(&buf, 4), Some(0x0000_0010));
assert_eq!(read_u32_be_at(&buf, 5), None);
}
#[test]
fn read_u64_be_at_basic_and_truncation() {
let buf = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
assert_eq!(read_u64_be_at(&buf, 0), Some(0x0102_0304_0506_0708));
assert_eq!(read_u64_be_at(&buf, 1), None);
}
#[test]
fn read_i32_le_at_signed_round_trip() {
let buf = [0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x80];
assert_eq!(read_i32_le_at(&buf, 0), Some(-1));
assert_eq!(read_i32_le_at(&buf, 4), Some(i32::MIN));
assert_eq!(read_i32_le_at(&buf, 5), None);
}
#[test]
fn relative_pointer_forward_and_backward() {
assert_eq!(relative_pointer(0x1_0000_1000, 0x40), 0x1_0000_1040);
assert_eq!(relative_pointer(0x1_0000_1000, -16), 0x1_0000_0ff0);
assert_eq!(
relative_pointer(0x1_0000_2000, i32::MIN),
0x1_0000_2000u64.wrapping_add(i32::MIN as i64 as u64)
);
}
#[test]
fn read_cstr_at_basic_and_truncation() {
let buf = b"hello\0world\0\xff";
assert_eq!(read_cstr_at(buf, 0), Some("hello"));
assert_eq!(read_cstr_at(buf, 6), Some("world"));
assert_eq!(read_cstr_at(buf, 12), None); assert_eq!(read_cstr_at(buf, 99), None);
}
#[test]
fn read_cstr_at_empty_string() {
assert_eq!(read_cstr_at(b"\0abc", 0), Some(""));
}
#[test]
fn cstr_from_fixed_truncates_at_nul() {
let mut buf = [0u8; 16];
buf[..6].copy_from_slice(b"__TEXT");
assert_eq!(cstr_from_fixed(&buf), "__TEXT");
}
#[test]
fn cstr_from_fixed_full_window_no_nul() {
let buf = *b"__DATA_CONSTAAAA";
assert_eq!(cstr_from_fixed(&buf), "__DATA_CONSTAAAA");
}
#[test]
fn cstr_from_fixed_invalid_utf8_returns_empty() {
let buf = [0xffu8; 16];
assert_eq!(cstr_from_fixed(&buf), "");
}
}