#![deny(clippy::arithmetic_side_effects)]
use std::path::PathBuf;
use crate::args::LinkOptions;
use crate::args::darwin::{MachOutputType, PlatformVersion, UndefinedTreatment, UuidMode};
use crate::error::{Error, Result};
use crate::macho::read::Arch;
use crate::macho::read::commands::PackedVersion;
use crate::macho::read::consts::{
CPU_TYPE_ARM64, CPU_TYPE_X86_64, DYLD_CHAINED_PTR_64, DYLD_CHAINED_PTR_ARM64E,
DYLD_CHAINED_PTR_ARM64E_USERLAND24, PLATFORM_BRIDGEOS, PLATFORM_DRIVERKIT, PLATFORM_IOS,
PLATFORM_IOSSIMULATOR, PLATFORM_MACCATALYST, PLATFORM_MACOS, PLATFORM_TVOS,
PLATFORM_TVOSSIMULATOR, PLATFORM_WATCHOS, PLATFORM_WATCHOSSIMULATOR, PLATFORM_XROS,
PLATFORM_XROS_SIMULATOR,
};
#[derive(Clone, Debug)]
pub struct Config {
pub arch: Arch,
pub output_type: MachOutputType,
pub platform: PlatformVersion,
pub page_size: u64,
pub chained_fixups: bool,
pub pointer_format: u16,
pub data_const: bool,
pub pagezero: u64,
pub image_base: u64,
pub headerpad: u64,
pub headerpad_max_install_names: bool,
pub sign: bool,
pub entry: Option<Vec<u8>>,
pub install_name: Vec<u8>,
pub current_version: PackedVersion,
pub compatibility_version: PackedVersion,
pub dead_strip: bool,
pub undefined: UndefinedTreatment,
pub uuid: bool,
pub identifier: Vec<u8>,
pub debug_map: bool,
pub oso_prefix: Option<PathBuf>,
pub flat_namespace: bool,
pub force_flat_namespace: bool,
pub relative_method_lists: bool,
pub objc_category_merging: bool,
}
fn chained_fixups_by_default(platform: &PlatformVersion) -> bool {
let min = match platform.platform {
PLATFORM_MACOS => PackedVersion::new(12, 0, 0),
PLATFORM_IOS | PLATFORM_TVOS | PLATFORM_MACCATALYST => PackedVersion::new(15, 0, 0),
PLATFORM_IOSSIMULATOR | PLATFORM_TVOSSIMULATOR => PackedVersion::new(15, 0, 0),
PLATFORM_WATCHOS | PLATFORM_WATCHOSSIMULATOR => PackedVersion::new(8, 0, 0),
PLATFORM_XROS | PLATFORM_XROS_SIMULATOR | PLATFORM_DRIVERKIT => PackedVersion::new(1, 0, 0),
_ => return false,
};
platform.min.0 >= min.0
}
fn relative_method_lists_by_default(platform: &PlatformVersion) -> bool {
let min = match platform.platform {
PLATFORM_MACOS => PackedVersion::new(10, 16, 0),
PLATFORM_IOS | PLATFORM_TVOS => PackedVersion::new(14, 0, 0),
PLATFORM_WATCHOS => PackedVersion::new(7, 0, 0),
PLATFORM_BRIDGEOS => PackedVersion::new(5, 0, 0),
PLATFORM_XROS => PackedVersion::new(1, 0, 0),
_ => return false,
};
platform.min.0 >= min.0
}
fn data_const_by_default(platform: &PlatformVersion) -> bool {
let min = match platform.platform {
PLATFORM_MACOS => PackedVersion::new(10, 15, 0),
PLATFORM_IOS | PLATFORM_IOSSIMULATOR | PLATFORM_TVOS | PLATFORM_TVOSSIMULATOR => {
PackedVersion::new(13, 0, 0)
}
PLATFORM_WATCHOS | PLATFORM_WATCHOSSIMULATOR => PackedVersion::new(6, 0, 0),
_ => PackedVersion::new(0, 0, 0),
};
platform.min.0 >= min.0
}
impl Config {
pub fn new(
options: &LinkOptions,
arch: Arch,
inferred_platform: Option<PlatformVersion>,
) -> Result<Self> {
let darwin = &options.darwin;
let is_arm64 = match arch.cpu_type {
CPU_TYPE_ARM64 => true,
CPU_TYPE_X86_64 => false,
_ => {
return Err(Error::Unimplemented(format!(
"Mach-O output for architecture {arch} (roadmap M8 covers arm64 and x86_64)"
)));
}
};
let platform = darwin
.platform
.or(inferred_platform)
.unwrap_or(PlatformVersion {
platform: PLATFORM_MACOS,
min: if is_arm64 {
PackedVersion::new(11, 0, 0)
} else {
PackedVersion::new(10, 15, 0)
},
sdk: PackedVersion::new(11, 0, 0),
});
let output_type = darwin.output_type;
let is_exec = output_type == MachOutputType::Execute;
if darwin.pie == Some(false) && is_arm64 && is_exec {
return Err(Error::Option(
"-no_pie is not supported for arm64 executables".into(),
));
}
if darwin.pie == Some(false) && is_exec {
return Err(Error::Unimplemented(
"non-PIE Mach-O executables (-no_pie)".into(),
));
}
let page_size = if is_arm64 { 0x4000 } else { 0x1000 };
let pagezero = if is_exec {
darwin.pagezero_size.unwrap_or(0x1_0000_0000)
} else {
0
};
if pagezero.checked_rem(page_size) != Some(0) {
return Err(Error::Option(format!(
"-pagezero_size {pagezero:#x} is not a multiple of the page size {page_size:#x}"
)));
}
let image_base = darwin.image_base.unwrap_or(pagezero);
if is_exec && image_base < pagezero {
return Err(Error::Option(format!(
"-image_base {image_base:#x} is inside __PAGEZERO"
)));
}
let entry = is_exec.then(|| {
options
.entry
.as_deref()
.unwrap_or("_main")
.as_bytes()
.to_vec()
});
let output = options.output_path();
let identifier = output
.file_name()
.map_or_else(|| b"a.out".to_vec(), |n| n.as_encoded_bytes().to_vec());
let install_name = match (&options.soname, output_type) {
(Some(name), _) => name.as_bytes().to_vec(),
(None, MachOutputType::Dylib) => output.as_os_str().as_encoded_bytes().to_vec(),
(None, _) => Vec::new(),
};
let relocatable = output_type == MachOutputType::Object;
let arm64e = arch == Arch::ARM64E;
if arm64e && darwin.fixup_chains == Some(false) && !relocatable {
return Err(Error::Option(
"-no_fixup_chains: arm64e images need chained fixups".into(),
));
}
let chained_fixups = arm64e
|| darwin
.fixup_chains
.unwrap_or_else(|| chained_fixups_by_default(&platform));
let pointer_format = match (arm64e, chained_fixups_by_default(&platform)) {
(false, _) => DYLD_CHAINED_PTR_64,
(true, true) => DYLD_CHAINED_PTR_ARM64E_USERLAND24,
(true, false) => DYLD_CHAINED_PTR_ARM64E,
};
Ok(Self {
arch,
output_type,
platform,
page_size,
chained_fixups,
pointer_format,
data_const: data_const_by_default(&platform),
pagezero,
image_base,
headerpad: darwin.headerpad.unwrap_or(32),
headerpad_max_install_names: darwin.headerpad_max_install_names,
sign: !relocatable && darwin.adhoc_codesign.unwrap_or(is_arm64),
entry,
install_name,
current_version: darwin
.current_version
.unwrap_or(PackedVersion::new(1, 0, 0)),
compatibility_version: darwin
.compatibility_version
.unwrap_or(PackedVersion::new(1, 0, 0)),
dead_strip: options.gc_sections && !relocatable,
undefined: darwin.undefined,
uuid: darwin.uuid == UuidMode::Content && !relocatable,
identifier,
debug_map: options.strip == crate::args::StripMode::None,
oso_prefix: darwin.oso_prefix.clone(),
flat_namespace: darwin.flat_namespace,
force_flat_namespace: darwin.force_flat_namespace,
relative_method_lists: darwin
.objc_relative_method_lists
.unwrap_or_else(|| relative_method_lists_by_default(&platform)),
objc_category_merging: darwin.objc_category_merging && !relocatable,
})
}
#[must_use]
pub fn is_arm64(&self) -> bool {
self.arch.cpu_type == CPU_TYPE_ARM64
}
#[must_use]
pub fn is_exec(&self) -> bool {
self.output_type == MachOutputType::Execute
}
#[must_use]
pub fn is_relocatable(&self) -> bool {
self.output_type == MachOutputType::Object
}
#[must_use]
pub fn is_arm64e(&self) -> bool {
self.arch == Arch::ARM64E
}
#[must_use]
pub fn stub_size(&self) -> u64 {
if self.is_arm64e() {
16
} else if self.is_arm64() {
12
} else {
6
}
}
}