use super::consts::{
IMAGE_SCN_ALIGN_MASK, IMAGE_SCN_CNT_CODE, IMAGE_SCN_CNT_INITIALIZED_DATA,
IMAGE_SCN_CNT_UNINITIALIZED_DATA, IMAGE_SCN_LNK_COMDAT, IMAGE_SCN_LNK_INFO,
IMAGE_SCN_LNK_NRELOC_OVFL, IMAGE_SCN_LNK_REMOVE, IMAGE_SCN_MEM_DISCARDABLE,
IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE,
};
use super::source::{array, entry_offset, until_nul};
use super::strtab::StringTable;
pub const SECTION_HEADER_SIZE: usize = 40;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct SectionHeader {
pub raw_name: [u8; 8],
pub virtual_size: u32,
pub virtual_address: u32,
pub size_of_raw_data: u32,
pub pointer_to_raw_data: u32,
pub pointer_to_relocations: u32,
pub pointer_to_linenumbers: u32,
pub number_of_relocations: u16,
pub number_of_linenumbers: u16,
pub characteristics: u32,
}
impl SectionHeader {
#[must_use]
pub fn decode(raw: &[u8; SECTION_HEADER_SIZE]) -> Self {
let u32_at = |offset: usize| array::<4>(raw, offset).map_or(0, u32::from_le_bytes);
let u16_at = |offset: usize| array::<2>(raw, offset).map_or(0, u16::from_le_bytes);
Self {
raw_name: array::<8>(raw, 0).unwrap_or_default(),
virtual_size: u32_at(8),
virtual_address: u32_at(12),
size_of_raw_data: u32_at(16),
pointer_to_raw_data: u32_at(20),
pointer_to_relocations: u32_at(24),
pointer_to_linenumbers: u32_at(28),
number_of_relocations: u16_at(32),
number_of_linenumbers: u16_at(34),
characteristics: u32_at(36),
}
}
#[must_use]
pub fn has_long_name(&self) -> bool {
self.raw_name[0] == b'/'
}
#[inline]
#[must_use]
pub fn alignment_log2(&self) -> Option<u32> {
match (self.characteristics & IMAGE_SCN_ALIGN_MASK) >> 20 {
0 | 15 => None,
n => Some(n.wrapping_sub(1)),
}
}
#[inline]
#[must_use]
pub fn alignment(&self) -> Option<u32> {
self.alignment_log2().map(|log2| 1u32 << log2)
}
#[inline]
#[must_use]
pub fn has(&self, flag: u32) -> bool {
self.characteristics & flag == flag
}
#[inline]
#[must_use]
pub fn is_code(&self) -> bool {
self.has(IMAGE_SCN_CNT_CODE)
}
#[inline]
#[must_use]
pub fn is_initialized_data(&self) -> bool {
self.has(IMAGE_SCN_CNT_INITIALIZED_DATA)
}
#[inline]
#[must_use]
pub fn is_uninitialized_data(&self) -> bool {
self.has(IMAGE_SCN_CNT_UNINITIALIZED_DATA)
}
#[inline]
#[must_use]
pub fn is_comdat(&self) -> bool {
self.has(IMAGE_SCN_LNK_COMDAT)
}
#[inline]
#[must_use]
pub fn is_remove(&self) -> bool {
self.has(IMAGE_SCN_LNK_REMOVE)
}
#[inline]
#[must_use]
pub fn is_info(&self) -> bool {
self.has(IMAGE_SCN_LNK_INFO)
}
#[inline]
#[must_use]
pub fn is_discardable(&self) -> bool {
self.has(IMAGE_SCN_MEM_DISCARDABLE)
}
#[inline]
#[must_use]
pub fn is_executable(&self) -> bool {
self.has(IMAGE_SCN_MEM_EXECUTE)
}
#[inline]
#[must_use]
pub fn is_readable(&self) -> bool {
self.has(IMAGE_SCN_MEM_READ)
}
#[inline]
#[must_use]
pub fn is_writable(&self) -> bool {
self.has(IMAGE_SCN_MEM_WRITE)
}
#[inline]
#[must_use]
pub fn has_extended_relocations(&self) -> bool {
self.has(IMAGE_SCN_LNK_NRELOC_OVFL) && self.number_of_relocations == u16::MAX
}
}
#[must_use]
pub fn resolve_name<'a>(raw_name: &'a [u8; 8], strings: &StringTable<'a>) -> Option<&'a [u8]> {
let name = until_nul(raw_name);
match string_table_offset(name) {
NameRef::Inline => Some(name),
NameRef::Offset(offset) => strings.get(offset),
NameRef::Invalid => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum NameRef {
Inline,
Offset(u32),
Invalid,
}
fn string_table_offset(name: &[u8]) -> NameRef {
let Some(rest) = name.strip_prefix(b"/") else {
return NameRef::Inline;
};
if let Some(encoded) = rest.strip_prefix(b"/") {
return decode_base64_offset(encoded).map_or(NameRef::Invalid, NameRef::Offset);
}
if rest.is_empty() {
return NameRef::Inline;
}
let mut value: u32 = 0;
for &b in rest {
if !b.is_ascii_digit() {
return NameRef::Invalid;
}
let Some(next) = value
.checked_mul(10)
.and_then(|v| v.checked_add(u32::from(b.wrapping_sub(b'0'))))
else {
return NameRef::Invalid;
};
value = next;
}
NameRef::Offset(value)
}
fn decode_base64_offset(encoded: &[u8]) -> Option<u32> {
if encoded.is_empty() || encoded.len() > 6 {
return None;
}
let mut value: u64 = 0;
for &b in encoded {
let digit = match b {
b'A'..=b'Z' => b.wrapping_sub(b'A'),
b'a'..=b'z' => b.wrapping_sub(b'a').wrapping_add(26),
b'0'..=b'9' => b.wrapping_sub(b'0').wrapping_add(52),
b'+' => 62,
b'/' => 63,
_ => return None,
};
value = value.checked_mul(64)?.checked_add(u64::from(digit))?;
}
u32::try_from(value).ok()
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SectionTable<'a> {
raw: &'a [[u8; SECTION_HEADER_SIZE]],
file_offset: u64,
}
impl<'a> SectionTable<'a> {
#[must_use]
pub fn new(raw: &'a [[u8; SECTION_HEADER_SIZE]], file_offset: u64) -> Self {
Self { raw, file_offset }
}
#[must_use]
pub fn from_bytes(data: &'a [u8], file_offset: u64) -> Self {
let (records, _) = data.as_chunks::<SECTION_HEADER_SIZE>();
Self::new(records, file_offset)
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.raw.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.raw.is_empty()
}
#[must_use]
pub fn file_offset(&self) -> u64 {
self.file_offset
}
#[must_use]
pub fn header_offset(&self, number: u32) -> u64 {
entry_offset(
self.file_offset,
u64::from(number.saturating_sub(1)),
SECTION_HEADER_SIZE,
)
}
#[inline]
#[must_use]
pub fn get(&self, number: u32) -> Option<SectionHeader> {
let index = usize::try_from(number.checked_sub(1)?).ok()?;
self.raw.get(index).map(SectionHeader::decode)
}
#[must_use]
pub fn name(&self, number: u32, strings: &StringTable<'a>) -> Option<&'a [u8]> {
let index = usize::try_from(number.checked_sub(1)?).ok()?;
let raw: &'a [u8; SECTION_HEADER_SIZE] = self.raw.get(index)?;
let (name, _) = raw.split_first_chunk::<8>()?;
resolve_name(name, strings)
}
#[must_use]
pub fn raw(&self) -> &'a [[u8; SECTION_HEADER_SIZE]] {
self.raw
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = SectionHeader> + use<'a> {
self.raw.iter().map(SectionHeader::decode)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn base64_names() {
assert_eq!(decode_base64_offset(b"A"), Some(0));
assert_eq!(decode_base64_offset(b"BA"), Some(64));
assert_eq!(decode_base64_offset(b"////"), Some(64u32.pow(4) - 1));
assert_eq!(decode_base64_offset(b"D/////"), Some(u32::MAX));
assert_eq!(decode_base64_offset(b"E/////"), None);
assert_eq!(decode_base64_offset(b"AAAAAAA"), None);
assert_eq!(decode_base64_offset(b""), None);
assert_eq!(decode_base64_offset(b"A-"), None);
}
#[test]
fn names() {
let strings = StringTable::new(b"\x10\0\0\0.debug_info\0", 0);
assert_eq!(resolve_name(b".text\0\0\0", &strings), Some(&b".text"[..]));
assert_eq!(
resolve_name(b"/4\0\0\0\0\0\0", &strings),
Some(&b".debug_info"[..])
);
assert_eq!(
resolve_name(b"//AAAAE\0", &strings),
Some(&b".debug_info"[..])
);
assert_eq!(resolve_name(b"/4x\0\0\0\0\0", &strings), None);
assert_eq!(resolve_name(b"/9999999", &strings), None);
assert_eq!(resolve_name(b".abcdefg", &strings), Some(&b".abcdefg"[..]));
}
#[test]
fn alignment() {
let mut header = SectionHeader::default();
assert_eq!(header.alignment(), None);
header.characteristics = 0x0050_0000;
assert_eq!(header.alignment(), Some(16));
header.characteristics = 0x00e0_0000;
assert_eq!(header.alignment(), Some(8192));
header.characteristics = 0x00f0_0000;
assert_eq!(header.alignment(), None);
}
}