#![deny(clippy::arithmetic_side_effects)]
use hashbrown::HashMap;
use crate::error::{Error, Result};
use crate::ids::{FileId, SymbolId};
use crate::symbols::{Resolution, SymbolFlags, SymbolName, SymbolTable};
use super::inputs::CoffInput;
use super::options::PeOptions;
use super::reloc::{Addresses, Value};
pub const TABLE_SYMBOL: &[u8] = b"___safe_se_handler_table";
pub const COUNT_SYMBOL: &[u8] = b"___safe_se_handler_count";
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum Plan {
#[default]
Nothing,
Zero,
Table(Vec<(u32, u32)>),
}
impl Plan {
#[must_use]
pub fn reserved_size(&self) -> u32 {
match self {
Self::Table(handlers) => u32::try_from(handlers.len())
.unwrap_or(u32::MAX)
.saturating_mul(4),
Self::Nothing | Self::Zero => 0,
}
}
}
pub fn plan(
pe: &PeOptions,
symbols: &SymbolTable<'_>,
files: &[CoffInput<'_>],
resolution: &Resolution<'_>,
) -> Result<Plan> {
if !pe.target().is_pe32() {
return Ok(Plan::Nothing);
}
let referenced = [TABLE_SYMBOL, COUNT_SYMBOL].iter().any(|name| {
symbols.lookup(&SymbolName::new(name)).is_some_and(|id| {
symbols
.flags(id)
.intersects(SymbolFlags::REFERENCED | SymbolFlags::WEAK_REFERENCED)
})
});
if !referenced && pe.safe_seh != Some(true) {
return Ok(Plan::Nothing);
}
if pe.safe_seh == Some(false) {
return Ok(Plan::Zero);
}
let mut handlers = Vec::new();
for (index, file) in files.iter().enumerate() {
let Some(parsed) = file.object() else {
continue;
};
if !resolution.is_live(FileId::new(index)) {
continue;
}
if !parsed.safe_seh_compatible() {
if pe.safe_seh == Some(true) {
return Err(Error::Option(format!(
"SafeSEH: {} is not compatible with SafeSEH (no `@feat.00` bit 0)",
file.display()
)));
}
return Ok(if referenced {
Plan::Zero
} else {
Plan::Nothing
});
}
let file = u32::try_from(index).unwrap_or(u32::MAX);
handlers.extend(parsed.sxdata.iter().map(|&record| (file, record)));
}
handlers.sort_unstable();
handlers.dedup();
Ok(Plan::Table(handlers))
}
#[must_use]
pub fn handler_rvas(addresses: &Addresses<'_, '_>, handlers: &[(u32, u32)]) -> Vec<u32> {
let mut rvas: Vec<u32> = handlers
.iter()
.filter_map(|&(file, record)| addresses.record_value(file as usize, record))
.filter_map(Value::rva)
.collect();
rvas.sort_unstable();
rvas.dedup();
rvas
}
#[must_use]
pub fn symbol_values(
symbols: &SymbolTable<'_>,
table: Option<Value>,
count: usize,
) -> HashMap<SymbolId, Value> {
let mut values = HashMap::default();
let count = u64::try_from(count).unwrap_or(0);
if let Some(id) = symbols.lookup(&SymbolName::new(TABLE_SYMBOL)) {
values.insert(id, table.unwrap_or(Value::Absolute(0)));
}
if let Some(id) = symbols.lookup(&SymbolName::new(COUNT_SYMBOL)) {
values.insert(id, Value::Absolute(count));
}
values
}
#[must_use]
pub fn encode(rvas: &[u32]) -> Vec<u8> {
rvas.iter().flat_map(|rva| rva.to_le_bytes()).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reserved_size_counts_entries() {
assert_eq!(Plan::Table(vec![(1, 2), (1, 3)]).reserved_size(), 8);
assert_eq!(Plan::Zero.reserved_size(), 0);
assert_eq!(encode(&[0x1000, 0x1010]), [0, 0x10, 0, 0, 0x10, 0x10, 0, 0]);
}
}