#![deny(clippy::arithmetic_side_effects)]
use crate::error::Result;
use crate::symbols::{DefinitionKind, SymbolName, SymbolUse};
use super::read::consts::{
IMAGE_COMDAT_SELECT_ANY, IMAGE_COMDAT_SELECT_ASSOCIATIVE, IMAGE_SCN_LNK_INFO,
IMAGE_SYM_CLASS_EXTERNAL, IMAGE_SYM_CLASS_WEAK_EXTERNAL, IMAGE_WEAK_EXTERN_ANTI_DEPENDENCY,
IMAGE_WEAK_EXTERN_SEARCH_LIBRARY,
};
use super::read::{CoffObject, SectionHeader, SectionNumber, Symbol};
pub const SXDATA_SECTION: &[u8] = b".sxdata";
pub const DEFAULT_ALIGNMENT: u32 = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SectionKind {
Regular,
Directive,
Dropped,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Comdat<'a> {
pub selection: u8,
pub key: SymbolName<'a>,
pub associative: u32,
pub check_sum: u32,
pub length: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SynthReloc {
pub offset: u32,
pub r_type: u16,
pub target: u32,
}
#[derive(Clone, Debug)]
pub struct InputSection<'a> {
pub number: u32,
pub name: std::borrow::Cow<'a, [u8]>,
pub header: SectionHeader,
pub data: std::borrow::Cow<'a, [u8]>,
pub relocs: Vec<SynthReloc>,
pub synthetic: bool,
pub size: u32,
pub align: u32,
pub kind: SectionKind,
pub comdat: Option<Comdat<'a>>,
pub discarded: bool,
pub live: bool,
pub output: u32,
pub rva: u32,
}
impl InputSection<'_> {
#[must_use]
pub fn is_bss(&self) -> bool {
self.header.is_uninitialized_data() || self.header.pointer_to_raw_data == 0
}
#[must_use]
pub fn is_live(&self) -> bool {
self.kind == SectionKind::Regular && !self.discarded && self.live
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RecordTarget {
None,
Global(u32),
Local {
section: u32,
value: u32,
},
Absolute(u32),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GlobalKind {
Defined {
section: u32,
value: u32,
},
Absolute(u32),
Common(u32),
Undefined,
Weak {
tag: u32,
characteristics: u32,
},
}
#[derive(Clone, Copy, Debug)]
pub struct Global<'a> {
pub name: SymbolName<'a>,
pub record: u32,
pub kind: GlobalKind,
pub align_log2: u32,
pub discarded: bool,
}
#[derive(Debug)]
pub struct ParsedObject<'a> {
pub object: Option<CoffObject<'a>>,
pub sections: Vec<InputSection<'a>>,
pub globals: Vec<Global<'a>>,
pub names: Vec<SymbolName<'a>>,
pub uses: Vec<SymbolUse>,
pub targets: Vec<RecordTarget>,
pub directives: Vec<&'a [u8]>,
pub safe_seh: bool,
pub sxdata: Vec<u32>,
}
impl<'a> ParsedObject<'a> {
pub fn parse(object: CoffObject<'a>) -> Result<Self> {
let count = object.section_count();
let mut sections = Vec::with_capacity(count as usize);
let mut directives = Vec::new();
let mut sxdata = Vec::new();
for number in 1..=count {
let section = object.section(number)?;
let header = section.header;
let kind = classify(section.name, &header);
let data = if header.is_uninitialized_data() {
&[][..]
} else {
object.section_data(&header)?
};
if kind == SectionKind::Directive {
directives.push(data);
}
if section.name == SXDATA_SECTION {
let (words, _) = data.as_chunks::<4>();
sxdata.extend(words.iter().map(|word| u32::from_le_bytes(*word)));
}
sections.push(InputSection {
number,
name: std::borrow::Cow::Borrowed(section.name),
header,
data: std::borrow::Cow::Borrowed(data),
relocs: Vec::new(),
synthetic: false,
size: header.size_of_raw_data,
align: header.alignment().unwrap_or(DEFAULT_ALIGNMENT).min(8192),
kind,
comdat: None,
discarded: false,
live: true,
output: u32::MAX,
rva: 0,
});
}
let records = object.symbols().len();
let mut targets = vec![RecordTarget::None; records as usize];
let mut globals = Vec::new();
let mut selections: Vec<Option<(u8, u32, u32, u32)>> = vec![None; sections.len()];
let mut symbols = Vec::new();
for symbol in object.symbols().iter() {
let symbol = symbol?;
if let Some(definition) = symbol.section_definition()
&& let SectionNumber::Section(number) = symbol.section()
&& let Some(slot) = index_of(&mut selections, number)
&& slot.is_none()
{
*slot = Some((
definition.selection,
definition.number,
definition.check_sum,
definition.length,
));
}
symbols.push(symbol);
}
for symbol in &symbols {
let record = symbol.index;
let target = match classify_symbol(symbol) {
Some(kind) => {
let index = u32::try_from(globals.len()).unwrap_or(u32::MAX);
globals.push(Global {
name: SymbolName::new(symbol.name),
record,
kind,
align_log2: 0,
discarded: false,
});
RecordTarget::Global(index)
}
None => match symbol.section() {
SectionNumber::Section(number) => RecordTarget::Local {
section: number,
value: symbol.value,
},
SectionNumber::Absolute => RecordTarget::Absolute(symbol.value),
_ => RecordTarget::None,
},
};
if let Some(slot) = targets.get_mut(record as usize) {
*slot = target;
}
}
for section in &mut sections {
if !section.header.is_comdat() {
continue;
}
let Some(Some((selection, number, check_sum, length))) = selections
.get(section.number.wrapping_sub(1) as usize)
.copied()
else {
continue;
};
let Some(key) = comdat_key(&symbols, section.number) else {
continue;
};
section.comdat = Some(Comdat {
selection: if selection == 0 {
IMAGE_COMDAT_SELECT_ANY
} else {
selection
},
key: SymbolName::new(key),
associative: if selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE {
number
} else {
0
},
check_sum,
length,
});
}
let names = globals.iter().map(|global| global.name).collect();
let uses = globals.iter().map(|global| symbol_use(global)).collect();
let safe_seh = object.feat00()?.is_some_and(super::read::Feat00::safe_seh);
Ok(Self {
object: Some(object),
sections,
globals,
names,
uses,
targets,
directives,
safe_seh,
sxdata,
})
}
#[must_use]
pub fn safe_seh_compatible(&self) -> bool {
if self.safe_seh {
return true;
}
let live = || self.sections.iter().filter(|section| section.is_live());
let has_code = live().any(|section| section.header.is_code() && section.size > 0);
let import_member = live().any(|section| section.name.starts_with(b".idata$"));
!has_code || import_member
}
#[must_use]
pub fn section(&self, number: u32) -> Option<&InputSection<'a>> {
self.sections.get(number.checked_sub(1)? as usize)
}
pub fn discard_lost_comdats(&mut self, keeps: &dyn Fn(&SymbolName<'a>) -> bool) {
let mut any = false;
for index in 0..self.sections.len() {
let Some(comdat) = self.sections[index].comdat else {
continue;
};
let discarded = if comdat.selection == IMAGE_COMDAT_SELECT_ASSOCIATIVE {
self.section(comdat.associative)
.is_some_and(|parent| parent.discarded)
} else {
!keeps(&comdat.key)
};
if discarded {
self.sections[index].discarded = true;
any = true;
}
}
if !any {
return;
}
for (index, global) in self.globals.iter_mut().enumerate() {
let GlobalKind::Defined { section, .. } = global.kind else {
continue;
};
let lost = section
.checked_sub(1)
.and_then(|i| self.sections.get(i as usize))
.is_some_and(|section| section.discarded);
if lost {
global.discarded = true;
if let Some(slot) = self.uses.get_mut(index) {
*slot = SymbolUse::Ignore;
}
}
}
}
pub fn align_common(&mut self, name: &[u8], align_log2: u32) {
for global in &mut self.globals {
if global.name.bytes() == name && matches!(global.kind, GlobalKind::Common(_)) {
global.align_log2 = global.align_log2.max(align_log2);
}
}
}
}
fn index_of<T>(slots: &mut [T], number: u32) -> Option<&mut T> {
slots.get_mut(number.checked_sub(1)? as usize)
}
fn comdat_key<'a>(symbols: &[Symbol<'a>], number: u32) -> Option<&'a [u8]> {
symbols
.iter()
.find(|symbol| {
symbol.section() == SectionNumber::Section(number)
&& matches!(
symbol.storage_class,
IMAGE_SYM_CLASS_EXTERNAL | IMAGE_SYM_CLASS_WEAK_EXTERNAL
)
})
.map(|symbol| symbol.name)
}
fn classify(name: &[u8], header: &SectionHeader) -> SectionKind {
if name == super::read::DRECTVE_SECTION {
return SectionKind::Directive;
}
if header.is_remove() || header.has(IMAGE_SCN_LNK_INFO) {
return SectionKind::Dropped;
}
let dropped = name.starts_with(b".debug$")
|| name.starts_with(b".gnu.lto_")
|| name == super::read::ADDRSIG_SECTION
|| name == b".gnu_object_only"
|| name == b".note.GNU-stack"
|| name == b".chks64";
if dropped {
SectionKind::Dropped
} else {
SectionKind::Regular
}
}
fn classify_symbol(symbol: &Symbol<'_>) -> Option<GlobalKind> {
if symbol.is_weak_external() {
let aux = symbol.weak_external().unwrap_or_default();
return Some(GlobalKind::Weak {
tag: aux.tag_index,
characteristics: aux.characteristics,
});
}
if !symbol.is_external() {
return None;
}
Some(match symbol.section() {
SectionNumber::Section(number) => GlobalKind::Defined {
section: number,
value: symbol.value,
},
SectionNumber::Absolute => GlobalKind::Absolute(symbol.value),
SectionNumber::Undefined if symbol.value != 0 => GlobalKind::Common(symbol.value),
SectionNumber::Undefined => GlobalKind::Undefined,
SectionNumber::Debug | SectionNumber::Reserved(_) => return None,
})
}
fn symbol_use(global: &Global<'_>) -> SymbolUse {
match global.kind {
GlobalKind::Defined { .. } | GlobalKind::Absolute(_) => SymbolUse::Definition {
kind: DefinitionKind::Regular,
aux: 0,
},
GlobalKind::Common(size) => SymbolUse::Definition {
kind: DefinitionKind::Common,
aux: u64::from(size),
},
GlobalKind::Undefined => SymbolUse::Reference { weak: false },
GlobalKind::Weak {
characteristics, ..
} => SymbolUse::Reference {
weak: characteristics != IMAGE_WEAK_EXTERN_SEARCH_LIBRARY
|| characteristics == IMAGE_WEAK_EXTERN_ANTI_DEPENDENCY,
},
}
}