use std::path::PathBuf;
use crate::args::{LinkOptions, OutputKind, StripMode};
use crate::error::{Error, Result};
use super::machine::Machine;
use super::read::consts::{
IMAGE_FILE_MACHINE_AMD64, IMAGE_SUBSYSTEM_NATIVE, IMAGE_SUBSYSTEM_WINDOWS_CUI,
IMAGE_SUBSYSTEM_WINDOWS_GUI,
};
pub const DEFAULT_IMAGE_BASE_EXE: u64 = 0x1_4000_0000;
pub const DEFAULT_IMAGE_BASE_DLL: u64 = 0x1_8000_0000;
pub const DEFAULT_SECTION_ALIGNMENT: u32 = 0x1000;
pub const DEFAULT_FILE_ALIGNMENT: u32 = 0x200;
pub const DEFAULT_STACK_RESERVE: u64 = 0x20_0000;
pub const DEFAULT_STACK_COMMIT: u64 = 0x1000;
pub const DEFAULT_HEAP_RESERVE: u64 = 0x10_0000;
pub const DEFAULT_HEAP_COMMIT: u64 = 0x1000;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Version {
pub major: u16,
pub minor: u16,
}
impl Version {
#[must_use]
pub const fn new(major: u16, minor: u16) -> Self {
Self { major, minor }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum AutoImport {
Disabled,
#[default]
Enabled,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct PeOptions {
pub machine: u16,
pub dll: bool,
pub image_base: Option<u64>,
pub section_alignment: u32,
pub file_alignment: u32,
pub subsystem: Option<u16>,
pub subsystem_version: Version,
pub os_version: Version,
pub image_version: Version,
pub stack: (u64, u64),
pub heap: (u64, u64),
pub dynamicbase: bool,
pub nxcompat: bool,
pub high_entropy_va: bool,
pub tsaware: bool,
pub no_seh: bool,
pub forceinteg: bool,
pub no_isolation: bool,
pub no_bind: bool,
pub wdmdriver: bool,
pub large_address_aware: bool,
pub disable_reloc_section: bool,
pub insert_timestamp: bool,
pub out_implib: Option<PathBuf>,
pub output_def: Option<PathBuf>,
pub def_file: Option<PathBuf>,
pub export_all_symbols: bool,
pub exclude_all_symbols: bool,
pub exclude_symbols: Vec<Vec<u8>>,
pub kill_at: bool,
pub add_stdcall_alias: bool,
pub enable_stdcall_fixup: Option<bool>,
pub auto_import: AutoImport,
pub runtime_pseudo_reloc: bool,
pub no_default_lib: bool,
pub no_default_libs: Vec<Vec<u8>>,
pub exclude_modules_for_implib: Vec<Vec<u8>>,
pub implib_dll_name: Option<Vec<u8>>,
pub exports: Vec<Vec<u8>>,
pub warn_duplicate_exports: bool,
pub safe_seh: Option<bool>,
}
impl Default for PeOptions {
fn default() -> Self {
Self::for_machine(IMAGE_FILE_MACHINE_AMD64)
}
}
impl PeOptions {
#[must_use]
pub fn for_machine(machine: u16) -> Self {
let target = Machine::or_default(machine);
let (os_version, image_version, subsystem_version) = match target {
Machine::Amd64 => (Version::new(4, 0), Version::new(0, 0), Version::new(5, 2)),
Machine::I386 => (Version::new(4, 0), Version::new(1, 0), Version::new(4, 0)),
Machine::Arm64 => (Version::new(6, 0), Version::new(0, 0), Version::new(6, 0)),
};
Self {
machine,
dll: false,
image_base: None,
section_alignment: DEFAULT_SECTION_ALIGNMENT,
file_alignment: DEFAULT_FILE_ALIGNMENT,
subsystem: None,
subsystem_version,
os_version,
image_version,
stack: (DEFAULT_STACK_RESERVE, DEFAULT_STACK_COMMIT),
heap: (DEFAULT_HEAP_RESERVE, DEFAULT_HEAP_COMMIT),
dynamicbase: true,
nxcompat: true,
high_entropy_va: true,
tsaware: false,
no_seh: false,
forceinteg: false,
no_isolation: false,
no_bind: false,
wdmdriver: false,
large_address_aware: !target.is_pe32(),
disable_reloc_section: false,
insert_timestamp: false,
out_implib: None,
output_def: None,
def_file: None,
export_all_symbols: false,
exclude_all_symbols: false,
exclude_symbols: Vec::new(),
kill_at: false,
add_stdcall_alias: false,
enable_stdcall_fixup: None,
auto_import: AutoImport::Enabled,
runtime_pseudo_reloc: true,
no_default_lib: false,
no_default_libs: Vec::new(),
exclude_modules_for_implib: Vec::new(),
implib_dll_name: None,
exports: Vec::new(),
warn_duplicate_exports: false,
safe_seh: None,
}
}
#[must_use]
pub fn target(&self) -> Machine {
Machine::or_default(self.machine)
}
#[must_use]
pub fn from_link_options(options: &LinkOptions) -> Self {
let pe = &options.pe;
let dll = options.kind == OutputKind::Shared;
let names = |list: &[String]| list.iter().map(|name| name.as_bytes().to_vec()).collect();
let machine = options
.target
.and_then(|target| super::machine_for(target.arch))
.unwrap_or(IMAGE_FILE_MACHINE_AMD64);
let base = Self::for_machine(machine);
let explicit = pe.explicit;
let pick = |given: bool, value: Version, default: Version| {
if given { value } else { default }
};
Self {
machine,
dll,
image_base: options.image_base,
section_alignment: pe.section_alignment,
file_alignment: pe.file_alignment,
subsystem: pe.subsystem,
subsystem_version: pick(
explicit.subsystem_version,
Version::new(pe.major_subsystem_version, pe.minor_subsystem_version),
base.subsystem_version,
),
os_version: pick(
explicit.os_version,
Version::new(pe.major_os_version, pe.minor_os_version),
base.os_version,
),
image_version: pick(
explicit.image_version,
Version::new(pe.major_image_version, pe.minor_image_version),
base.image_version,
),
stack: pe.stack,
heap: pe.heap,
dynamicbase: pe.dynamicbase,
nxcompat: pe.nxcompat,
high_entropy_va: pe.high_entropy_va,
tsaware: pe.tsaware,
no_seh: pe.no_seh,
forceinteg: pe.forceinteg,
no_isolation: pe.no_isolation,
no_bind: pe.no_bind,
wdmdriver: pe.wdmdriver,
large_address_aware: if explicit.large_address_aware {
pe.large_address_aware
} else {
base.large_address_aware
},
disable_reloc_section: !pe.reloc_section,
insert_timestamp: pe.insert_timestamp,
out_implib: pe.out_implib.clone(),
output_def: pe.output_def.clone(),
def_file: pe.def_file.clone(),
export_all_symbols: pe.export_all_symbols || (dll && options.export_dynamic),
exclude_all_symbols: pe.exclude_all_symbols,
exclude_symbols: names(&pe.exclude_symbols),
kill_at: pe.kill_at,
add_stdcall_alias: pe.add_stdcall_alias,
enable_stdcall_fixup: pe.stdcall_fixup,
auto_import: if pe.auto_import {
AutoImport::Enabled
} else {
AutoImport::Disabled
},
runtime_pseudo_reloc: pe.runtime_pseudo_reloc,
no_default_lib: options.nostdlib,
no_default_libs: Vec::new(),
exclude_modules_for_implib: names(&pe.exclude_modules_for_implib),
implib_dll_name: None,
exports: names(&pe.exports),
warn_duplicate_exports: pe.warn_duplicate_exports,
safe_seh: None,
}
}
#[must_use]
pub fn effective_image_base(&self) -> u64 {
self.image_base
.unwrap_or_else(|| self.target().default_image_base(self.dll))
}
pub fn validate(&self) -> Result<()> {
let bad = |what: &str, value: u64| Error::Option(format!("invalid {what}: {value:#x}"));
if !self.file_alignment.is_power_of_two()
|| !(512..=0x1_0000).contains(&self.file_alignment)
{
return Err(bad("--file-alignment", u64::from(self.file_alignment)));
}
if !self.section_alignment.is_power_of_two() {
return Err(bad(
"--section-alignment",
u64::from(self.section_alignment),
));
}
if self.section_alignment < self.file_alignment {
return Err(Error::Option(
"--section-alignment must be at least --file-alignment".into(),
));
}
if !self.effective_image_base().is_multiple_of(0x1_0000) {
return Err(bad("--image-base", self.effective_image_base()));
}
let machine = Machine::from_coff(self.machine)?;
if machine.is_pe32() && u32::try_from(self.effective_image_base()).is_err() {
return Err(bad("--image-base", self.effective_image_base()));
}
if machine == Machine::Arm64 && !self.dynamicbase {
return Err(Error::Option(
"--disable-dynamicbase is not compatible with ARM64".into(),
));
}
Ok(())
}
#[must_use]
pub fn keep_symbols(options: &LinkOptions) -> bool {
options.strip < StripMode::All
}
}
pub fn parse_subsystem(value: &str) -> Result<(u16, Option<Version>)> {
let (name, version) = match value.split_once([',', ':']) {
Some((name, version)) => (name, Some(version)),
None => (value, None),
};
let subsystem = match name.to_ascii_lowercase().as_str() {
"console" => IMAGE_SUBSYSTEM_WINDOWS_CUI,
"windows" => IMAGE_SUBSYSTEM_WINDOWS_GUI,
"native" => IMAGE_SUBSYSTEM_NATIVE,
"posix" => 7,
"wince" => 9,
"efi-app" | "efi_application" => 10,
"efi-bsd" | "efi_boot_service_driver" => 11,
"efi-rtd" | "efi_runtime_driver" => 12,
"efi-rom" | "efi_rom" => 13,
"xbox" => 14,
digits => {
return digits
.parse::<u16>()
.map(|n| (n, None))
.map_err(|_| Error::Option(format!("unknown subsystem `{name}`")));
}
};
let version = match version {
None => None,
Some(text) => {
let (major, minor) = match text.split_once('.') {
Some((major, minor)) => (major, Some(minor)),
None => (text, None),
};
let parse = |field: &str| {
field
.parse::<u16>()
.map_err(|_| Error::Option(format!("invalid subsystem version `{text}`")))
};
Some(Version::new(
parse(major)?,
match minor {
Some(minor) => parse(minor)?,
None => 0,
},
))
}
};
Ok((subsystem, version))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_follow_mingw() {
let options = PeOptions::default();
assert_eq!(options.effective_image_base(), DEFAULT_IMAGE_BASE_EXE);
let dll = PeOptions {
dll: true,
..PeOptions::default()
};
assert_eq!(dll.effective_image_base(), DEFAULT_IMAGE_BASE_DLL);
options.validate().unwrap();
}
#[test]
fn rejects_bad_alignments() {
let bad = PeOptions {
file_alignment: 300,
..PeOptions::default()
};
assert!(bad.validate().is_err());
let bad = PeOptions {
section_alignment: 256,
file_alignment: 512,
..PeOptions::default()
};
assert!(bad.validate().is_err());
}
#[test]
fn command_line_defaults_match_the_mingw_defaults() {
let options = LinkOptions::new();
assert_eq!(PeOptions::from_link_options(&options), PeOptions::default());
}
#[test]
fn shared_output_becomes_a_dll() {
let options = LinkOptions {
kind: OutputKind::Shared,
export_dynamic: true,
nostdlib: true,
..LinkOptions::new()
};
let pe = PeOptions::from_link_options(&options);
assert!(pe.dll && pe.export_all_symbols && pe.no_default_lib);
assert_eq!(pe.effective_image_base(), DEFAULT_IMAGE_BASE_DLL);
}
#[test]
fn subsystems() {
assert_eq!(
parse_subsystem("console").unwrap(),
(IMAGE_SUBSYSTEM_WINDOWS_CUI, None)
);
assert_eq!(
parse_subsystem("windows,6.1").unwrap(),
(IMAGE_SUBSYSTEM_WINDOWS_GUI, Some(Version::new(6, 1)))
);
assert_eq!(parse_subsystem("10").unwrap(), (10, None));
assert!(parse_subsystem("bogus").is_err());
}
}