use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::io::Write;
use gimli::RunTimeEndian;
use goblin::pe;
use scroll::{Pread, LE};
use thiserror::Error;
use symbolic_common::{Arch, AsSelf, CodeId, DebugId};
use crate::base::*;
use crate::dwarf::*;
pub use goblin::pe::exception::*;
pub use goblin::pe::section_table::SectionTable;
#[derive(Debug, Error)]
#[error("invalid PE file")]
pub struct PeError {
#[source]
source: Option<Box<dyn Error + Send + Sync + 'static>>,
}
impl PeError {
fn new<E>(source: E) -> Self
where
E: Into<Box<dyn Error + Send + Sync>>,
{
let source = Some(source.into());
Self { source }
}
}
fn is_pe_stub(pe: &pe::PE<'_>) -> bool {
let mut has_stub = false;
let mut pdata_empty = false;
for section in &pe.sections {
let name = section.name().unwrap_or_default();
pdata_empty = pdata_empty || name == ".pdata" && section.size_of_raw_data == 0;
has_stub = has_stub || name.starts_with(".stub");
}
pdata_empty && has_stub
}
pub struct PeObject<'data> {
pe: pe::PE<'data>,
data: &'data [u8],
is_stub: bool,
}
impl<'data> PeObject<'data> {
pub fn test(data: &[u8]) -> bool {
matches!(
data.get(0..2)
.and_then(|data| data.pread_with::<u16>(0, LE).ok()),
Some(pe::header::DOS_MAGIC)
)
}
pub fn parse(data: &'data [u8]) -> Result<Self, PeError> {
let pe = pe::PE::parse(data).map_err(PeError::new)?;
let is_stub = is_pe_stub(&pe);
Ok(PeObject { pe, data, is_stub })
}
pub fn file_format(&self) -> FileFormat {
FileFormat::Pe
}
pub fn code_id(&self) -> Option<CodeId> {
let header = &self.pe.header;
let optional_header = header.optional_header.as_ref()?;
let timestamp = header.coff_header.time_date_stamp;
let size_of_image = optional_header.windows_fields.size_of_image;
let string = format!("{timestamp:08x}{size_of_image:x}");
Some(CodeId::new(string))
}
pub fn debug_id(&self) -> DebugId {
self.pe
.debug_data
.as_ref()
.and_then(|debug_data| {
debug_data
.codeview_pdb70_debug_info
.as_ref()
.map(|cv_record| (debug_data.image_debug_directory, cv_record))
})
.and_then(|(debug_directory, cv_record)| {
let guid = &cv_record.signature;
let age = if debug_directory.minor_version == 0x504d {
debug_directory.time_date_stamp
} else {
cv_record.age
};
DebugId::from_guid_age(guid, age).ok()
})
.unwrap_or_default()
}
pub fn debug_file_name(&self) -> Option<Cow<'_, str>> {
self.pe
.debug_data
.as_ref()
.and_then(|debug_data| debug_data.codeview_pdb70_debug_info.as_ref())
.and_then(|debug_info| {
debug_info
.filename
.iter()
.position(|&c| c == 0)
.map(|nul_byte| String::from_utf8_lossy(&debug_info.filename[..nul_byte]))
})
}
pub fn arch(&self) -> Arch {
let machine = self.pe.header.coff_header.machine;
crate::pdb::arch_from_machine(machine.into())
}
pub fn kind(&self) -> ObjectKind {
if self.pe.is_lib {
ObjectKind::Library
} else if self.is_stub {
ObjectKind::Other
} else {
ObjectKind::Executable
}
}
pub fn load_address(&self) -> u64 {
self.pe.image_base as u64
}
pub fn has_symbols(&self) -> bool {
!self.pe.exports.is_empty()
}
pub fn symbols(&self) -> PeSymbolIterator<'data, '_> {
PeSymbolIterator {
exports: self.pe.exports.iter(),
}
}
pub fn symbol_map(&self) -> SymbolMap<'data> {
self.symbols().collect()
}
pub fn has_debug_info(&self) -> bool {
self.section(".debug_info").is_some()
}
pub fn has_sources(&self) -> bool {
false
}
pub fn is_malformed(&self) -> bool {
false
}
pub fn debug_session(&self) -> Result<DwarfDebugSession<'data>, DwarfError> {
let symbols = self.symbol_map();
DwarfDebugSession::parse(self, symbols, self.load_address() as i64, self.kind())
}
pub fn has_unwind_info(&self) -> bool {
!self.is_stub && self.exception_data().is_some_and(|e| !e.is_empty())
}
pub fn data(&self) -> &'data [u8] {
self.data
}
pub fn sections(&self) -> &[SectionTable] {
&self.pe.sections
}
pub fn section(&self, name: &str) -> Option<SectionTable> {
for s in &self.pe.sections {
let sect_name = s.name();
if sect_name.is_ok() && sect_name.unwrap() == name {
return Some(s.clone());
}
}
None
}
pub fn exception_data(&self) -> Option<&ExceptionData<'_>> {
if self.is_stub {
None
} else {
self.pe.exception_data.as_ref()
}
}
pub fn embedded_ppdb(&self) -> Result<Option<PeEmbeddedPortablePDB<'data>>, PeError> {
let Some(opt_header) = self.pe.header.optional_header else {
return Ok(None);
};
let Some(debug_directory) = opt_header.data_directories.get_debug_table() else {
return Ok(None);
};
let file_alignment = opt_header.windows_fields.file_alignment;
let parse_options = &pe::options::ParseOptions::default();
let Some(offset) = pe::utils::find_offset(
debug_directory.virtual_address as usize,
&self.pe.sections,
file_alignment,
parse_options,
) else {
return Ok(None);
};
use pe::debug::ImageDebugDirectory;
let entries = debug_directory.size as usize / std::mem::size_of::<ImageDebugDirectory>();
for i in 0..entries {
let entry = offset + i * std::mem::size_of::<ImageDebugDirectory>();
let idd: ImageDebugDirectory = self.data.pread_with(entry, LE).map_err(PeError::new)?;
if idd.data_type == 17 {
if idd.size_of_data < 8 {
return Err(PeError::new(symbolic_ppdb::FormatError::from(
symbolic_ppdb::FormatErrorKind::InvalidLength,
)));
}
let mut offset: usize = match parse_options.resolve_rva {
true => idd.pointer_to_raw_data as usize,
false => idd.address_of_raw_data as usize,
};
let mut signature: [u8; 4] = [0; 4];
self.data
.gread_inout(&mut offset, &mut signature)
.map_err(PeError::new)?;
if signature != "MPDB".as_bytes() {
return Err(PeError::new(symbolic_ppdb::FormatError::from(
symbolic_ppdb::FormatErrorKind::InvalidSignature,
)));
}
let uncompressed_size: u32 = self
.data
.gread_with(&mut offset, LE)
.map_err(PeError::new)?;
let compressed_size = idd.size_of_data as usize - 8;
return Ok(Some(PeEmbeddedPortablePDB {
compressed_data: self
.data
.get(offset..(offset + compressed_size))
.ok_or_else(|| {
PeError::new(symbolic_ppdb::FormatError::from(
symbolic_ppdb::FormatErrorKind::InvalidBlobOffset,
))
})?,
uncompressed_size: uncompressed_size as usize,
}));
}
}
Ok(None)
}
}
impl fmt::Debug for PeObject<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PeObject")
.field("code_id", &self.code_id())
.field("debug_id", &self.debug_id())
.field("debug_file_name", &self.debug_file_name())
.field("arch", &self.arch())
.field("kind", &self.kind())
.field("load_address", &format_args!("{:#x}", self.load_address()))
.field("has_symbols", &self.has_symbols())
.field("has_debug_info", &self.has_debug_info())
.field("has_unwind_info", &self.has_unwind_info())
.field("is_malformed", &self.is_malformed())
.finish()
}
}
impl<'slf, 'data: 'slf> AsSelf<'slf> for PeObject<'data> {
type Ref = PeObject<'slf>;
fn as_self(&'slf self) -> &'slf Self::Ref {
self
}
}
impl<'data> Parse<'data> for PeObject<'data> {
type Error = PeError;
fn test(data: &[u8]) -> bool {
Self::test(data)
}
fn parse(data: &'data [u8]) -> Result<Self, PeError> {
Self::parse(data)
}
}
impl<'data: 'object, 'object> ObjectLike<'data, 'object> for PeObject<'data> {
type Error = DwarfError;
type Session = DwarfDebugSession<'data>;
type SymbolIterator = PeSymbolIterator<'data, 'object>;
fn file_format(&self) -> FileFormat {
self.file_format()
}
fn code_id(&self) -> Option<CodeId> {
self.code_id()
}
fn debug_id(&self) -> DebugId {
self.debug_id()
}
fn arch(&self) -> Arch {
self.arch()
}
fn kind(&self) -> ObjectKind {
self.kind()
}
fn load_address(&self) -> u64 {
self.load_address()
}
fn has_symbols(&self) -> bool {
self.has_symbols()
}
fn symbols(&'object self) -> Self::SymbolIterator {
self.symbols()
}
fn symbol_map(&self) -> SymbolMap<'data> {
self.symbol_map()
}
fn has_debug_info(&self) -> bool {
self.has_debug_info()
}
fn debug_session(&self) -> Result<Self::Session, Self::Error> {
self.debug_session()
}
fn has_unwind_info(&self) -> bool {
self.has_unwind_info()
}
fn has_sources(&self) -> bool {
self.has_sources()
}
fn is_malformed(&self) -> bool {
self.is_malformed()
}
}
pub struct PeSymbolIterator<'data, 'object> {
exports: std::slice::Iter<'object, pe::export::Export<'data>>,
}
impl<'data> Iterator for PeSymbolIterator<'data, '_> {
type Item = Symbol<'data>;
fn next(&mut self) -> Option<Self::Item> {
self.exports.next().map(|export| Symbol {
name: export.name.map(Cow::Borrowed),
address: export.rva as u64,
size: export.size as u64,
})
}
}
impl<'data> Dwarf<'data> for PeObject<'data> {
fn endianity(&self) -> RunTimeEndian {
RunTimeEndian::Little
}
fn raw_section(&self, name: &str) -> Option<DwarfSection<'data>> {
let sect = self.section(&format!(".{name}"))?;
let start = sect.pointer_to_raw_data as usize;
let end = start + (sect.virtual_size as usize);
let dwarf_data: &'data [u8] = self.data.get(start..end)?;
let dwarf_sect = DwarfSection {
address: u64::from(sect.virtual_address),
data: Cow::from(dwarf_data),
offset: u64::from(sect.pointer_to_raw_data),
align: 4096, };
Some(dwarf_sect)
}
}
#[derive(Debug, Clone)]
pub struct PeEmbeddedPortablePDB<'data> {
compressed_data: &'data [u8],
uncompressed_size: usize,
}
impl PeEmbeddedPortablePDB<'_> {
pub fn get_size(&self) -> usize {
self.uncompressed_size
}
pub fn decompress_to<W: Write>(&self, output: W) -> Result<(), PeError> {
use std::io::prelude::*;
let mut decoder = flate2::write::DeflateDecoder::new(output);
decoder
.write_all(self.compressed_data)
.and_then(|_| decoder.finish())
.map_err(PeError::new)?;
Ok(())
}
}