#![deny(clippy::arithmetic_side_effects)]
use crate::error::{Error, Result};
use super::read::consts::{
IMAGE_FILE_MACHINE_AMD64, IMAGE_FILE_MACHINE_ARM64, IMAGE_FILE_MACHINE_ARM64EC,
IMAGE_FILE_MACHINE_ARM64X, IMAGE_FILE_MACHINE_I386,
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Machine {
#[default]
Amd64,
I386,
Arm64,
}
impl Machine {
pub fn from_coff(machine: u16) -> Result<Self> {
match machine {
IMAGE_FILE_MACHINE_AMD64 => Ok(Self::Amd64),
IMAGE_FILE_MACHINE_I386 => Ok(Self::I386),
IMAGE_FILE_MACHINE_ARM64 => Ok(Self::Arm64),
IMAGE_FILE_MACHINE_ARM64EC | IMAGE_FILE_MACHINE_ARM64X => Err(Error::Unimplemented(
"ARM64EC and ARM64X images for PE/COFF (only plain ARM64 is supported)".into(),
)),
other => Err(Error::Unimplemented(format!(
"PE output for machine {other:#06x} (x86-64, i386 and ARM64 are supported)"
))),
}
}
#[must_use]
pub fn or_default(machine: u16) -> Self {
Self::from_coff(machine).unwrap_or(Self::Amd64)
}
#[must_use]
pub const fn coff(self) -> u16 {
match self {
Self::Amd64 => IMAGE_FILE_MACHINE_AMD64,
Self::I386 => IMAGE_FILE_MACHINE_I386,
Self::Arm64 => IMAGE_FILE_MACHINE_ARM64,
}
}
#[must_use]
pub const fn is_pe32(self) -> bool {
matches!(self, Self::I386)
}
#[must_use]
pub const fn pointer_size(self) -> u32 {
if self.is_pe32() { 4 } else { 8 }
}
#[must_use]
pub const fn underscores(self) -> bool {
self.is_pe32()
}
#[must_use]
pub fn decorate(self, name: &[u8]) -> Vec<u8> {
if self.underscores() && !matches!(name.first(), Some(b'@' | b'?')) {
let mut out = Vec::with_capacity(name.len().saturating_add(1));
out.push(b'_');
out.extend_from_slice(name);
out
} else {
name.to_vec()
}
}
#[must_use]
pub fn undecorate(self, name: &[u8]) -> &[u8] {
if self.underscores() {
name.strip_prefix(b"_").unwrap_or(name)
} else {
name
}
}
#[must_use]
pub const fn optional_header_size(self) -> usize {
if self.is_pe32() { 224 } else { 240 }
}
#[must_use]
pub const fn default_image_base(self, dll: bool) -> u64 {
match (self.is_pe32(), dll) {
(true, false) => 0x40_0000,
(true, true) => 0x1000_0000,
(false, false) => 0x1_4000_0000,
(false, true) => 0x1_8000_0000,
}
}
#[must_use]
pub const fn pointer_base_reloc(self) -> u16 {
if self.is_pe32() {
super::reloc::IMAGE_REL_BASED_HIGHLOW
} else {
super::reloc::IMAGE_REL_BASED_DIR64
}
}
#[must_use]
pub const fn pdata_entry_size(self) -> usize {
match self {
Self::Amd64 => 12,
Self::Arm64 => 8,
Self::I386 => 0,
}
}
#[must_use]
pub const fn tls_directory_size(self) -> u32 {
if self.is_pe32() { 24 } else { 40 }
}
#[must_use]
pub fn default_entry(self, dll: bool, gui: bool) -> Vec<u8> {
match (dll, gui) {
(true, _) if self.underscores() => b"_DllMainCRTStartup@12".to_vec(),
(true, _) => b"DllMainCRTStartup".to_vec(),
(false, true) => self.decorate(b"WinMainCRTStartup"),
(false, false) => self.decorate(b"mainCRTStartup"),
}
}
#[must_use]
pub const fn bfd_names(self) -> &'static [&'static str] {
match self {
Self::Amd64 => &["pei-x86-64", "pe-x86-64"],
Self::I386 => &["pei-i386", "pe-i386"],
Self::Arm64 => &["pei-aarch64-little", "pe-aarch64-little"],
}
}
}
#[must_use]
pub fn split_stdcall(name: &[u8]) -> (&[u8], Option<u32>) {
if name.first() == Some(&b'?') {
return (name, None);
}
let Some(at) = name.iter().rposition(|&byte| byte == b'@') else {
return (name, None);
};
if at == 0 {
return (name, None);
}
let digits = name.get(at.saturating_add(1)..).unwrap_or_default();
if digits.is_empty() || !digits.iter().all(u8::is_ascii_digit) {
return (name, None);
}
let value = std::str::from_utf8(digits)
.ok()
.and_then(|text| text.parse::<u32>().ok());
match value {
Some(value) => (name.get(..at).unwrap_or(name), Some(value)),
None => (name, None),
}
}
#[must_use]
pub fn kill_at(name: &[u8]) -> &[u8] {
match split_stdcall(name) {
(body, Some(_)) => body.strip_prefix(b"@").unwrap_or(body),
(_, None) => name,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decoration_round_trips() {
let i386 = Machine::I386;
assert_eq!(i386.decorate(b"main"), b"_main");
assert_eq!(i386.decorate(b"@fast@8"), b"@fast@8");
assert_eq!(i386.decorate(b"?cxx@@YAXXZ"), b"?cxx@@YAXXZ");
assert_eq!(i386.undecorate(b"_main"), b"main");
assert_eq!(i386.undecorate(b"@fast@8"), b"@fast@8");
assert_eq!(Machine::Amd64.decorate(b"main"), b"main");
assert_eq!(Machine::Arm64.undecorate(b"_main"), b"_main");
}
#[test]
fn stdcall_suffixes() {
assert_eq!(split_stdcall(b"_foo@8"), (&b"_foo"[..], Some(8)));
assert_eq!(split_stdcall(b"@foo@12"), (&b"@foo"[..], Some(12)));
assert_eq!(split_stdcall(b"_foo"), (&b"_foo"[..], None));
assert_eq!(split_stdcall(b"foo@bar"), (&b"foo@bar"[..], None));
assert_eq!(split_stdcall(b"?f@@YAXXZ"), (&b"?f@@YAXXZ"[..], None));
assert_eq!(kill_at(b"foo@8"), b"foo");
assert_eq!(kill_at(b"@foo@8"), b"foo");
assert_eq!(kill_at(b"plain"), b"plain");
}
#[test]
fn machine_properties() {
assert_eq!(
Machine::from_coff(IMAGE_FILE_MACHINE_I386).unwrap(),
Machine::I386
);
assert!(Machine::from_coff(IMAGE_FILE_MACHINE_ARM64EC).is_err());
assert!(Machine::from_coff(0x1c4).is_err());
assert_eq!(Machine::I386.default_image_base(false), 0x40_0000);
assert_eq!(
Machine::I386.default_entry(true, false),
b"_DllMainCRTStartup@12"
);
assert_eq!(
Machine::Arm64.default_entry(false, false),
b"mainCRTStartup"
);
assert_eq!(Machine::I386.optional_header_size(), 224);
}
}