Skip to content

Add PE support for x86_64 #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ gimli = "0.28.1"
object = { version = "0.32", optional = true }
thiserror = "1.0.30"
macho-unwind-info = "0.3.0"
pe-unwind-info = "0.1.0"
fallible-iterator = "0.3.0"
arrayvec = "0.7.4"

[dev-dependencies]
object = "0.32"
Expand Down
2 changes: 2 additions & 0 deletions src/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod cache;
mod dwarf;
mod instruction_analysis;
mod macho;
mod pe;
mod unwind_rule;
mod unwinder;
mod unwindregs;
Expand All @@ -12,6 +13,7 @@ pub use cache::*;
pub use dwarf::*;
pub use instruction_analysis::*;
pub use macho::*;
pub use pe::*;
pub use unwind_rule::*;
pub use unwinder::*;
pub use unwindregs::*;
18 changes: 18 additions & 0 deletions src/aarch64/pe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use super::arch::ArchAarch64;
use crate::pe::{PeSections, PeUnwinderError, PeUnwinding};
use crate::unwind_result::UnwindResult;

impl PeUnwinding for ArchAarch64 {
fn unwind_frame<F, D>(
_sections: PeSections<D>,
_address: u32,
_regs: &mut Self::UnwindRegs,
_read_stack: &mut F,
) -> Result<UnwindResult<Self::UnwindRule>, PeUnwinderError>
where
F: FnMut(u64) -> Result<u64, ()>,
D: std::ops::Deref<Target = [u8]>,
{
Err(PeUnwinderError::Aarch64Unsupported)
}
}
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::dwarf::DwarfUnwinderError;
use crate::macho::CompactUnwindInfoUnwinderError;
use crate::pe::PeUnwinderError;

/// The error type used in this crate.
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -28,6 +29,9 @@ pub enum UnwinderError {
#[error("DWARF unwinding failed: {0}")]
Dwarf(#[from] DwarfUnwinderError),

#[error("PE unwinding failed: {0}")]
Pe(#[from] PeUnwinderError),

#[error("__unwind_info referred to DWARF FDE but we do not have __eh_frame data")]
NoDwarfData,

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ mod dwarf;
mod error;
mod instruction_analysis;
mod macho;
mod pe;
mod rule_cache;
mod unwind_result;
mod unwind_rule;
Expand Down
79 changes: 79 additions & 0 deletions src/pe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use crate::{arch::Arch, unwind_result::UnwindResult};
use std::ops::Range;

#[derive(thiserror::Error, Clone, Copy, Debug, PartialEq, Eq)]
pub enum PeUnwinderError {
#[error("failed to read unwind info memory at RVA {0:x}")]
MissingUnwindInfoData(u32),
#[error("failed to read instruction memory at RVA {0:x}")]
MissingInstructionData(u32),
#[error("failed to read stack{}", .0.map(|a| format!(" at address {a:x}")).unwrap_or_default())]
MissingStackData(Option<u64>),
#[error("failed to parse UnwindInfo")]
UnwindInfoParseError,
#[error("AArch64 is not yet supported")]
Aarch64Unsupported,
}

/// Data and the related RVA range within the binary.
///
/// This is only used by PE unwinding.
///
/// Type arguments:
/// - `D`: The type for unwind section data. This allows carrying owned data on the
/// module, e.g. `Vec<u8>`. But it could also be a wrapper around mapped memory from
/// a file or a different process, for example. It just needs to provide a slice of
/// bytes via its `Deref` implementation.
pub struct DataAtRvaRange<D> {
pub data: D,
pub rva_range: Range<u32>,
}

pub struct PeSections<'a, D> {
pub pdata: &'a D,
pub rdata: Option<&'a DataAtRvaRange<D>>,
pub xdata: Option<&'a DataAtRvaRange<D>>,
pub text: Option<&'a DataAtRvaRange<D>>,
}

impl<'a, D> PeSections<'a, D>
where
D: std::ops::Deref<Target = [u8]>,
{
pub fn unwind_info_memory_at_rva(&self, rva: u32) -> Result<&'a [u8], PeUnwinderError> {
[&self.rdata, &self.xdata]
.into_iter()
.find_map(|o| o.and_then(|m| memory_at_rva(m, rva)))
.ok_or(PeUnwinderError::MissingUnwindInfoData(rva))
}

pub fn text_memory_at_rva(&self, rva: u32) -> Result<&'a [u8], PeUnwinderError> {
self.text
.and_then(|m| memory_at_rva(m, rva))
.ok_or(PeUnwinderError::MissingInstructionData(rva))
}
}

fn memory_at_rva<D: std::ops::Deref<Target = [u8]>>(
DataAtRvaRange { data, rva_range }: &DataAtRvaRange<D>,
address: u32,
) -> Option<&[u8]> {
if rva_range.contains(&address) {
let offset = address - rva_range.start;
Some(&data[(offset as usize)..])
} else {
None
}
}

pub trait PeUnwinding: Arch {
fn unwind_frame<F, D>(
sections: PeSections<D>,
address: u32,
regs: &mut Self::UnwindRegs,
read_stack: &mut F,
) -> Result<UnwindResult<Self::UnwindRule>, PeUnwinderError>
where
F: FnMut(u64) -> Result<u64, ()>,
D: std::ops::Deref<Target = [u8]>;
}
47 changes: 44 additions & 3 deletions src/unwinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::instruction_analysis::InstructionAnalysis;
use crate::macho::{
CompactUnwindInfoUnwinder, CompactUnwindInfoUnwinding, CuiUnwindResult, TextBytes,
};
use crate::pe::{DataAtRvaRange, PeUnwinding};
use crate::rule_cache::CacheResult;
use crate::unwind_result::UnwindResult;
use crate::unwind_rule::UnwindRule;
Expand Down Expand Up @@ -201,7 +202,7 @@ fn next_global_modules_generation() -> u16 {

pub struct UnwinderInternal<
D: Deref<Target = [u8]>,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + InstructionAnalysis,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + PeUnwinding + InstructionAnalysis,
P: AllocationPolicy<D>,
> {
/// sorted by avma_range.start
Expand All @@ -214,7 +215,7 @@ pub struct UnwinderInternal<

impl<
D: Deref<Target = [u8]>,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + InstructionAnalysis,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + PeUnwinding + InstructionAnalysis,
P: AllocationPolicy<D>,
> Default for UnwinderInternal<D, A, P>
{
Expand All @@ -225,7 +226,7 @@ impl<

impl<
D: Deref<Target = [u8]>,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + InstructionAnalysis,
A: Arch + DwarfUnwinding + CompactUnwindInfoUnwinding + PeUnwinding + InstructionAnalysis,
P: AllocationPolicy<D>,
> UnwinderInternal<D, A, P>
{
Expand Down Expand Up @@ -522,6 +523,22 @@ impl<
read_stack,
)?
}
ModuleUnwindDataInternal::PeUnwindInfo {
pdata,
rdata,
xdata,
text,
} => <A as PeUnwinding>::unwind_frame(
crate::pe::PeSections {
pdata,
rdata: rdata.as_ref(),
xdata: xdata.as_ref(),
text: text.as_ref(),
},
rel_lookup_address,
regs,
read_stack,
)?,
ModuleUnwindDataInternal::None => return Err(UnwinderError::NoModuleUnwindData),
};
Ok(unwind_result)
Expand Down Expand Up @@ -575,6 +592,13 @@ enum ModuleUnwindDataInternal<D: Deref<Target = [u8]>> {
debug_frame: Arc<D>,
base_addresses: crate::dwarf::BaseAddresses,
},
/// Used with PE binaries (Windows).
PeUnwindInfo {
pdata: D,
rdata: Option<DataAtRvaRange<D>>,
xdata: Option<DataAtRvaRange<D>>,
text: Option<DataAtRvaRange<D>>,
},
/// No unwind information is used. Unwinding in this module will use a fallback rule
/// (usually frame pointer unwinding).
None,
Expand Down Expand Up @@ -611,6 +635,23 @@ impl<D: Deref<Target = [u8]>> ModuleUnwindDataInternal<D> {
base_addresses: base_addresses_for_sections(section_info),
text_data,
}
} else if let Some(pdata) = section_info.section_data(b".pdata") {
let mut range_and_data = |name| {
let rva_range = section_info.section_svma_range(name).and_then(|range| {
Some(Range {
start: (range.start - section_info.base_svma()).try_into().ok()?,
end: (range.end - section_info.base_svma()).try_into().ok()?,
})
})?;
let data = section_info.section_data(name)?;
Some(DataAtRvaRange { data, rva_range })
};
ModuleUnwindDataInternal::PeUnwindInfo {
pdata,
rdata: range_and_data(b".rdata"),
xdata: range_and_data(b".xdata"),
text: range_and_data(b".text"),
}
} else if let Some(eh_frame) = section_info
.section_data(b".eh_frame")
.or_else(|| section_info.section_data(b"__eh_frame"))
Expand Down
2 changes: 2 additions & 0 deletions src/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod cache;
mod dwarf;
mod instruction_analysis;
mod macho;
mod pe;
mod unwind_rule;
mod unwinder;
mod unwindregs;
Expand All @@ -12,6 +13,7 @@ pub use cache::*;
pub use dwarf::*;
pub use instruction_analysis::*;
pub use macho::*;
pub use pe::*;
pub use unwind_rule::*;
pub use unwinder::*;
pub use unwindregs::*;
Loading