From e841eb9306d72405cf59dabcdcbe368de8e697a5 Mon Sep 17 00:00:00 2001 From: Samuel Hunt Date: Sat, 7 Mar 2026 21:24:25 +1300 Subject: [PATCH] Add support for windows linker args --- Cargo.lock | 9 +- Cargo.toml | 1 + libwild/Cargo.toml | 1 + libwild/src/arch.rs | 14 + libwild/src/args.rs | 3044 +++++++----------------- libwild/src/args/consts.rs | 64 + libwild/src/args/linux.rs | 1852 ++++++++++++++ libwild/src/args/windows.rs | 1502 ++++++++++++ libwild/src/lib.rs | 10 +- libwild/src/linker_plugins_disabled.rs | 2 +- libwild/src/save_dir.rs | 12 +- libwild/src/subprocess.rs | 4 +- wild/src/main.rs | 29 +- 13 files changed, 4335 insertions(+), 2209 deletions(-) create mode 100644 libwild/src/args/consts.rs create mode 100644 libwild/src/args/linux.rs create mode 100644 libwild/src/args/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 82850f980..196f3f837 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,6 +901,7 @@ dependencies = [ "smallvec", "symbolic-common", "symbolic-demangle", + "target-lexicon", "tempfile", "thread_local", "tracing", @@ -1882,6 +1883,12 @@ dependencies = [ "syn", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tempfile" version = "3.25.0" @@ -1889,7 +1896,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys", diff --git a/Cargo.toml b/Cargo.toml index 7370e4983..55404469d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,7 @@ jobserver = "0.1.30" leb128 = "0.2.5" libc = "0.2.171" libloading = "0.9.0" +target-lexicon = "0.13.5" memchr = "2.6.0" memmap2 = "0.9.0" mimalloc = { version = "0.1", default-features = false } diff --git a/libwild/Cargo.toml b/libwild/Cargo.toml index c3ebbba2a..6d1cf48e0 100644 --- a/libwild/Cargo.toml +++ b/libwild/Cargo.toml @@ -35,6 +35,7 @@ libloading = { workspace = true, optional = true } linker-layout = { path = "../linker-layout", version = "0.8.0" } linker-trace = { path = "../linker-trace", version = "0.8.0" } linker-utils = { path = "../linker-utils", version = "0.8.0" } +target-lexicon = { workspace = true } memchr = { workspace = true } memmap2 = { workspace = true } object = { workspace = true } diff --git a/libwild/src/arch.rs b/libwild/src/arch.rs index 6edd35f36..275306d62 100644 --- a/libwild/src/arch.rs +++ b/libwild/src/arch.rs @@ -14,6 +14,20 @@ pub(crate) enum Architecture { LoongArch64, } +impl Architecture { + /// Convert a `target_lexicon::Architecture` to an `Architecture`. + /// Panics if the architecture is not supported. + pub const fn from_target_lexicon(arch: target_lexicon::Architecture) -> Self { + match arch { + target_lexicon::Architecture::X86_64 => Self::X86_64, + target_lexicon::Architecture::Aarch64(_) => Self::AArch64, + target_lexicon::Architecture::Riscv64(_) => Self::RISCV64, + target_lexicon::Architecture::LoongArch64 => Self::LoongArch64, + _ => panic!("Unsupported architecture"), + } + } +} + impl TryFrom for Architecture { type Error = crate::error::Error; diff --git a/libwild/src/args.rs b/libwild/src/args.rs index 82b37977f..ab6f0c0fb 100644 --- a/libwild/src/args.rs +++ b/libwild/src/args.rs @@ -12,39 +12,31 @@ //! Basically, we need to be able to parse arguments in the same way as the other linkers on the //! platform that we're targeting. +pub(crate) mod consts; +pub(crate) mod linux; +pub(crate) mod windows; + +pub(crate) use consts::*; +use target_lexicon::BinaryFormat; + use crate::alignment::Alignment; use crate::arch::Architecture; use crate::bail; -use crate::ensure; use crate::error::Context as _; use crate::error::Result; use crate::input_data::FileId; -use crate::linker_script::maybe_forced_sysroot; use crate::save_dir::SaveDir; -use crate::timing_phase; use hashbrown::HashMap; use hashbrown::HashSet; -use indexmap::IndexSet; -use itertools::Itertools; use jobserver::Acquired; use jobserver::Client; -use object::elf::GNU_PROPERTY_X86_ISA_1_BASELINE; -use object::elf::GNU_PROPERTY_X86_ISA_1_V2; -use object::elf::GNU_PROPERTY_X86_ISA_1_V3; -use object::elf::GNU_PROPERTY_X86_ISA_1_V4; -use rayon::ThreadPoolBuilder; -use std::ffi::CString; use std::fmt::Display; -use std::mem::take; -use std::num::NonZero; -use std::num::NonZeroU32; -use std::num::NonZeroU64; use std::num::NonZeroUsize; use std::path::Path; use std::path::PathBuf; -use std::str::FromStr; use std::sync::Arc; use std::sync::atomic::AtomicI64; +use target_lexicon::Triple; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum VersionMode { @@ -64,104 +56,81 @@ pub(crate) enum DefsymValue { SymbolWithOffset(String, i64), } +/// Parsed linker arguments. Common fields are directly accessible. +/// Format-specific fields are accessible via `Deref`/`DerefMut` through `target_args`. +/// +/// `T` defaults to `TargetArgs` (the enum). During parsing, `T` is set to the +/// concrete format type (e.g. `ElfArgs` or `PeArgs`). #[derive(Debug)] -pub struct Args { - pub(crate) unrecognized_options: Vec, - +pub struct Args { + // ── Infrastructure ─────────────────────────────────────────────────────── + pub should_fork: bool, + pub(crate) output: Arc, pub(crate) arch: Architecture, - pub(crate) lib_search_path: Vec>, pub(crate) inputs: Vec, - pub(crate) output: Arc, - pub(crate) dynamic_linker: Option>, + pub(crate) lib_search_path: Vec>, pub num_threads: Option, - pub(crate) strip: Strip, - pub(crate) prepopulate_maps: bool, - pub(crate) sym_info: Option, - pub(crate) merge_sections: bool, - pub(crate) debug_fuel: Option, - pub(crate) validate_output: bool, - pub(crate) version_script_path: Option, - pub(crate) debug_address: Option, + pub(crate) available_threads: NonZeroUsize, + pub(crate) save_dir: SaveDir, + pub(crate) unrecognized_options: Vec, + pub(crate) files_per_group: Option, pub(crate) write_layout: bool, - pub(crate) should_write_eh_frame_hdr: bool, pub(crate) write_trace: bool, - pub(crate) wrap: Vec, - pub(crate) rpath: Option, - pub(crate) soname: Option, - pub(crate) files_per_group: Option, - pub(crate) exclude_libs: ExcludeLibs, + pub(crate) jobserver_client: Option, + + // ── Core linker behavior ───────────────────────────────────────────────── + pub(crate) strip: Strip, pub(crate) gc_sections: bool, - pub(crate) should_fork: bool, - pub(crate) mmap_output_file: bool, - pub(crate) build_id: BuildIdOption, - pub(crate) file_write_mode: Option, + pub(crate) merge_sections: bool, + pub(crate) relax: bool, + pub(crate) demangle: bool, pub(crate) no_undefined: bool, pub(crate) allow_shlib_undefined: bool, - pub(crate) needs_origin_handling: bool, - pub(crate) needs_nodelete_handling: bool, + pub(crate) error_unresolved_symbols: bool, + pub(crate) allow_multiple_definitions: bool, + pub(crate) unresolved_symbols: UnresolvedSymbols, + pub(crate) undefined: Vec, pub(crate) copy_relocations: CopyRelocations, pub(crate) sysroot: Option>, - pub(crate) undefined: Vec, - pub(crate) relro: bool, + pub(crate) dynamic_linker: Option>, pub(crate) entry: Option, - pub(crate) export_all_dynamic_symbols: bool, + pub(crate) wrap: Vec, + pub(crate) exclude_libs: ExcludeLibs, + pub(crate) b_symbolic: BSymbolicKind, pub(crate) export_list: Vec, - pub(crate) export_list_path: Option, - pub(crate) auxiliary: Vec, - pub(crate) enable_new_dtags: bool, - pub(crate) plugin_path: Option, - pub(crate) plugin_args: Vec, - - /// Symbol definitions from `--defsym` options. Each entry is (symbol_name, value_or_symbol). pub(crate) defsym: Vec<(String, DefsymValue)>, - - /// Section start addresses from `--section-start` options. Maps section name to address. pub(crate) section_start: HashMap, - - /// If set, GC stats will be written to the specified filename. - pub(crate) write_gc_stats: Option, - - /// If set, and we're writing GC stats, then ignore any input files that contain any of the - /// specified substrings. - pub(crate) gc_stats_ignore: Vec, - - /// If `Some`, then we'll time how long each phase takes. We'll also measure the specified - /// counters, if any. - pub(crate) time_phase_options: Option>, - - pub(crate) verbose_gc_stats: bool, - - pub(crate) save_dir: SaveDir, - pub(crate) dependency_file: Option, - pub(crate) print_allocations: Option, + pub(crate) max_page_size: Option, pub(crate) execstack: bool, - pub(crate) verify_allocation_consistency: bool, pub(crate) version_mode: VersionMode, - pub(crate) demangle: bool, - pub(crate) got_plt_syms: bool, - pub(crate) b_symbolic: BSymbolicKind, - pub(crate) relax: bool, - pub(crate) should_write_linker_identity: bool, - pub(crate) hash_style: HashStyle, - pub(crate) unresolved_symbols: UnresolvedSymbols, - pub(crate) error_unresolved_symbols: bool, - pub(crate) allow_multiple_definitions: bool, - pub(crate) z_interpose: bool, - pub(crate) z_isa: Option, - pub(crate) z_stack_size: Option, - pub(crate) max_page_size: Option, - pub(crate) relocation_model: RelocationModel, pub(crate) should_output_executable: bool, + pub(crate) export_all_dynamic_symbols: bool, + pub(crate) version_script_path: Option, + pub(crate) export_list_path: Option, - /// The number of actually available threads (considering jobserver) - pub(crate) available_threads: NonZeroUsize, + // ── Output/writing ─────────────────────────────────────────────────────── + pub(crate) mmap_output_file: bool, + pub(crate) file_write_mode: Option, + pub(crate) prepopulate_maps: bool, + pub(crate) should_write_linker_identity: bool, + // ── Debug/diagnostic ───────────────────────────────────────────────────── + pub(crate) debug_fuel: Option, + pub(crate) validate_output: bool, + pub(crate) sym_info: Option, + pub(crate) debug_address: Option, + pub(crate) print_allocations: Option, + pub(crate) verify_allocation_consistency: bool, + pub(crate) time_phase_options: Option>, pub(crate) numeric_experiments: Vec>, + pub(crate) write_gc_stats: Option, + pub(crate) gc_stats_ignore: Vec, + pub(crate) verbose_gc_stats: bool, + pub(crate) dependency_file: Option, - rpath_set: IndexSet, - - jobserver_client: Option, + // ── Format-specific ────────────────────────────────────────────────────── + pub target_args: T, } #[derive(Debug)] @@ -193,26 +162,11 @@ pub(crate) enum CopyRelocations { /// Represents a command-line argument that specifies the number of threads to use, /// triggering activation of the thread pool. -pub struct ActivatedArgs { - pub args: Args, +pub struct ActivatedArgs { + pub args: Args, _jobserver_tokens: Vec, } -#[derive(Debug)] -pub(crate) enum BuildIdOption { - None, - Fast, - Hex(Vec), - Uuid, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum HashStyle { - Gnu, - Sysv, - Both, -} - #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum ExcludeLibs { None, @@ -235,16 +189,6 @@ impl ExcludeLibs { } } -impl HashStyle { - pub(crate) const fn includes_gnu(self) -> bool { - matches!(self, HashStyle::Gnu | HashStyle::Both) - } - - pub(crate) const fn includes_sysv(self) -> bool { - matches!(self, HashStyle::Sysv | HashStyle::Both) - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum RelocationModel { NonRelocatable, @@ -345,574 +289,267 @@ pub(crate) enum UnresolvedSymbols { IgnoreAll, } -pub const WILD_UNSUPPORTED_ENV: &str = "WILD_UNSUPPORTED"; -pub const VALIDATE_ENV: &str = "WILD_VALIDATE_OUTPUT"; -pub const WRITE_LAYOUT_ENV: &str = "WILD_WRITE_LAYOUT"; -pub const WRITE_TRACE_ENV: &str = "WILD_WRITE_TRACE"; -pub const REFERENCE_LINKER_ENV: &str = "WILD_REFERENCE_LINKER"; -pub(crate) const FILES_PER_GROUP_ENV: &str = "WILD_FILES_PER_GROUP"; - -/// Set this environment variable if you get a failure during writing due to too much or too little -/// space being allocated to some section. When set, each time we allocate during layout, we'll -/// check that what we're doing is consistent with writing and fail in a more easy to debug way. i.e -/// we'll report the particular combination of value flags, resolution flags etc that triggered the -/// inconsistency. -pub(crate) const WRITE_VERIFY_ALLOCATIONS_ENV: &str = "WILD_VERIFY_ALLOCATIONS"; - -// These flags don't currently affect our behaviour. TODO: Assess whether we should error or warn if -// these are given. This is tricky though. On the one hand we want to be a drop-in replacement for -// other linkers. On the other, we should perhaps somehow let the user know that we don't support a -// feature. -const SILENTLY_IGNORED_FLAGS: &[&str] = &[ - // Just like other modern linkers, we don't need groups in order to resolve cycles. - "start-group", - "end-group", - // TODO: This is supposed to suppress built-in search paths, but I don't think we have any - // built-in search paths. Perhaps we should? - "nostdlib", - // TODO - "no-undefined-version", - "fatal-warnings", - "color-diagnostics", - "undefined-version", - "sort-common", - "stats", -]; -const SILENTLY_IGNORED_SHORT_FLAGS: &[&str] = &[ - "(", - ")", - // On Illumos, the Clang driver inserts a meaningless -C flag before calling any non-GNU ld - // linker. - #[cfg(target_os = "illumos")] - "C", -]; - -const IGNORED_FLAGS: &[&str] = &[ - "gdb-index", - "fix-cortex-a53-835769", - "fix-cortex-a53-843419", - "discard-all", - "use-android-relr-tags", - "x", // alias for --discard-all -]; - -// These flags map to the default behavior of the linker. -const DEFAULT_FLAGS: &[&str] = &[ - "no-call-graph-profile-sort", - "no-copy-dt-needed-entries", - "no-add-needed", - "discard-locals", - "no-fatal-warnings", - "no-use-android-relr-tags", -]; -const DEFAULT_SHORT_FLAGS: &[&str] = &[ - "X", // alias for --discard-locals - "EL", // little endian -]; - -impl Default for Args { +impl Default for Args { fn default() -> Self { Args { - arch: default_target_arch(), + // Infrastructure + should_fork: true, + arch: const { Architecture::from_target_lexicon(target_lexicon::HOST.architecture) }, unrecognized_options: Vec::new(), - lib_search_path: Vec::new(), inputs: Vec::new(), output: Arc::from(Path::new("a.out")), - should_output_executable: true, - dynamic_linker: None, - time_phase_options: None, num_threads: None, + write_layout: std::env::var(WRITE_LAYOUT_ENV).is_ok_and(|v| v == "1"), + write_trace: std::env::var(WRITE_TRACE_ENV).is_ok_and(|v| v == "1"), + files_per_group: None, + save_dir: Default::default(), + jobserver_client: None, + available_threads: NonZeroUsize::new(1).unwrap(), + // Core linker behavior strip: Strip::Nothing, - // For now, we default to --gc-sections. This is different to other linkers, but other - // than being different, there doesn't seem to be any downside to doing - // this. We don't currently do any less work if we're not GCing sections, - // but do end up writing more, so --no-gc-sections will almost always be as - // slow or slower than --gc-sections. For that reason, the latter is - // probably a good default. gc_sections: true, - prepopulate_maps: false, - sym_info: None, merge_sections: true, + relax: true, + demangle: true, + no_undefined: false, + allow_shlib_undefined: false, + error_unresolved_symbols: true, + allow_multiple_definitions: false, + unresolved_symbols: UnresolvedSymbols::ReportAll, + undefined: Vec::new(), copy_relocations: CopyRelocations::Allowed, + sysroot: None, + dynamic_linker: None, + entry: None, + wrap: Vec::new(), + exclude_libs: ExcludeLibs::None, + b_symbolic: BSymbolicKind::None, + export_list: Vec::new(), + defsym: Vec::new(), + section_start: HashMap::new(), + max_page_size: None, + execstack: false, + version_mode: VersionMode::None, + relocation_model: RelocationModel::NonRelocatable, + should_output_executable: true, + export_all_dynamic_symbols: false, + version_script_path: None, + export_list_path: None, + // Output/writing + mmap_output_file: true, + file_write_mode: None, + prepopulate_maps: false, + should_write_linker_identity: true, + // Debug/diagnostic debug_fuel: None, validate_output: std::env::var(VALIDATE_ENV).is_ok_and(|v| v == "1"), - write_layout: std::env::var(WRITE_LAYOUT_ENV).is_ok_and(|v| v == "1"), - write_trace: std::env::var(WRITE_TRACE_ENV).is_ok_and(|v| v == "1"), - verify_allocation_consistency: std::env::var(WRITE_VERIFY_ALLOCATIONS_ENV) - .is_ok_and(|v| v == "1"), + sym_info: None, + debug_address: None, print_allocations: std::env::var("WILD_PRINT_ALLOCATIONS") .ok() .and_then(|s| s.parse().ok()) .map(FileId::from_encoded), - relocation_model: RelocationModel::NonRelocatable, - version_script_path: None, - debug_address: None, - should_write_eh_frame_hdr: false, + verify_allocation_consistency: std::env::var(WRITE_VERIFY_ALLOCATIONS_ENV) + .is_ok_and(|v| v == "1"), + time_phase_options: None, + numeric_experiments: Vec::new(), write_gc_stats: None, - wrap: Vec::new(), gc_stats_ignore: Vec::new(), verbose_gc_stats: false, - rpath: None, - soname: None, - enable_new_dtags: true, - execstack: false, - should_fork: true, - mmap_output_file: true, - needs_origin_handling: false, - needs_nodelete_handling: false, - should_write_linker_identity: true, - file_write_mode: None, - build_id: BuildIdOption::None, - files_per_group: None, - exclude_libs: ExcludeLibs::None, - no_undefined: false, - allow_shlib_undefined: false, - version_mode: VersionMode::None, - sysroot: None, - save_dir: Default::default(), dependency_file: None, - demangle: true, - undefined: Vec::new(), - relro: true, - entry: None, - b_symbolic: BSymbolicKind::None, - export_all_dynamic_symbols: false, - export_list: Vec::new(), - export_list_path: None, - defsym: Vec::new(), - section_start: HashMap::new(), - got_plt_syms: false, - relax: true, - hash_style: HashStyle::Both, - jobserver_client: None, - available_threads: NonZeroUsize::new(1).unwrap(), - unresolved_symbols: UnresolvedSymbols::ReportAll, - error_unresolved_symbols: true, - allow_multiple_definitions: false, - z_interpose: false, - z_stack_size: None, - z_isa: None, - max_page_size: None, - auxiliary: Vec::new(), - numeric_experiments: Vec::new(), - rpath_set: Default::default(), - plugin_path: None, - plugin_args: Vec::new(), + // Format-specific + target_args: T::default(), } } } -// Parse the supplied input arguments, which should not include the program name. -pub(crate) fn parse I, S: AsRef, I: Iterator>(input: F) -> Result { - use crate::input_data::MAX_FILES_PER_GROUP; - - // SAFETY: Should be called early before other descriptors are opened and - // so we open it before the arguments are parsed (can open a file). - let jobserver_client = unsafe { Client::from_env() }; - - let files_per_group = std::env::var(FILES_PER_GROUP_ENV) - .ok() - .map(|s| s.parse()) - .transpose()?; +impl Args { + /// Parse CLI arguments. Detects target format from `--target=`, `-m`, + /// or host default, then routes to the format-specific parser. + pub fn parse I, S: Into, I: Iterator>(input: F) -> Result { + let mut input = input().map(S::into); + // TODO: This should be used as a fallback if no target can be detected from the arguments. + let _executable_name = input + .next() + .ok_or_else(|| crate::error!("should always be at least the executable name"))?; + let all_args = input.collect::>(); + let detected = detect_target(&all_args)?; + let filtered = filter_and_inject_target_flags(&all_args, detected.format, detected.arch); + + match detected.format { + BinaryFormat::Elf => { + let elf_args = linux::parse(|| filtered.iter().map(|s| s.as_str()))?; + Ok(elf_args.map_target(TargetArgs::Elf)) + } + BinaryFormat::Coff => { + let pe_args = windows::parse(|| filtered.iter().map(|s| s.as_str()))?; + Ok(pe_args.map_target(TargetArgs::Pe)) + } + _ => bail!("unsupported binary format: {}", detected.format), + } + } +} - if let Some(x) = files_per_group { - ensure!( - x <= MAX_FILES_PER_GROUP, - "{FILES_PER_GROUP_ENV}={x} but maximum is {MAX_FILES_PER_GROUP}" - ); +impl Default for Modifiers { + fn default() -> Self { + Self { + as_needed: false, + allow_shared: true, + whole_archive: false, + archive_semantics: false, + temporary: false, + } } +} + +pub(crate) struct ArgumentParser { + options: HashMap<&'static str, OptionHandler>, + short_options: HashMap<&'static str, OptionHandler>, + prefix_options: HashMap<&'static str, PrefixOptionHandler>, + case_insensitive: bool, + has_option_prefix: fn(&str) -> bool, + strip_option: for<'a> fn(&'a str) -> Option<&'a str>, + find_separator: fn(&str) -> Option, +} - let mut args = Args { - files_per_group, - jobserver_client, - ..Default::default() - }; +struct OptionHandler { + help_text: &'static str, + handler: OptionHandlerFn, + short_names: Vec<&'static str>, +} - args.save_dir = SaveDir::new(&input)?; +impl Clone for OptionHandler { + fn clone(&self) -> Self { + Self { + help_text: self.help_text, + handler: self.handler, + short_names: self.short_names.clone(), + } + } +} - let mut input = input(); +struct PrefixOptionHandler { + help_text: &'static str, + handler: fn(&mut Args, &mut Vec, &str) -> Result<()>, + sub_options: HashMap<&'static str, SubOption>, +} - let mut modifier_stack = vec![Modifiers::default()]; +#[allow(clippy::enum_variant_names)] +enum OptionHandlerFn { + NoParam(fn(&mut Args, &mut Vec) -> Result<()>), + WithParam(fn(&mut Args, &mut Vec, &str) -> Result<()>), + OptionalParam(fn(&mut Args, &mut Vec, Option<&str>) -> Result<()>), +} - if std::env::var(REFERENCE_LINKER_ENV).is_ok() { - args.write_layout = true; - args.write_trace = true; +impl Clone for OptionHandlerFn { + fn clone(&self) -> Self { + *self } +} - let arg_parser = setup_argument_parser(); - while let Some(arg) = input.next() { - let arg = arg.as_ref(); +impl Copy for OptionHandlerFn {} - arg_parser.handle_argument(&mut args, &mut modifier_stack, arg, &mut input)?; +impl OptionHandlerFn { + fn help_suffix_long(&self) -> &'static str { + match self { + OptionHandlerFn::NoParam(_) => "", + OptionHandlerFn::WithParam(_) => "=", + OptionHandlerFn::OptionalParam(_) => "[=]", + } } - // Copy relocations are only permitted when building executables. - if !args.should_output_executable { - args.copy_relocations = - CopyRelocations::Disallowed(CopyRelocationsDisabledReason::SharedObject); + fn help_suffix_short(&self) -> &'static str { + match self { + OptionHandlerFn::NoParam(_) => "", + OptionHandlerFn::WithParam(_) => " ", + OptionHandlerFn::OptionalParam(_) => " []", + } } +} - if !args.rpath_set.is_empty() { - args.rpath = Some(take(&mut args.rpath_set).into_iter().join(":")); - } +pub(crate) struct OptionDeclaration<'a, T, S> { + parser: &'a mut ArgumentParser, + long_names: Vec<&'static str>, + short_names: Vec<&'static str>, + prefixes: Vec<&'static str>, + sub_options: HashMap<&'static str, SubOption>, + help_text: &'static str, + _phantom: std::marker::PhantomData, +} - if !args.unrecognized_options.is_empty() { - let options_list = args.unrecognized_options.join(", "); - bail!("unrecognized option(s): {}", options_list); - } +pub struct NoParam; +pub struct WithParam; +pub struct WithOptionalParam; + +enum SubOptionHandler { + /// Handler without value parameter (exact match) + NoValue(fn(&mut Args, &mut Vec) -> Result<()>), + /// Handler with value parameter (prefix match) + WithValue(fn(&mut Args, &mut Vec, &str) -> Result<()>), +} - if !args.auxiliary.is_empty() && args.should_output_executable { - bail!("-f may not be used without -shared"); +impl Clone for SubOptionHandler { + fn clone(&self) -> Self { + *self } +} - Ok(args) +impl Copy for SubOptionHandler {} + +struct SubOption { + help: &'static str, + handler: SubOptionHandler, } -const fn default_target_arch() -> Architecture { - // We default to targeting the architecture that we're running on. We don't support running on - // architectures that we can't target. - #[cfg(target_arch = "x86_64")] - { - Architecture::X86_64 +impl Clone for SubOption { + fn clone(&self) -> Self { + *self } - #[cfg(target_arch = "aarch64")] - { - Architecture::AArch64 - } - #[cfg(target_arch = "riscv64")] - { - Architecture::RISCV64 - } - #[cfg(target_arch = "loongarch64")] - { - Architecture::LoongArch64 - } -} - -pub(crate) fn read_args_from_file(path: &Path) -> Result> { - let contents = std::fs::read_to_string(path) - .with_context(|| format!("Failed to read arguments from file `{}`", path.display()))?; - arguments_from_string(&contents) -} - -impl Args { - pub fn parse I, S: AsRef, I: Iterator>(input: F) -> Result { - timing_phase!("Parse args"); - parse(input) - } - - /// Uses 1 debug fuel, returning how much fuel remains. Debug fuel is intended to be used when - /// debugging certain kinds of bugs, so this function isn't normally referenced. To use it, the - /// caller should take a different branch depending on whether the value is still positive. You - /// can then do a binary search. - pub(crate) fn use_debug_fuel(&self) -> i64 { - let Some(fuel) = self.debug_fuel.as_ref() else { - return i64::MAX; - }; - fuel.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) - 1 - } - - /// Returns whether there was sufficient fuel. If the last bit of fuel was used, then calls - /// `last_cb`. - #[allow(unused)] - pub(crate) fn use_debug_fuel_on_last(&self, last_cb: impl FnOnce()) -> bool { - match self.use_debug_fuel() { - 1.. => true, - 0 => { - last_cb(); - true - } - _ => false, - } - } - - pub(crate) fn trace_span_for_file( - &self, - file_id: FileId, - ) -> Option { - let should_trace = self.print_allocations == Some(file_id); - should_trace.then(|| tracing::trace_span!(crate::debug_trace::TRACE_SPAN_NAME).entered()) - } - - pub fn should_fork(&self) -> bool { - self.should_fork - } - - pub(crate) fn loadable_segment_alignment(&self) -> Alignment { - if let Some(max_page_size) = self.max_page_size { - return max_page_size; - } - - match self.arch { - Architecture::X86_64 => Alignment { exponent: 12 }, - Architecture::AArch64 => Alignment { exponent: 16 }, - Architecture::RISCV64 => Alignment { exponent: 12 }, - Architecture::LoongArch64 => Alignment { exponent: 16 }, - } - } - - /// Adds a linker script to our outputs. Note, this is only called for scripts specified via - /// flags like -T. Where a linker script is just listed as an argument, this won't be called. - fn add_script(&mut self, path: &str) { - self.inputs.push(Input { - spec: InputSpec::File(Box::from(Path::new(path))), - search_first: None, - modifiers: Modifiers::default(), - }); - } - - /// Sets up the thread pool, using the explicit number of threads if specified, - /// or falling back to the jobserver protocol if available. - /// - /// - pub fn activate_thread_pool(mut self) -> Result { - timing_phase!("Activate thread pool"); - - let mut tokens = Vec::new(); - self.available_threads = self.num_threads.unwrap_or_else(|| { - if let Some(client) = &self.jobserver_client { - while let Ok(Some(acquired)) = client.try_acquire() { - tokens.push(acquired); - } - tracing::trace!(count = tokens.len(), "Acquired jobserver tokens"); - // Our parent "holds" one jobserver token, add it. - NonZeroUsize::new((tokens.len() + 1).max(1)).unwrap() - } else { - std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()) - } - }); - - // The pool might be already initialized, suppress the error intentionally. - let _ = ThreadPoolBuilder::new() - .num_threads(self.available_threads.get()) - .build_global(); - - Ok(ActivatedArgs { - args: self, - _jobserver_tokens: tokens, - }) - } - - pub(crate) fn numeric_experiment(&self, exp: Experiment, default: u64) -> u64 { - self.numeric_experiments - .get(exp as usize) - .copied() - .flatten() - .unwrap_or(default) - } - - pub(crate) fn strip_all(&self) -> bool { - matches!(self.strip, Strip::All) - } - - pub(crate) fn strip_debug(&self) -> bool { - matches!(self.strip, Strip::All | Strip::Debug) - } -} - -fn parse_number(s: &str) -> Result { - crate::parsing::parse_number(s).map_err(|_| crate::error!("Invalid number: {}", s)) -} - -fn parse_defsym_expression(s: &str) -> DefsymValue { - use crate::parsing::ParsedSymbolExpression; - use crate::parsing::parse_symbol_expression; - - match parse_symbol_expression(s) { - ParsedSymbolExpression::Absolute(value) => DefsymValue::Value(value), - ParsedSymbolExpression::SymbolWithOffset(sym, offset) => { - DefsymValue::SymbolWithOffset(sym.to_owned(), offset) - } - } -} - -impl Default for Modifiers { - fn default() -> Self { - Self { - as_needed: false, - allow_shared: true, - whole_archive: false, - archive_semantics: false, - temporary: false, - } - } -} - -/// Parses arguments from a string, handling quoting, escapes etc. -/// All arguments must be surrounded by a white space. -fn arguments_from_string(input: &str) -> Result> { - const QUOTES: [char; 2] = ['\'', '"']; - - let mut out = Vec::new(); - let mut chars = input.chars(); - let mut heap = None; - let mut quote = None; - let mut expect_whitespace = false; - - loop { - let Some(mut ch) = chars.next() else { - if let Some(quote) = quote.take() { - bail!("Missing closing '{quote}'"); - } - if let Some(arg) = heap.take() { - out.push(arg); - } - break; - }; - - ensure!( - !expect_whitespace || ch.is_whitespace(), - "Expected white space after quoted argument" - ); - expect_whitespace = false; - - if QUOTES.contains(&ch) { - if let Some(qchr) = quote { - if qchr == ch { - // close the argument - if let Some(arg) = heap.take() { - out.push(arg); - } - quote = None; - expect_whitespace = true; - } else { - // accept the other quoting character as normal char - heap.get_or_insert(String::new()).push(ch); - } - } else { - // beginning of a new argument - ensure!(heap.is_none(), "Missing opening quote '{ch}'"); - quote = Some(ch); - } - } else if ch.is_whitespace() { - if quote.is_none() { - if let Some(arg) = heap.take() { - out.push(arg); - } - } else { - heap.get_or_insert(String::new()).push(ch); - } - } else { - if ch == '\\' { - ch = chars.next().context("Invalid escape")?; - } - heap.get_or_insert(String::new()).push(ch); - } - } - - Ok(out) -} - -fn warn_unsupported(opt: &str) -> Result { - match std::env::var(WILD_UNSUPPORTED_ENV) - .unwrap_or_default() - .as_str() - { - "warn" | "" => crate::error::warning(&format!("{opt} is not yet supported")), - "ignore" => {} - "error" => bail!("{opt} is not yet supported"), - other => bail!("Unsupported value for {WILD_UNSUPPORTED_ENV}={other}"), - } - Ok(()) -} - -struct ArgumentParser { - options: HashMap<&'static str, OptionHandler>, - short_options: HashMap<&'static str, OptionHandler>, // Short option lookup - prefix_options: HashMap<&'static str, PrefixOptionHandler>, // For options like -L, -l, etc. -} - -#[derive(Clone)] -struct OptionHandler { - help_text: &'static str, - handler: OptionHandlerFn, - short_names: Vec<&'static str>, -} - -struct PrefixOptionHandler { - help_text: &'static str, - handler: fn(&mut Args, &mut Vec, &str) -> Result<()>, - sub_options: HashMap<&'static str, SubOption>, -} - -#[allow(clippy::enum_variant_names)] -#[derive(Clone, Copy)] -enum OptionHandlerFn { - NoParam(fn(&mut Args, &mut Vec) -> Result<()>), - WithParam(fn(&mut Args, &mut Vec, &str) -> Result<()>), - OptionalParam(fn(&mut Args, &mut Vec, Option<&str>) -> Result<()>), -} - -impl OptionHandlerFn { - fn help_suffix_long(&self) -> &'static str { - match self { - OptionHandlerFn::NoParam(_) => "", - OptionHandlerFn::WithParam(_) => "=", - OptionHandlerFn::OptionalParam(_) => "[=]", - } - } - - fn help_suffix_short(&self) -> &'static str { - match self { - OptionHandlerFn::NoParam(_) => "", - OptionHandlerFn::WithParam(_) => " ", - OptionHandlerFn::OptionalParam(_) => " []", - } - } -} - -struct OptionDeclaration<'a, T> { - parser: &'a mut ArgumentParser, - long_names: Vec<&'static str>, - short_names: Vec<&'static str>, - prefixes: Vec<&'static str>, - sub_options: HashMap<&'static str, SubOption>, - help_text: &'static str, - _phantom: std::marker::PhantomData, -} - -struct NoParam; -struct WithParam; -struct WithOptionalParam; - -#[derive(Clone, Copy)] -enum SubOptionHandler { - /// Handler without value parameter (exact match) - NoValue(fn(&mut Args, &mut Vec) -> Result<()>), - /// Handler with value parameter (prefix match) - WithValue(fn(&mut Args, &mut Vec, &str) -> Result<()>), } -#[derive(Clone, Copy)] -struct SubOption { - help: &'static str, - handler: SubOptionHandler, -} +impl Copy for SubOption {} -impl SubOption { +impl SubOption { fn with_value(&self) -> bool { matches!(self.handler, SubOptionHandler::WithValue(_)) } } -impl Default for ArgumentParser { +impl Default for ArgumentParser { fn default() -> Self { Self::new() } } -impl ArgumentParser { +impl ArgumentParser { #[must_use] - fn new() -> Self { + pub fn new() -> Self { Self { options: HashMap::new(), short_options: HashMap::new(), prefix_options: HashMap::new(), + case_insensitive: false, + has_option_prefix: |arg| arg.starts_with('-'), + strip_option: |arg| arg.strip_prefix("--").or(arg.strip_prefix('-')), + find_separator: |stripped| stripped.find('='), } } - fn declare(&mut self) -> OptionDeclaration<'_, NoParam> { + #[must_use] + pub fn new_case_insensitive() -> Self { + Self { + options: HashMap::new(), + short_options: HashMap::new(), + prefix_options: HashMap::new(), + case_insensitive: true, + has_option_prefix: |arg| arg.starts_with('/') || arg.starts_with('-'), + strip_option: |arg| arg.strip_prefix('/').or(arg.strip_prefix('-')), + find_separator: |stripped| stripped.find(':'), + } + } + + pub fn declare(&mut self) -> OptionDeclaration<'_, T, NoParam> { OptionDeclaration { parser: self, long_names: Vec::new(), @@ -924,7 +561,7 @@ impl ArgumentParser { } } - fn declare_with_param(&mut self) -> OptionDeclaration<'_, WithParam> { + pub fn declare_with_param(&mut self) -> OptionDeclaration<'_, T, WithParam> { OptionDeclaration { parser: self, long_names: Vec::new(), @@ -936,7 +573,7 @@ impl ArgumentParser { } } - fn declare_with_optional_param(&mut self) -> OptionDeclaration<'_, WithOptionalParam> { + pub fn declare_with_optional_param(&mut self) -> OptionDeclaration<'_, T, WithOptionalParam> { OptionDeclaration { parser: self, long_names: Vec::new(), @@ -948,9 +585,25 @@ impl ArgumentParser { } } - fn handle_argument, I: Iterator>( + fn get_option_handler(&self, option_name: &str) -> Option<&OptionHandler> { + if self.case_insensitive { + if let Some(handler) = self.options.get(option_name) { + return Some(handler); + } + for (key, handler) in &self.options { + if key.eq_ignore_ascii_case(option_name) { + return Some(handler); + } + } + None + } else { + self.options.get(option_name) + } + } + + pub(crate) fn handle_argument, I: Iterator>( &self, - args: &mut Args, + args: &mut Args, modifier_stack: &mut Vec, arg: &str, input: &mut I, @@ -966,13 +619,13 @@ impl ArgumentParser { return Ok(()); } - if let Some(stripped) = strip_option(arg) { - // Check for option with '=' syntax - if let Some(eq_pos) = stripped.find('=') { + if let Some(stripped) = (self.strip_option)(arg) { + // Check for option with separator syntax + if let Some(eq_pos) = (self.find_separator)(stripped) { let option_name = &stripped[..eq_pos]; let value = &stripped[eq_pos + 1..]; - if let Some(handler) = self.options.get(option_name) { + if let Some(handler) = self.get_option_handler(option_name) { match &handler.handler { OptionHandlerFn::WithParam(f) => f(args, modifier_stack, value)?, OptionHandlerFn::OptionalParam(f) => f(args, modifier_stack, Some(value))?, @@ -982,14 +635,14 @@ impl ArgumentParser { } } else { if stripped == "build-id" - && let Some(handler) = self.options.get(stripped) + && let Some(handler) = self.get_option_handler(stripped) && let OptionHandlerFn::WithParam(f) = &handler.handler { f(args, modifier_stack, "fast")?; return Ok(()); } - if let Some(handler) = self.options.get(stripped) { + if let Some(handler) = self.get_option_handler(stripped) { match &handler.handler { OptionHandlerFn::NoParam(f) => f(args, modifier_stack)?, OptionHandlerFn::WithParam(f) => { @@ -1068,8 +721,8 @@ impl ArgumentParser { } } - if arg.starts_with('-') { - if let Some(stripped) = strip_option(arg) + if (self.has_option_prefix)(arg) { + if let Some(stripped) = (self.strip_option)(arg) && IGNORED_FLAGS.contains(&stripped) { warn_unsupported(arg)?; @@ -1193,36 +846,36 @@ impl ArgumentParser { } } -impl<'a, T> OptionDeclaration<'a, T> { +impl<'a, T, S> OptionDeclaration<'a, T, S> { #[must_use] - fn long(mut self, name: &'static str) -> Self { + pub fn long(mut self, name: &'static str) -> Self { self.long_names.push(name); self } #[must_use] - fn short(mut self, option: &'static str) -> Self { + pub fn short(mut self, option: &'static str) -> Self { self.short_names.push(option); self } #[must_use] - fn help(mut self, text: &'static str) -> Self { + pub fn help(mut self, text: &'static str) -> Self { self.help_text = text; self } - fn prefix(mut self, prefix: &'static str) -> Self { + pub fn prefix(mut self, prefix: &'static str) -> Self { self.prefixes.push(prefix); self } #[must_use] - fn sub_option( + pub fn sub_option( mut self, name: &'static str, help: &'static str, - handler: fn(&mut Args, &mut Vec) -> Result<()>, + handler: fn(&mut Args, &mut Vec) -> Result<()>, ) -> Self { self.sub_options.insert( name, @@ -1235,11 +888,11 @@ impl<'a, T> OptionDeclaration<'a, T> { } #[must_use] - fn sub_option_with_value( + pub fn sub_option_with_value( mut self, name: &'static str, help: &'static str, - handler: fn(&mut Args, &mut Vec, &str) -> Result<()>, + handler: fn(&mut Args, &mut Vec, &str) -> Result<()>, ) -> Self { self.sub_options.insert( name, @@ -1252,8 +905,8 @@ impl<'a, T> OptionDeclaration<'a, T> { } } -impl<'a> OptionDeclaration<'a, NoParam> { - fn execute(self, handler: fn(&mut Args, &mut Vec) -> Result<()>) { +impl<'a, T> OptionDeclaration<'a, T, NoParam> { + pub fn execute(self, handler: fn(&mut Args, &mut Vec) -> Result<()>) { let option_handler = OptionHandler { help_text: self.help_text, handler: OptionHandlerFn::NoParam(handler), @@ -1272,8 +925,8 @@ impl<'a> OptionDeclaration<'a, NoParam> { } } -impl<'a> OptionDeclaration<'a, WithParam> { - fn execute(self, handler: fn(&mut Args, &mut Vec, &str) -> Result<()>) { +impl<'a, T> OptionDeclaration<'a, T, WithParam> { + pub fn execute(self, handler: fn(&mut Args, &mut Vec, &str) -> Result<()>) { let mut short_names = self.short_names.clone(); short_names.extend_from_slice(&self.prefixes); @@ -1305,8 +958,11 @@ impl<'a> OptionDeclaration<'a, WithParam> { } } -impl<'a> OptionDeclaration<'a, WithOptionalParam> { - fn execute(self, handler: fn(&mut Args, &mut Vec, Option<&str>) -> Result<()>) { +impl<'a, T> OptionDeclaration<'a, T, WithOptionalParam> { + pub fn execute( + self, + handler: fn(&mut Args, &mut Vec, Option<&str>) -> Result<()>, + ) { let option_handler = OptionHandler { help_text: self.help_text, handler: OptionHandlerFn::OptionalParam(handler), @@ -1325,1315 +981,509 @@ impl<'a> OptionDeclaration<'a, WithOptionalParam> { } } -fn strip_option(arg: &str) -> Option<&str> { - arg.strip_prefix("--").or(arg.strip_prefix('-')) -} - -fn setup_argument_parser() -> ArgumentParser { - let mut parser = ArgumentParser::new(); - - parser - .declare_with_param() - .prefix("L") - .help("Add directory to library search path") - .execute(|args, _modifier_stack, value| { - let handle_sysroot = |path| { - args.sysroot - .as_ref() - .and_then(|sysroot| maybe_forced_sysroot(path, sysroot)) - .unwrap_or_else(|| Box::from(path)) - }; - - let dir = handle_sysroot(Path::new(value)); - args.save_dir.handle_file(value); - args.lib_search_path.push(dir); - Ok(()) - }); - - parser - .declare_with_param() - .prefix("l") - .help("Link with library") - .sub_option_with_value( - ":filename", - "Link with specific file", - |args, modifier_stack, value| { - let stripped = value.strip_prefix(':').unwrap_or(value); - let spec = InputSpec::File(Box::from(Path::new(stripped))); - args.inputs.push(Input { - spec, - search_first: None, - modifiers: *modifier_stack.last().unwrap(), - }); - Ok(()) - }, - ) - .sub_option_with_value( - "libname", - "Link with library libname.so or libname.a", - |args, modifier_stack, value| { - let spec = InputSpec::Lib(Box::from(value)); - args.inputs.push(Input { - spec, - search_first: None, - modifiers: *modifier_stack.last().unwrap(), - }); - Ok(()) - }, - ) - .execute(|args, modifier_stack, value| { - let spec = if let Some(stripped) = value.strip_prefix(':') { - InputSpec::Search(Box::from(stripped)) - } else { - InputSpec::Lib(Box::from(value)) - }; - args.inputs.push(Input { - spec, - search_first: None, - modifiers: *modifier_stack.last().unwrap(), - }); - Ok(()) - }); - - parser - .declare_with_param() - .prefix("u") - .help("Force resolution of the symbol") - .execute(|args, _modifier_stack, value| { - args.undefined.push(value.to_owned()); - Ok(()) - }); - - parser - .declare_with_param() - .prefix("m") - .help("Set target architecture") - .sub_option("elf_x86_64", "x86-64 ELF target", |args, _| { - args.arch = Architecture::X86_64; - Ok(()) - }) - .sub_option( - "elf_x86_64_sol2", - "x86-64 ELF target (Solaris)", - |args, _| { - if args.dynamic_linker.is_none() { - args.dynamic_linker = Some(Path::new("/lib/amd64/ld.so.1").into()); - } - args.arch = Architecture::X86_64; - Ok(()) - }, - ) - .sub_option("aarch64elf", "AArch64 ELF target", |args, _| { - args.arch = Architecture::AArch64; - Ok(()) - }) - .sub_option("aarch64linux", "AArch64 ELF target (Linux)", |args, _| { - args.arch = Architecture::AArch64; - Ok(()) - }) - .sub_option("elf64lriscv", "RISC-V 64-bit ELF target", |args, _| { - args.arch = Architecture::RISCV64; - Ok(()) - }) - .sub_option( - "elf64loongarch", - "LoongArch 64-bit ELF target", - |args, _| { - args.arch = Architecture::LoongArch64; - Ok(()) - }, - ) - .execute(|_args, _modifier_stack, value| { - bail!("-m {value} is not yet supported"); - }); - - parser - .declare_with_param() - .prefix("z") - .help("Linker option") - .sub_option("now", "Resolve all symbols immediately", |_, _| Ok(())) - .sub_option( - "origin", - "Mark object as requiring immediate $ORIGIN", - |args, _| { - args.needs_origin_handling = true; - Ok(()) - }, - ) - .sub_option("relro", "Enable RELRO program header", |args, _| { - args.relro = true; - Ok(()) - }) - .sub_option("norelro", "Disable RELRO program header", |args, _| { - args.relro = false; - Ok(()) - }) - .sub_option("notext", "Do not report DT_TEXTREL as an error", |_, _| { - Ok(()) - }) - .sub_option("nostart-stop-gc", "Disable start/stop symbol GC", |_, _| { - Ok(()) - }) - .sub_option( - "execstack", - "Mark object as requiring an executable stack", - |args, _| { - args.execstack = true; - Ok(()) - }, - ) - .sub_option( - "noexecstack", - "Mark object as not requiring an executable stack", - |args, _| { - args.execstack = false; - Ok(()) - }, - ) - .sub_option("nocopyreloc", "Disable copy relocations", |args, _| { - args.copy_relocations = - CopyRelocations::Disallowed(CopyRelocationsDisabledReason::Flag); - Ok(()) - }) - .sub_option( - "nodelete", - "Mark shared object as non-deletable", - |args, _| { - args.needs_nodelete_handling = true; - Ok(()) - }, - ) - .sub_option( - "defs", - "Report unresolved symbol references in object files", - |args, _| { - args.no_undefined = true; - Ok(()) - }, - ) - .sub_option( - "undefs", - "Do not report unresolved symbol references in object files", - |args, _| { - args.no_undefined = false; - Ok(()) - }, - ) - .sub_option("muldefs", "Allow multiple definitions", |args, _| { - args.allow_multiple_definitions = true; - Ok(()) - }) - .sub_option("lazy", "Use lazy binding (default)", |_, _| Ok(())) - .sub_option( - "interpose", - "Mark object to interpose all DSOs but executable", - |args, _| { - args.z_interpose = true; - Ok(()) - }, - ) - .sub_option_with_value( - "stack-size=", - "Set size of stack segment", - |args, _, value| { - let size: u64 = parse_number(value)?; - args.z_stack_size = NonZero::new(size); - - Ok(()) - }, - ) - .sub_option( - "x86-64-baseline", - "Mark x86-64-baseline ISA as needed", - |args, _| { - args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_BASELINE); - Ok(()) - }, - ) - .sub_option("x86-64-v2", "Mark x86-64-v2 ISA as needed", |args, _| { - args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V2); - Ok(()) - }) - .sub_option("x86-64-v3", "Mark x86-64-v3 ISA as needed", |args, _| { - args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V3); - Ok(()) - }) - .sub_option("x86-64-v4", "Mark x86-64-v4 ISA as needed", |args, _| { - args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V4); - Ok(()) - }) - .sub_option_with_value( - "max-page-size=", - "Set maximum page size for load segments", - |args, _, value| { - let size: u64 = parse_number(value)?; - if !size.is_power_of_two() { - bail!("Invalid alignment {size:#x}"); - } - args.max_page_size = Some(Alignment { - exponent: size.trailing_zeros() as u8, - }); - - Ok(()) - }, - ) - .execute(|_args, _modifier_stack, value| { - warn_unsupported(&("-z ".to_owned() + value))?; - Ok(()) - }); - - parser - .declare_with_param() - .prefix("R") - .help("Add runtime library search path") - .execute(|args, _modifier_stack, value| { - if Path::new(value).is_file() { - args.unrecognized_options - .push(format!("-R,{value}(filename)")); - } else { - args.rpath_set.insert(value.to_string()); - } - Ok(()) - }); - - parser - .declare_with_param() - .prefix("O") - .execute(|_args, _modifier_stack, _value| - // We don't use opt-level for now. - Ok(())); - - parser - .declare() - .long("static") - .long("Bstatic") - .help("Disallow linking of shared libraries") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().allow_shared = false; - Ok(()) - }); - - parser - .declare() - .long("Bdynamic") - .help("Allow linking of shared libraries") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().allow_shared = true; - Ok(()) - }); - - parser - .declare_with_param() - .long("output") - .short("o") - .help("Set the output filename") - .execute(|args, _modifier_stack, value| { - args.output = Arc::from(Path::new(value)); - Ok(()) - }); - - parser - .declare() - .long("strip-all") - .short("s") - .help("Strip all symbols") - .execute(|args, _modifier_stack| { - args.strip = Strip::All; - Ok(()) - }); - - parser - .declare() - .long("strip-debug") - .short("S") - .help("Strip debug symbols") - .execute(|args, _modifier_stack| { - args.strip = Strip::Debug; - Ok(()) - }); - - parser - .declare() - .long("gc-sections") - .help("Enable removal of unused sections") - .execute(|args, _modifier_stack| { - args.gc_sections = true; - Ok(()) - }); - - parser - .declare() - .long("no-gc-sections") - .help("Disable removal of unused sections") - .execute(|args, _modifier_stack| { - args.gc_sections = false; - Ok(()) - }); - - parser - .declare() - .long("shared") - .long("Bshareable") - .help("Create a shared library") - .execute(|args, _modifier_stack| { - args.should_output_executable = false; - Ok(()) - }); - - parser - .declare() - .long("pie") - .long("pic-executable") - .help("Create a position-independent executable") - .execute(|args, _modifier_stack| { - args.relocation_model = RelocationModel::Relocatable; - args.should_output_executable = true; - Ok(()) - }); - - parser - .declare() - .long("no-pie") - .help("Do not create a position-dependent executable (default)") - .execute(|args, _modifier_stack| { - args.relocation_model = RelocationModel::NonRelocatable; - args.should_output_executable = true; - Ok(()) - }); - - parser - .declare_with_param() - .long("pack-dyn-relocs") - .help("Specify dynamic relocation packing format") - .execute(|_args, _modifier_stack, value| { - if value != "none" { - warn_unsupported(&format!("--pack-dyn-relocs={value}"))?; - } - Ok(()) - }); - - parser - .declare() - .long("help") - .help("Show this help message") - .execute(|_args, _modifier_stack| { - use std::io::Write as _; - let parser = setup_argument_parser(); - let mut stdout = std::io::stdout().lock(); - writeln!(stdout, "{}", parser.generate_help())?; - - // The following listing is something autoconf detection relies on. - writeln!(stdout, "wild: supported targets:elf64 -x86-64 elf64-littleaarch64 elf64-littleriscv elf64-loongarch")?; - writeln!(stdout, "wild: supported emulations: elf_x86_64 aarch64elf elf64lriscv elf64loongarch")?; - - std::process::exit(0); - }); - - parser - .declare() - .long("version") - .help("Show version information and exit") - .execute(|args, _modifier_stack| { - args.version_mode = VersionMode::ExitAfterPrint; - Ok(()) - }); - - parser - .declare() - .short("v") - .help("Print version and continue linking") - .execute(|args, _modifier_stack| { - args.version_mode = VersionMode::Verbose; - Ok(()) - }); - - parser - .declare() - .long("demangle") - .help("Enable symbol demangling") - .execute(|args, _modifier_stack| { - args.demangle = true; - Ok(()) - }); - - parser - .declare() - .long("no-demangle") - .help("Disable symbol demangling") - .execute(|args, _modifier_stack| { - args.demangle = false; - Ok(()) - }); - - parser - .declare_with_optional_param() - .long("time") - .help("Show timing information") - .execute(|args, _modifier_stack, value| { - match value { - Some(v) => args.time_phase_options = Some(parse_time_phase_options(v)?), - None => args.time_phase_options = Some(Vec::new()), - } - Ok(()) - }); - - parser - .declare_with_param() - .long("dynamic-linker") - .help("Set dynamic linker path") - .execute(|args, _modifier_stack, value| { - args.dynamic_linker = Some(Box::from(Path::new(value))); - Ok(()) - }); - - parser - .declare() - .long("no-dynamic-linker") - .help("Omit the load-time dynamic linker request") - .execute(|args, _modifier_stack| { - args.dynamic_linker = None; - Ok(()) - }); - - parser - .declare() - .long("mmap-output-file") - .help("Write output file using mmap (default)") - .execute(|args, _modifier_stack| { - args.mmap_output_file = true; - Ok(()) - }); - - parser - .declare() - .long("no-mmap-output-file") - .help("Write output file without mmap") - .execute(|args, _modifier_stack| { - args.mmap_output_file = false; - Ok(()) - }); - - parser - .declare_with_param() - .long("entry") - .short("e") - .help("Set the entry point") - .execute(|args, _modifier_stack, value| { - args.entry = Some(value.to_owned()); - Ok(()) - }); - - parser - .declare_with_optional_param() - .long("threads") - .help("Use multiple threads for linking") - .execute(|args, _modifier_stack, value| { - match value { - Some(v) => { - args.num_threads = Some(NonZeroUsize::try_from(v.parse::()?)?); - } - None => { - args.num_threads = None; // Default behaviour - } - } - Ok(()) - }); - - parser - .declare() - .long("no-threads") - .help("Use a single thread") - .execute(|args, _modifier_stack| { - args.num_threads = Some(NonZeroUsize::new(1).unwrap()); - Ok(()) - }); - - parser - .declare_with_param() - .long("wild-experiments") - .help("List of numbers. Used to tweak internal parameters. '_' keeps default value.") - .execute(|args, _modifier_stack, value| { - args.numeric_experiments = value - .split(',') - .map(|p| { - if p == "_" { - Ok(None) - } else { - Ok(Some(p.parse()?)) - } - }) - .collect::>>>()?; - Ok(()) - }); - - parser - .declare() - .long("as-needed") - .help("Set DT_NEEDED if used") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().as_needed = true; - Ok(()) - }); - - parser - .declare() - .long("no-as-needed") - .help("Always set DT_NEEDED") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().as_needed = false; - Ok(()) - }); - - parser - .declare() - .long("whole-archive") - .help("Include all objects from archives") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().whole_archive = true; - Ok(()) - }); - - parser - .declare() - .long("no-whole-archive") - .help("Disable --whole-archive") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().whole_archive = false; - Ok(()) - }); - - parser - .declare() - .long("push-state") - .help("Save current linker flags") - .execute(|_args, modifier_stack| { - modifier_stack.push(*modifier_stack.last().unwrap()); - Ok(()) - }); - - parser - .declare() - .long("pop-state") - .help("Restore previous linker flags") - .execute(|_args, modifier_stack| { - modifier_stack.pop(); - if modifier_stack.is_empty() { - bail!("Mismatched --pop-state"); - } - Ok(()) - }); - - parser - .declare() - .long("eh-frame-hdr") - .help("Create .eh_frame_hdr section") - .execute(|args, _modifier_stack| { - args.should_write_eh_frame_hdr = true; - Ok(()) - }); - - parser - .declare() - .long("no-eh-frame-hdr") - .help("Don't create .eh_frame_hdr section") - .execute(|args, _modifier_stack| { - args.should_write_eh_frame_hdr = false; - Ok(()) - }); - - parser - .declare() - .long("export-dynamic") - .short("E") - .help("Export all dynamic symbols") - .execute(|args, _modifier_stack| { - args.export_all_dynamic_symbols = true; - Ok(()) - }); - - parser - .declare() - .long("no-export-dynamic") - .help("Do not export dynamic symbols") - .execute(|args, _modifier_stack| { - args.export_all_dynamic_symbols = false; - Ok(()) - }); - - parser - .declare_with_param() - .long("soname") - .prefix("h") - .help("Set shared object name") - .execute(|args, _modifier_stack, value| { - args.soname = Some(value.to_owned()); - Ok(()) - }); - - parser - .declare_with_param() - .long("rpath") - .help("Add directory to runtime library search path") - .execute(|args, _modifier_stack, value| { - args.rpath_set.insert(value.to_string()); - Ok(()) - }); - - parser - .declare() - .long("no-string-merge") - .help("Disable section merging") - .execute(|args, _modifier_stack| { - args.merge_sections = false; - Ok(()) - }); - - parser - .declare() - .long("no-undefined") - .help("Do not allow unresolved symbols in object files") - .execute(|args, _modifier_stack| { - args.no_undefined = true; - Ok(()) - }); - - parser - .declare() - .long("allow-multiple-definition") - .help("Allow multiple definitions of symbols") - .execute(|args, _modifier_stack| { - args.allow_multiple_definitions = true; - Ok(()) - }); - - parser - .declare() - .long("relax") - .help("Enable target-specific optimization (instruction relaxation)") - .execute(|args, _modifier_stack| { - args.relax = true; - Ok(()) - }); - - parser - .declare() - .long("no-relax") - .help("Disable relaxation") - .execute(|args, _modifier_stack| { - args.relax = false; - Ok(()) - }); - - parser - .declare() - .long("validate-output") - .execute(|args, _modifier_stack| { - args.validate_output = true; - Ok(()) - }); - - parser - .declare() - .long("write-layout") - .execute(|args, _modifier_stack| { - args.write_layout = true; - Ok(()) - }); - - parser - .declare() - .long("write-trace") - .execute(|args, _modifier_stack| { - args.write_trace = true; - Ok(()) - }); - - parser - .declare() - .long("got-plt-syms") - .help("Write symbol table entries that point to the GOT/PLT entry for symbols") - .execute(|args, _modifier_stack| { - args.got_plt_syms = true; - Ok(()) - }); - - parser - .declare() - .long("Bsymbolic") - .help("Bind global references locally") - .execute(|args, _modifier_stack| { - args.b_symbolic = BSymbolicKind::All; - Ok(()) - }); - - parser - .declare() - .long("Bsymbolic-functions") - .help("Bind global function references locally") - .execute(|args, _modifier_stack| { - args.b_symbolic = BSymbolicKind::Functions; - Ok(()) - }); - - parser - .declare() - .long("Bsymbolic-non-weak-functions") - .help("Bind non-weak global function references locally") - .execute(|args, _modifier_stack| { - args.b_symbolic = BSymbolicKind::NonWeakFunctions; - Ok(()) - }); - - parser - .declare() - .long("Bsymbolic-non-weak") - .help("Bind non-weak global references locally") - .execute(|args, _modifier_stack| { - args.b_symbolic = BSymbolicKind::NonWeak; - Ok(()) - }); - - parser - .declare() - .long("Bno-symbolic") - .help("Do not bind global symbol references locally") - .execute(|args, _modifier_stack| { - args.b_symbolic = BSymbolicKind::None; - Ok(()) - }); - - parser - .declare_with_param() - .long("thread-count") - .help("Set the number of threads to use") - .execute(|args, _modifier_stack, value| { - args.num_threads = Some(NonZeroUsize::try_from(value.parse::()?)?); - Ok(()) - }); - - parser - .declare_with_param() - .long("exclude-libs") - .help("Exclude libraries") - .execute(|args, _modifier_stack, value| { - for lib in value.split([',', ':']) { - if lib.is_empty() { - continue; - } - - if lib == "ALL" { - args.exclude_libs = ExcludeLibs::All; - return Ok(()); - } - - match &mut args.exclude_libs { - ExcludeLibs::All => {} - ExcludeLibs::None => { - let mut set = HashSet::new(); - set.insert(Box::from(lib)); - args.exclude_libs = ExcludeLibs::Some(set); - } - ExcludeLibs::Some(set) => { - set.insert(Box::from(lib)); - } - } - } - - Ok(()) - }); - - parser - .declare_with_param() - .long("version-script") - .help("Use version script") - .execute(|args, _modifier_stack, value| { - args.save_dir.handle_file(value); - args.version_script_path = Some(PathBuf::from(value)); - Ok(()) - }); - - parser - .declare_with_param() - .long("script") - .prefix("T") - .help("Use linker script") - .execute(|args, _modifier_stack, value| { - args.save_dir.handle_file(value); - args.add_script(value); - Ok(()) - }); - - parser - .declare_with_param() - .long("export-dynamic-symbol") - .help("Export dynamic symbol") - .execute(|args, _modifier_stack, value| { - args.export_list.push(value.to_owned()); - Ok(()) - }); - - parser - .declare_with_param() - .long("export-dynamic-symbol-list") - .help("Export dynamic symbol list") - .execute(|args, _modifier_stack, value| { - args.export_list_path = Some(PathBuf::from(value)); - Ok(()) - }); - - parser - .declare_with_param() - .long("dynamic-list") - .help("Read the dynamic symbol list from a file") - .execute(|args, _modifier_stack, value| { - args.b_symbolic = BSymbolicKind::All; - args.export_list_path = Some(PathBuf::from(value)); - Ok(()) - }); - - parser - .declare_with_param() - .long("write-gc-stats") - .help("Write GC statistics") - .execute(|args, _modifier_stack, value| { - args.write_gc_stats = Some(PathBuf::from(value)); - Ok(()) - }); - - parser - .declare_with_param() - .long("gc-stats-ignore") - .help("Ignore files in GC stats") - .execute(|args, _modifier_stack, value| { - args.gc_stats_ignore.push(value.to_owned()); - Ok(()) - }); - - parser - .declare() - .long("no-identity-comment") - .help("Don't write the linker name and version in .comment") - .execute(|args, _modifier_stack| { - args.should_write_linker_identity = false; - Ok(()) - }); +// ── End argument parser infrastructure ─────────────────────────────────────── - parser - .declare_with_param() - .long("debug-address") - .help("Set debug address") - .execute(|args, _modifier_stack, value| { - args.debug_address = Some(parse_number(value).context("Invalid --debug-address")?); - Ok(()) - }); +pub(crate) fn add_silently_ignored_flags(parser: &mut ArgumentParser) { + fn noop(_args: &mut Args, _modifier_stack: &mut Vec) -> Result<()> { + Ok(()) + } + for flag in SILENTLY_IGNORED_FLAGS { + parser.declare().long(flag).execute(noop); + } + for flag in SILENTLY_IGNORED_SHORT_FLAGS { + parser.declare().short(flag).execute(noop); + } +} - parser - .declare_with_param() - .long("debug-fuel") - .execute(|args, _modifier_stack, value| { - args.debug_fuel = Some(AtomicI64::new(value.parse()?)); - args.num_threads = Some(NonZeroUsize::new(1).unwrap()); - Ok(()) - }); +pub(crate) fn add_default_flags(parser: &mut ArgumentParser) { + fn noop(_args: &mut Args, _modifier_stack: &mut Vec) -> Result<()> { + Ok(()) + } + for flag in DEFAULT_FLAGS { + parser.declare().long(flag).execute(noop); + } + for flag in DEFAULT_SHORT_FLAGS { + parser.declare().short(flag).execute(noop); + } +} - parser - .declare_with_param() - .long("unresolved-symbols") - .help("Specify how to handle unresolved symbols") - .execute(|args, _modifier_stack, value| { - args.unresolved_symbols = match value { - "report-all" => UnresolvedSymbols::ReportAll, - "ignore-in-shared-libs" => UnresolvedSymbols::IgnoreInSharedLibs, - "ignore-in-object-files" => UnresolvedSymbols::IgnoreInObjectFiles, - "ignore-all" => UnresolvedSymbols::IgnoreAll, - _ => bail!("Invalid unresolved-symbols value {value}"), - }; - Ok(()) - }); +pub(crate) fn read_args_from_file(path: &Path) -> Result> { + let contents = std::fs::read_to_string(path) + .with_context(|| format!("Failed to read arguments from file `{}`", path.display()))?; + arguments_from_string(&contents) +} - parser - .declare_with_param() - .long("undefined") - .help("Force resolution of the symbol") - .execute(|args, _modifier_stack, value| { - args.undefined.push(value.to_owned()); - Ok(()) - }); +/// Parses arguments from a string, handling quoting, escapes etc. +/// All arguments must be surrounded by a white space. +pub(crate) fn arguments_from_string(input: &str) -> Result> { + const QUOTES: [char; 2] = ['\'', '"']; - parser - .declare_with_param() - .long("wrap") - .help("Use a wrapper function") - .execute(|args, _modifier_stack, value| { - args.wrap.push(value.to_owned()); - Ok(()) - }); + let mut out = Vec::new(); + let mut chars = input.chars(); + let mut heap = None; + let mut quote = None; + let mut expect_whitespace = false; - parser - .declare_with_param() - .long("defsym") - .help("Define a symbol alias: --defsym=symbol=value") - .execute(|args, _modifier_stack, value| { - let parts: Vec<&str> = value.splitn(2, '=').collect(); - if parts.len() != 2 { - bail!("Invalid --defsym format. Expected: --defsym=symbol=value"); + loop { + let Some(mut ch) = chars.next() else { + if let Some(quote) = quote.take() { + bail!("Missing closing '{quote}'"); } - let symbol_name = parts[0].to_owned(); - let value_str = parts[1]; - - let defsym_value = parse_defsym_expression(value_str); + if let Some(arg) = heap.take() { + out.push(arg); + } + break; + }; - args.defsym.push((symbol_name, defsym_value)); - Ok(()) - }); + crate::ensure!( + !expect_whitespace || ch.is_whitespace(), + "Expected white space after quoted argument" + ); + expect_whitespace = false; - parser - .declare_with_param() - .long("section-start") - .help("Set start address for a section: --section-start=.section=address") - .execute(|args, _modifier_stack, value| { - let parts: Vec<&str> = value.splitn(2, '=').collect(); - if parts.len() != 2 { - bail!("Invalid --section-start format. Expected: --section-start=.section=address"); + if QUOTES.contains(&ch) { + if let Some(qchr) = quote { + if qchr == ch { + // close the argument + if let Some(arg) = heap.take() { + out.push(arg); + } + quote = None; + expect_whitespace = true; + } else { + // accept the other quoting character as normal char + heap.get_or_insert(String::new()).push(ch); + } + } else { + // beginning of a new argument + crate::ensure!(heap.is_none(), "Missing opening quote '{ch}'"); + quote = Some(ch); } + } else if ch.is_whitespace() { + if quote.is_none() { + if let Some(arg) = heap.take() { + out.push(arg); + } + } else { + heap.get_or_insert(String::new()).push(ch); + } + } else { + if ch == '\\' && (quote.is_some() || !cfg!(target_os = "windows")) { + ch = chars.next().context("Invalid escape")?; + } + heap.get_or_insert(String::new()).push(ch); + } + } - let section_name = parts[0].to_owned(); - let address = parse_number(parts[1]).with_context(|| { - format!( - "Invalid address `{}` in --section-start={}", - parts[1], value - ) - })?; - args.section_start.insert(section_name, address); - - Ok(()) - }); + Ok(out) +} - parser - .declare_with_param() - .long("hash-style") - .help("Set hash style") - .execute(|args, _modifier_stack, value| { - args.hash_style = match value { - "gnu" => HashStyle::Gnu, - "sysv" => HashStyle::Sysv, - "both" => HashStyle::Both, - _ => bail!("Unknown hash-style `{value}`"), - }; - Ok(()) - }); +pub(super) fn warn_unsupported(opt: &str) -> Result { + match std::env::var(WILD_UNSUPPORTED_ENV) + .unwrap_or_default() + .as_str() + { + "warn" | "" => crate::error::warning(&format!("{opt} is not yet supported")), + "ignore" => {} + "error" => bail!("{opt} is not yet supported"), + other => bail!("Unsupported value for {WILD_UNSUPPORTED_ENV}={other}"), + } + Ok(()) +} - parser - .declare() - .long("enable-new-dtags") - .help("Use DT_RUNPATH and DT_FLAGS/DT_FLAGS_1 (default)") - .execute(|args, _modifier_stack| { - args.enable_new_dtags = true; - Ok(()) - }); +/// Result of pre-scanning args for target-determining flags. +#[derive(Debug)] +pub(crate) struct DetectedTarget { + pub format: target_lexicon::BinaryFormat, + /// Architecture from `--target` triple. `None` if no `--target` was given. + pub arch: Option, +} - parser - .declare() - .long("disable-new-dtags") - .help("Use DT_RPATH and individual dynamic entries instead of DT_FLAGS") - .execute(|args, _modifier_stack| { - args.enable_new_dtags = false; - Ok(()) - }); +/// Known `-m` emulation values that imply ELF output. +const ELF_EMULATIONS: &[&str] = &[ + "elf_x86_64", + "elf_x86_64_sol2", + "aarch64elf", + "aarch64linux", + "elf64lriscv", + "elf64loongarch", +]; - parser - .declare_with_param() - .long("retain-symbols-file") - .help( - "Filter symtab to contain only symbols listed in the supplied file. \ - One symbol per line.", - ) - .execute(|args, _modifier_stack, value| { - // The performance this flag is not especially optimised. For one, we copy each string - // to the heap. We also do two lookups in the hashset for each symbol. This is a pretty - // obscure flag that we don't expect to be used much, so at this stage, it doesn't seem - // worthwhile to optimise it. - let contents = std::fs::read_to_string(value) - .with_context(|| format!("Failed to read `{value}`"))?; - args.strip = Strip::Retain( - contents - .lines() - .filter_map(|l| { - if l.is_empty() { - None - } else { - Some(l.as_bytes().to_owned()) - } - }) - .collect(), - ); - Ok(()) - }); +/// Map `target_lexicon::Architecture` to Wild's `Architecture`. +fn map_triple_arch(arch: target_lexicon::Architecture) -> Result { + use target_lexicon::Architecture as TL; + match arch { + TL::X86_64 | TL::X86_64h => Ok(Architecture::X86_64), + TL::Aarch64(_) => Ok(Architecture::AArch64), + TL::Riscv64(_) => Ok(Architecture::RISCV64), + TL::LoongArch64 => Ok(Architecture::LoongArch64), + other => bail!("unsupported architecture in target triple: {other}"), + } +} - parser - .declare_with_param() - .long("build-id") - .help("Generate build ID") - .execute(|args, _modifier_stack, value| { - args.build_id = match value { - "none" => BuildIdOption::None, - "fast" | "md5" | "sha1" => BuildIdOption::Fast, - "uuid" => BuildIdOption::Uuid, - s if s.starts_with("0x") || s.starts_with("0X") => { - let hex_string = &s[2..]; - let decoded_bytes = hex::decode(hex_string) - .with_context(|| format!("Invalid Hex Build Id `0x{hex_string}`"))?; - BuildIdOption::Hex(decoded_bytes) - } - s => bail!( - "Invalid build-id value `{s}` valid values are `none`, `fast`, `md5`, `sha1` and `uuid`" - ), - }; - Ok(()) - }); +/// Extract the target triple value from a flag, handling all prefix styles. +/// Returns `(Some(value), consumed_next)` if the arg is a target flag. +fn extract_target_value<'a>(arg: &'a str, next_arg: Option<&'a str>) -> (Option<&'a str>, bool) { + // Combined forms: --target=VAL, -target=VAL, /TARGET:VAL + if let Some(val) = arg + .strip_prefix("--target=") + .or_else(|| arg.strip_prefix("-target=")) + .or_else(|| arg.strip_prefix("/TARGET:")) + .or_else(|| arg.strip_prefix("/target:")) + { + return (Some(val), false); + } + // Space-separated: --target VAL, -target VAL, /TARGET VAL + if matches!(arg, "--target" | "-target" | "/TARGET" | "/target") { + if let Some(val) = next_arg { + return (Some(val), true); + } + } + (None, false) +} - parser - .declare_with_param() - .long("icf") - .help("Enable identical code folding (merge duplicate functions)") - .execute(|_args, _modifier_stack, value| { - match value { - "none" => {} - other => warn_unsupported(&format!("--icf={other}"))?, +/// Pre-scan CLI arguments to determine the output format and architecture. +/// +/// Recognizes: +/// - `--target=` / `-target=` / `/TARGET:` — primary (parsed by target-lexicon) +/// - `-m ` — overrides format to ELF when present +/// +/// Priority: `-m` overrides format from `--target`. Architecture comes from `--target` only. +pub(crate) fn detect_target(args: &[String]) -> Result { + let mut from_triple: Option<(BinaryFormat, Architecture)> = None; + let mut m_implies_elf = false; + + let mut i = 0; + while i < args.len() { + let next = if i + 1 < args.len() { + Some(args[i + 1].as_str()) + } else { + None + }; + let (target_val, consumed_next) = extract_target_value(&args[i], next); + + if let Some(val) = target_val { + let triple: Triple = val + .parse() + .map_err(|e| anyhow::anyhow!("invalid target triple '{val}': {e}"))?; + let arch = map_triple_arch(triple.architecture)?; + from_triple = Some((triple.binary_format, arch)); + if consumed_next { + i += 1; } - Ok(()) - }); - - parser - .declare_with_param() - .long("sysroot") - .help("Set system root") - .execute(|args, _modifier_stack, value| { - args.save_dir.handle_file(value); - let sysroot = std::fs::canonicalize(value).unwrap_or_else(|_| PathBuf::from(value)); - args.sysroot = Some(Box::from(sysroot.as_path())); - for path in &mut args.lib_search_path { - if let Some(new_path) = maybe_forced_sysroot(path, &sysroot) { - *path = new_path; + } + // Check for -m (implies ELF) + else if args[i] == "-m" || args[i] == "--m" { + if let Some(next_val) = next { + if ELF_EMULATIONS.contains(&next_val) { + m_implies_elf = true; } + i += 1; } - Ok(()) - }); - - parser - .declare_with_param() - .long("auxiliary") - .short("f") - .help("Set DT_AUXILIARY to a given value") - .execute(|args, _modifier_stack, value| { - args.auxiliary.push(value.to_owned()); - Ok(()) - }); - - parser - .declare_with_param() - .long("plugin-opt") - .help("Pass options to the plugin") - .execute(|args, _modifier_stack, value| { - args.plugin_args - .push(CString::new(value).context("Invalid --plugin-opt argument")?); - Ok(()) - }); + } else if let Some(emu) = args[i].strip_prefix("-m") { + if ELF_EMULATIONS.contains(&emu) { + m_implies_elf = true; + } + } - parser - .declare_with_param() - .long("dependency-file") - .help("Write dependency rules") - .execute(|args, _modifier_stack, value| { - args.dependency_file = Some(PathBuf::from(value)); - Ok(()) - }); + i += 1; + } - parser - .declare_with_param() - .long("plugin") - .help("Load plugin") - .execute(|args, _modifier_stack, value| { - args.plugin_path = Some(value.to_owned()); - Ok(()) - }); + match (from_triple, m_implies_elf) { + (Some((_, arch)), true) => { + // -m overrides format to ELF; arch from triple preserved + Ok(DetectedTarget { + format: BinaryFormat::Elf, + arch: Some(arch), + }) + } + (Some((format, arch)), false) => Ok(DetectedTarget { + format, + arch: Some(arch), + }), + (None, true) => Ok(DetectedTarget { + format: BinaryFormat::Elf, + arch: None, + }), + (None, false) => Ok(DetectedTarget { + format: BinaryFormat::host(), + arch: None, + }), + } +} - parser - .declare_with_param() - .long("rpath-link") - .help("Add runtime library search path") - .execute(|_args, _modifier_stack, _value| { - // TODO - Ok(()) - }); +/// Map Wild `Architecture` to the GNU ld `-m` emulation name. +fn arch_to_elf_emulation(arch: Architecture) -> &'static str { + match arch { + Architecture::X86_64 => "elf_x86_64", + Architecture::AArch64 => "aarch64linux", + Architecture::RISCV64 => "elf64lriscv", + Architecture::LoongArch64 => "elf64loongarch", + } +} - parser - .declare_with_param() - .long("sym-info") - .help("Show symbol information. Accepts symbol name or ID.") - .execute(|args, _modifier_stack, value| { - args.sym_info = Some(value.to_owned()); - Ok(()) - }); +/// Map Wild `Architecture` to the MSVC `/MACHINE:` value. +fn arch_to_machine_value(arch: Architecture) -> &'static str { + match arch { + Architecture::X86_64 => "X64", + Architecture::AArch64 => "ARM64", + Architecture::RISCV64 => "X64", + Architecture::LoongArch64 => "X64", + } +} - parser - .declare() - .long("start-lib") - .help("Start library group") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().archive_semantics = true; - Ok(()) - }); +/// Strip `--target`/`-target`/`/TARGET` flags and inject a synthetic `-m` or `/MACHINE:` flag +/// from the detected architecture so the format-specific parser picks it up. +/// +/// The user's explicit `-m` or `/MACHINE:` flags are preserved and will override the injected one +/// since they appear later in the argument list. +pub(crate) fn filter_and_inject_target_flags( + args: &[String], + format: BinaryFormat, + arch: Option, +) -> Vec { + let mut result = Vec::with_capacity(args.len() + 2); + + // Inject synthetic arch flag at the front (user's explicit flags override later) + if let Some(arch) = arch { + match format { + BinaryFormat::Elf => { + result.push("-m".to_string()); + result.push(arch_to_elf_emulation(arch).to_string()); + } + BinaryFormat::Coff => { + result.push(format!("/MACHINE:{}", arch_to_machine_value(arch))); + } + _ => { /* no additional flags needed for other formats */ } + } + } - parser - .declare() - .long("end-lib") - .help("End library group") - .execute(|_args, modifier_stack| { - modifier_stack.last_mut().unwrap().archive_semantics = false; - Ok(()) - }); + // Strip --target flags, keep everything else + let mut i = 0; + while i < args.len() { + let arg = &args[i]; + if arg.starts_with("--target=") + || arg.starts_with("-target=") + || arg.starts_with("/TARGET:") + || arg.starts_with("/target:") + { + // Skip this combined arg + } else if matches!(arg.as_str(), "--target" | "-target" | "/TARGET" | "/target") { + i += 1; // skip value too + } else { + result.push(arg.clone()); + } + i += 1; + } + result +} - parser - .declare() - .long("no-fork") - .help("Do not fork while linking") - .execute(|args, _modifier_stack| { - args.should_fork = false; - Ok(()) - }); +/// Format-specific parsed arguments. +pub enum TargetArgs { + Elf(linux::ElfArgs), + #[allow(dead_code)] + Pe(windows::PeArgs), +} - parser - .declare() - .long("update-in-place") - .help("Update file in place") - .execute(|args, _modifier_stack| { - args.file_write_mode = Some(FileWriteMode::UpdateInPlace); - Ok(()) - }); +impl std::fmt::Debug for TargetArgs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TargetArgs::Elf(e) => e.fmt(f), + TargetArgs::Pe(p) => p.fmt(f), + } + } +} - parser - .declare() - .long("no-update-in-place") - .help("Delete and recreate the file") - .execute(|args, _modifier_stack| { - args.file_write_mode = Some(FileWriteMode::UnlinkAndReplace); - Ok(()) - }); +impl std::ops::Deref for Args { + type Target = T; + fn deref(&self) -> &T { + &self.target_args + } +} - parser - .declare() - .long("EB") - .help("Big-endian (not supported)") - .execute(|_args, _modifier_stack| { - bail!("Big-endian target is not supported"); - }); +impl std::ops::DerefMut for Args { + fn deref_mut(&mut self) -> &mut T { + &mut self.target_args + } +} - parser - .declare() - .long("prepopulate-maps") - .help("Prepopulate maps") - .execute(|args, _modifier_stack| { - args.prepopulate_maps = true; - Ok(()) - }); +impl Args { + /// Transform the target-specific part while preserving common fields. + pub fn map_target(self, f: impl FnOnce(T) -> U) -> Args { + Args { + // Infrastructure + should_fork: self.should_fork, + output: self.output, + arch: self.arch, + inputs: self.inputs, + lib_search_path: self.lib_search_path, + num_threads: self.num_threads, + available_threads: self.available_threads, + save_dir: self.save_dir, + unrecognized_options: self.unrecognized_options, + files_per_group: self.files_per_group, + write_layout: self.write_layout, + write_trace: self.write_trace, + jobserver_client: self.jobserver_client, + // Core linker behavior + strip: self.strip, + gc_sections: self.gc_sections, + merge_sections: self.merge_sections, + relax: self.relax, + demangle: self.demangle, + no_undefined: self.no_undefined, + allow_shlib_undefined: self.allow_shlib_undefined, + error_unresolved_symbols: self.error_unresolved_symbols, + allow_multiple_definitions: self.allow_multiple_definitions, + unresolved_symbols: self.unresolved_symbols, + undefined: self.undefined, + copy_relocations: self.copy_relocations, + sysroot: self.sysroot, + dynamic_linker: self.dynamic_linker, + entry: self.entry, + wrap: self.wrap, + exclude_libs: self.exclude_libs, + b_symbolic: self.b_symbolic, + export_list: self.export_list, + defsym: self.defsym, + section_start: self.section_start, + max_page_size: self.max_page_size, + execstack: self.execstack, + version_mode: self.version_mode, + relocation_model: self.relocation_model, + should_output_executable: self.should_output_executable, + export_all_dynamic_symbols: self.export_all_dynamic_symbols, + version_script_path: self.version_script_path, + export_list_path: self.export_list_path, + // Output/writing + mmap_output_file: self.mmap_output_file, + file_write_mode: self.file_write_mode, + prepopulate_maps: self.prepopulate_maps, + should_write_linker_identity: self.should_write_linker_identity, + // Debug/diagnostic + debug_fuel: self.debug_fuel, + validate_output: self.validate_output, + sym_info: self.sym_info, + debug_address: self.debug_address, + print_allocations: self.print_allocations, + verify_allocation_consistency: self.verify_allocation_consistency, + time_phase_options: self.time_phase_options, + numeric_experiments: self.numeric_experiments, + write_gc_stats: self.write_gc_stats, + gc_stats_ignore: self.gc_stats_ignore, + verbose_gc_stats: self.verbose_gc_stats, + dependency_file: self.dependency_file, + // Format-specific + target_args: f(self.target_args), + } + } - parser - .declare() - .long("verbose-gc-stats") - .help("Show GC statistics") - .execute(|args, _modifier_stack| { - args.verbose_gc_stats = true; - Ok(()) - }); + pub fn map_ref_target(&self, f: impl FnOnce(&T) -> U) -> U { + f(&self.target_args) + } - parser - .declare() - .long("allow-shlib-undefined") - .help("Allow undefined symbol references in shared libraries") - .execute(|args, _modifier_stack| { - args.allow_shlib_undefined = true; - Ok(()) - }); + /// Uses 1 debug fuel, returning how much fuel remains. Debug fuel is intended to be used when + /// debugging certain kinds of bugs, so this function isn't normally referenced. To use it, the + /// caller should take a different branch depending on whether the value is still positive. You + /// can then do a binary search. + pub(crate) fn use_debug_fuel(&self) -> i64 { + let Some(fuel) = self.debug_fuel.as_ref() else { + return i64::MAX; + }; + fuel.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) - 1 + } - parser - .declare() - .long("no-allow-shlib-undefined") - .help("Disallow undefined symbol references in shared libraries") - .execute(|args, _modifier_stack| { - args.allow_shlib_undefined = false; - Ok(()) - }); + /// Returns whether there was sufficient fuel. If the last bit of fuel was used, then calls + /// `last_cb`. + #[allow(unused)] + pub(crate) fn use_debug_fuel_on_last(&self, last_cb: impl FnOnce()) -> bool { + match self.use_debug_fuel() { + 1.. => true, + 0 => { + last_cb(); + true + } + _ => false, + } + } - parser - .declare() - .long("error-unresolved-symbols") - .help("Treat unresolved symbols as errors") - .execute(|args, _modifier_stack| { - args.error_unresolved_symbols = true; - Ok(()) - }); + pub(crate) fn trace_span_for_file( + &self, + file_id: FileId, + ) -> Option { + let should_trace = self.print_allocations == Some(file_id); + should_trace.then(|| tracing::trace_span!(crate::debug_trace::TRACE_SPAN_NAME).entered()) + } - parser - .declare() - .long("warn-unresolved-symbols") - .help("Treat unresolved symbols as warnings") - .execute(|args, _modifier_stack| { - args.error_unresolved_symbols = false; - Ok(()) - }); + pub(crate) fn numeric_experiment(&self, exp: Experiment, default: u64) -> u64 { + self.numeric_experiments + .get(exp as usize) + .copied() + .flatten() + .unwrap_or(default) + } - add_silently_ignored_flags(&mut parser); - add_default_flags(&mut parser); + pub(crate) fn loadable_segment_alignment(&self) -> Alignment { + if let Some(max_page_size) = self.max_page_size { + return max_page_size; + } - parser -} + match self.arch { + Architecture::X86_64 => Alignment { exponent: 12 }, + Architecture::AArch64 => Alignment { exponent: 16 }, + Architecture::RISCV64 => Alignment { exponent: 12 }, + Architecture::LoongArch64 => Alignment { exponent: 16 }, + } + } -fn add_silently_ignored_flags(parser: &mut ArgumentParser) { - for flag in SILENTLY_IGNORED_FLAGS { - let mut declaration = parser.declare(); - declaration = declaration.long(flag); - declaration.execute(|_args, _modifier_stack| Ok(())); + pub(crate) fn strip_all(&self) -> bool { + matches!(self.strip, Strip::All) } - for flag in SILENTLY_IGNORED_SHORT_FLAGS { - let mut declaration = parser.declare(); - declaration = declaration.short(flag); - declaration.execute(|_args, _modifier_stack| Ok(())); + + pub(crate) fn strip_debug(&self) -> bool { + matches!(self.strip, Strip::All | Strip::Debug) } } -fn add_default_flags(parser: &mut ArgumentParser) { - for flag in DEFAULT_FLAGS { - let mut declaration = parser.declare(); - declaration = declaration.long(flag); - declaration.execute(|_args, _modifier_stack| Ok(())); - } - for flag in DEFAULT_SHORT_FLAGS { - let mut declaration = parser.declare(); - declaration = declaration.short(flag); - declaration.execute(|_args, _modifier_stack| Ok(())); +impl ActivatedArgs { + pub fn map_target(self, f: impl FnOnce(T) -> U) -> ActivatedArgs { + ActivatedArgs { + args: self.args.map_target(f), + _jobserver_tokens: self._jobserver_tokens, + } } } -fn parse_time_phase_options(input: &str) -> Result> { - input.split(',').map(|s| s.parse()).collect() -} +impl Args { + /// Sets up the thread pool, using the explicit number of threads if specified, + /// or falling back to the jobserver protocol if available. + /// + /// + pub fn activate_thread_pool(mut self) -> Result> { + crate::timing_phase!("Activate thread pool"); + + let mut tokens = Vec::new(); + self.available_threads = self.num_threads.unwrap_or_else(|| { + if let Some(client) = &self.jobserver_client { + while let Ok(Some(acquired)) = client.try_acquire() { + tokens.push(acquired); + } + tracing::trace!(count = tokens.len(), "Acquired jobserver tokens"); + // Our parent "holds" one jobserver token, add it. + NonZeroUsize::new((tokens.len() + 1).max(1)).unwrap() + } else { + std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()) + } + }); + + // The pool might be already initialized, suppress the error intentionally. + let _ = rayon::ThreadPoolBuilder::new() + .num_threads(self.available_threads.get()) + .build_global(); -impl FromStr for CounterKind { - type Err = crate::error::Error; - - fn from_str(s: &str) -> Result { - Ok(match s { - "cycles" => CounterKind::Cycles, - "instructions" => CounterKind::Instructions, - "cache-misses" => CounterKind::CacheMisses, - "branch-misses" => CounterKind::BranchMisses, - "page-faults" => CounterKind::PageFaults, - "page-faults-minor" => CounterKind::PageFaultsMinor, - "page-faults-major" => CounterKind::PageFaultsMajor, - "l1d-read" => CounterKind::L1dRead, - "l1d-miss" => CounterKind::L1dMiss, - other => bail!("Unsupported performance counter `{other}`"), + Ok(ActivatedArgs { + args: self, + _jobserver_tokens: tokens, }) } } @@ -2658,314 +1508,134 @@ impl Display for CopyRelocationsDisabledReason { #[cfg(test)] mod tests { - use super::SILENTLY_IGNORED_FLAGS; - use super::VersionMode; - use crate::Args; - use crate::args::InputSpec; - use itertools::Itertools; - use std::fs::File; - use std::io::BufWriter; - use std::io::Write; - use std::num::NonZeroUsize; - use std::path::Path; - use std::path::PathBuf; - use std::str::FromStr; - use tempfile::NamedTempFile; - - const INPUT1: &[&str] = &[ - "-pie", - "-z", - "relro", - "-zrelro", - "-hash-style=gnu", - "--hash-style=gnu", - "-build-id", - "--build-id", - "--eh-frame-hdr", - "-m", - "elf_x86_64", - "-dynamic-linker", - "/lib64/ld-linux-x86-64.so.2", - "-o", - "/build/target/debug/deps/c1-a212b73b12b6d123", - "/lib/x86_64-linux-gnu/Scrt1.o", - "/lib/x86_64-linux-gnu/crti.o", - "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtbeginS.o", - "-L/build/target/debug/deps", - "-L/tool/lib/rustlib/x86_64/lib", - "-L/tool/lib/rustlib/x86_64/lib", - "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12", - "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../lib64", - "-L/lib/x86_64-linux-gnu", - "-L/lib/../lib64", - "-L/usr/lib/x86_64-linux-gnu", - "-L/usr/lib/../lib64", - "-L", - "/lib", - "-L/usr/lib", - "/tmp/rustcDcR20O/symbols.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.1.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.2.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.3.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.4.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.5.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.6.rcgu.o", - "/build/target/debug/deps/c1-a212b73b12b6d123.7.rcgu.o", - "--as-needed", - "-as-needed", - "-Bstatic", - "/tool/lib/rustlib/x86_64/lib/libstd-6498d8891e016dca.rlib", - "/tool/lib/rustlib/x86_64/lib/libpanic_unwind-3debdee1a9058d84.rlib", - "/tool/lib/rustlib/x86_64/lib/libobject-8339c5bd5cbc92bf.rlib", - "/tool/lib/rustlib/x86_64/lib/libmemchr-160ebcebb54c11ba.rlib", - "/tool/lib/rustlib/x86_64/lib/libaddr2line-95c75789f1b65e37.rlib", - "/tool/lib/rustlib/x86_64/lib/libgimli-7e8094f2d6258832.rlib", - "/tool/lib/rustlib/x86_64/lib/librustc_demangle-bac9783ef1b45db0.rlib", - "/tool/lib/rustlib/x86_64/lib/libstd_detect-a1cd87df2f2d8e76.rlib", - "/tool/lib/rustlib/x86_64/lib/libhashbrown-7fd06d468d7dba16.rlib", - "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_alloc-5ac19487656e05bf.rlib", - "/tool/lib/rustlib/x86_64/lib/libminiz_oxide-c7c35d32cf825c11.rlib", - "/tool/lib/rustlib/x86_64/lib/libadler-c523f1571362e70b.rlib", - "/tool/lib/rustlib/x86_64/lib/libunwind-85f17c92b770a911.rlib", - "/tool/lib/rustlib/x86_64/lib/libcfg_if-598d3ba148dadcea.rlib", - "/tool/lib/rustlib/x86_64/lib/liblibc-a58ec2dab545caa4.rlib", - "/tool/lib/rustlib/x86_64/lib/liballoc-f9dda8cca149f0fc.rlib", - "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_core-7ba4c315dd7a3503.rlib", - "/tool/lib/rustlib/x86_64/lib/libcore-5ac2993e19124966.rlib", - "/tool/lib/rustlib/x86_64/lib/libcompiler_builtins-df2fb7f50dec519a.rlib", - "-Bdynamic", - "-lgcc_s", - "-lutil", - "-lrt", - "-lpthread", - "-lm", - "-ldl", - "-lc", - "--eh-frame-hdr", - "-z", - "noexecstack", - "-znoexecstack", - "--gc-sections", - "-z", - "relro", - "-z", - "now", - "-z", - "lazy", - "-soname=fpp", - "-soname", - "bar", - "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtendS.o", - "/lib/x86_64-linux-gnu/crtn.o", - "--version-script", - "a.ver", - "--no-threads", - "--no-add-needed", - "--no-copy-dt-needed-entries", - "--discard-locals", - "--use-android-relr-tags", - "--pack-dyn-relocs=relr", - "-X", - "-EL", - "-O", - "1", - "-O3", - "-v", - "--sysroot=/usr/aarch64-linux-gnu", - "--demangle", - "--no-demangle", - "-l:lib85caec4suo0pxg06jm2ma7b0o.so", - "-rpath", - "foo/", - "-rpath=bar/", - "-Rbaz", - "-R", - "somewhere", - // Adding the same rpath multiple times should not create duplicates - "-rpath", - "foo/", - "-x", - "--discard-all", - "--dependency-file=deps.d", - ]; - - const FILE_OPTIONS: &[&str] = &["-pie"]; - - const INLINE_OPTIONS: &[&str] = &["-L", "/lib"]; - - fn write_options_to_file(file: &File, options: &[&str]) { - let mut writer = BufWriter::new(file); - for option in options { - writeln!(writer, "{option}").expect("Failed to write to temporary file"); - } - } + use super::*; - #[track_caller] - fn assert_contains(c: &[Box], v: &str) { - assert!(c.iter().any(|p| p.as_ref() == Path::new(v))); + fn to_strings(args: &[&str]) -> Vec { + args.iter().map(|s| s.to_string()).collect() } - fn input1_assertions(args: &Args) { - assert_eq!( - args.inputs - .iter() - .filter_map(|i| match &i.spec { - InputSpec::File(_) | InputSpec::Search(_) => None, - InputSpec::Lib(lib_name) => Some(lib_name.as_ref()), - }) - .collect_vec(), - &["gcc_s", "util", "rt", "pthread", "m", "dl", "c"] - ); - assert_contains(&args.lib_search_path, "/lib"); - assert_contains(&args.lib_search_path, "/usr/lib"); - assert!(!args.inputs.iter().any(|i| match &i.spec { - InputSpec::File(f) => f.as_ref() == Path::new("/usr/bin/ld"), - InputSpec::Lib(_) | InputSpec::Search(_) => false, - })); - assert_eq!( - args.version_script_path, - Some(PathBuf::from_str("a.ver").unwrap()) - ); - assert_eq!(args.soname, Some("bar".to_owned())); - assert_eq!(args.num_threads, Some(NonZeroUsize::new(1).unwrap())); - assert_eq!(args.version_mode, VersionMode::Verbose); - assert_eq!( - args.sysroot, - Some(Box::from(Path::new("/usr/aarch64-linux-gnu"))) - ); - assert!(args.inputs.iter().any(|i| match &i.spec { - InputSpec::File(_) | InputSpec::Lib(_) => false, - InputSpec::Search(lib) => lib.as_ref() == "lib85caec4suo0pxg06jm2ma7b0o.so", - })); - assert_eq!(args.rpath.as_deref(), Some("foo/:bar/:baz:somewhere")); - assert_eq!( - args.dependency_file, - Some(PathBuf::from_str("deps.d").unwrap()) - ); - } + // ---- detect_target tests ---- - fn inline_and_file_options_assertions(args: &Args) { - assert_contains(&args.lib_search_path, "/lib"); + #[test] + fn test_detect_format_from_triple_linux_x86() { + let args = to_strings(&["--target=x86_64-unknown-linux-gnu", "-o", "out"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Elf); + assert_eq!(result.arch, Some(Architecture::X86_64)); } #[test] - fn test_parse_inline_only_options() { - let args = super::parse(|| INPUT1.iter()).unwrap(); - input1_assertions(&args); + fn test_detect_format_from_triple_windows() { + let args = to_strings(&["-target=x86_64-pc-windows-msvc", "/OUT:foo.exe"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Coff); + assert_eq!(result.arch, Some(Architecture::X86_64)); } #[test] - fn test_parse_file_only_options() { - // Create a temporary file containing the same options (one per line) as INPUT1 - let file = NamedTempFile::new().expect("Could not create temp file"); - write_options_to_file(file.as_file(), INPUT1); - - // pass the name of the file where options are as the only inline option "@filename" - let inline_options = [format!("@{}", file.path().to_str().unwrap())]; - let args = super::parse(|| inline_options.iter()).unwrap(); - input1_assertions(&args); + fn test_detect_format_from_slash_target() { + let args = to_strings(&["/TARGET:aarch64-pc-windows-msvc", "foo.obj"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Coff); + assert_eq!(result.arch, Some(Architecture::AArch64)); } #[test] - fn test_parse_mixed_file_and_inline_options() { - // Create a temporary file containing some options - let file = NamedTempFile::new().expect("Could not create temp file"); - write_options_to_file(file.as_file(), FILE_OPTIONS); + fn test_detect_format_space_separated() { + let args = to_strings(&["--target", "aarch64-unknown-linux-gnu", "-o", "out"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Elf); + assert_eq!(result.arch, Some(Architecture::AArch64)); + } - // create an inline option referring to "@filename" - let file_option = format!("@{}", file.path().to_str().unwrap()); - // start with the set of inline options - let mut inline_options = INLINE_OPTIONS.to_vec(); - // and extend with the "@filename" option - inline_options.push(&file_option); + #[test] + fn test_detect_format_from_m_flag() { + let args = to_strings(&["-m", "elf_x86_64", "-o", "out"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Elf); + assert_eq!(result.arch, None); + } - // confirm that this works and the resulting set of options is correct - let args = super::parse(|| inline_options.iter()).unwrap(); - inline_and_file_options_assertions(&args); + #[test] + fn test_m_flag_overrides_target_format() { + let args = to_strings(&["--target=x86_64-pc-windows-msvc", "-m", "elf_x86_64"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Elf); } #[test] - fn test_parse_overlapping_file_and_inline_options() { - // Create a set of file options that has a duplicate of an inline option - let mut file_options = FILE_OPTIONS.to_vec(); - file_options.append(&mut INLINE_OPTIONS.to_vec()); - // and save them to a file - let file = NamedTempFile::new().expect("Could not create temp file"); - write_options_to_file(file.as_file(), &file_options); - - // pass the name of the file where options are, as an inline option "@filename" - let file_option = format!("@{}", file.path().to_str().unwrap()); - // start with the set of inline options - let mut inline_options = INLINE_OPTIONS.to_vec(); - // and extend with the "@filename" option - inline_options.push(&file_option); - - // confirm that this works and the resulting set of options is correct - let args = super::parse(|| inline_options.iter()).unwrap(); - inline_and_file_options_assertions(&args); + fn test_detect_format_default_no_flags() { + let args = to_strings(&["-o", "out", "foo.o"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::host()); + assert_eq!(result.arch, None); } #[test] - fn test_parse_recursive_file_option() { - // Create a temporary file containing a @file option - let file1 = NamedTempFile::new().expect("Could not create temp file"); - let file2 = NamedTempFile::new().expect("Could not create temp file"); - let file_option = format!("@{}", file2.path().to_str().unwrap()); - write_options_to_file(file1.as_file(), &[&file_option]); - write_options_to_file(file2.as_file(), INPUT1); + fn test_detect_format_riscv_triple() { + let args = to_strings(&["--target=riscv64gc-unknown-linux-gnu", "-o", "out"]); + let result = detect_target(&args).unwrap(); + assert_eq!(result.format, BinaryFormat::Elf); + assert_eq!(result.arch, Some(Architecture::RISCV64)); + } - // pass the name of the file where options are, as an inline option "@filename" - let inline_options = [format!("@{}", file1.path().to_str().unwrap())]; + // ---- filter_and_inject_target_flags tests ---- - // confirm that this works and the resulting set of options is correct - let args = super::parse(|| inline_options.iter()) - .expect("Recursive @file options should parse correctly but be ignored"); - input1_assertions(&args); + #[test] + fn test_filter_strips_target_equals() { + let args = to_strings(&["--target=x86_64-unknown-linux-gnu", "-o", "out", "foo.o"]); + let filtered = + filter_and_inject_target_flags(&args, BinaryFormat::Elf, Some(Architecture::X86_64)); + assert_eq!(filtered[0], "-m"); + assert_eq!(filtered[1], "elf_x86_64"); + assert_eq!(filtered[2], "-o"); + assert!(!filtered.iter().any(|a| a.contains("--target"))); } #[test] - fn test_arguments_from_string() { - use super::arguments_from_string; - - assert!(arguments_from_string("").unwrap().is_empty()); - assert!(arguments_from_string("''").unwrap().is_empty()); - assert!(arguments_from_string("\"\"").unwrap().is_empty()); - assert_eq!( - arguments_from_string(r#""foo" "bar""#).unwrap(), - ["foo", "bar"] - ); - assert_eq!( - arguments_from_string(r#""foo\"" "\"b\"ar""#).unwrap(), - ["foo\"", "\"b\"ar"] - ); - assert_eq!( - arguments_from_string(" foo bar ").unwrap(), - ["foo", "bar"] - ); - assert!(arguments_from_string("'foo''bar'").is_err()); - assert_eq!( - arguments_from_string("'foo' 'bar' baz").unwrap(), - ["foo", "bar", "baz"] - ); - assert_eq!(arguments_from_string("foo\nbar").unwrap(), ["foo", "bar"]); - assert_eq!( - arguments_from_string(r#"'foo' "bar" baz"#).unwrap(), - ["foo", "bar", "baz"] - ); - assert_eq!(arguments_from_string("'foo bar'").unwrap(), ["foo bar"]); - assert_eq!( - arguments_from_string("'foo \" bar'").unwrap(), - ["foo \" bar"] + fn test_filter_strips_target_space() { + let args = to_strings(&["--target", "aarch64-unknown-linux-gnu", "-o", "out"]); + let filtered = + filter_and_inject_target_flags(&args, BinaryFormat::Elf, Some(Architecture::AArch64)); + assert_eq!(filtered[0], "-m"); + assert_eq!(filtered[1], "aarch64linux"); + assert!( + !filtered + .iter() + .any(|a| a == "--target" || a.contains("linux-gnu")) ); - assert!(arguments_from_string("foo\\").is_err()); - assert!(arguments_from_string("'foo").is_err()); - assert!(arguments_from_string("foo\"").is_err()); } #[test] - fn test_ignored_flags() { - for flag in SILENTLY_IGNORED_FLAGS { - assert!(!flag.starts_with('-')); - } + fn test_filter_strips_slash_target() { + let args = to_strings(&["/TARGET:x86_64-pc-windows-msvc", "/OUT:foo.exe", "bar.obj"]); + let filtered = + filter_and_inject_target_flags(&args, BinaryFormat::Coff, Some(Architecture::X86_64)); + assert_eq!(filtered[0], "/MACHINE:X64"); + assert_eq!(filtered[1], "/OUT:foo.exe"); + } + + #[test] + fn test_filter_preserves_m_flag() { + let args = to_strings(&[ + "--target=x86_64-unknown-linux-gnu", + "-m", + "aarch64linux", + "-o", + "out", + ]); + let filtered = + filter_and_inject_target_flags(&args, BinaryFormat::Elf, Some(Architecture::X86_64)); + assert_eq!(filtered[0], "-m"); + assert_eq!(filtered[1], "elf_x86_64"); + assert!(filtered.contains(&"-m".to_string())); + assert!(filtered.contains(&"aarch64linux".to_string())); + } + + #[test] + fn test_filter_no_target_no_inject() { + let args = to_strings(&["-o", "out", "foo.o"]); + let filtered = filter_and_inject_target_flags(&args, BinaryFormat::Elf, None); + assert_eq!(filtered, args); } } diff --git a/libwild/src/args/consts.rs b/libwild/src/args/consts.rs new file mode 100644 index 000000000..7a6363a4b --- /dev/null +++ b/libwild/src/args/consts.rs @@ -0,0 +1,64 @@ +pub const WILD_UNSUPPORTED_ENV: &str = "WILD_UNSUPPORTED"; +pub const VALIDATE_ENV: &str = "WILD_VALIDATE_OUTPUT"; +pub const WRITE_LAYOUT_ENV: &str = "WILD_WRITE_LAYOUT"; +pub const WRITE_TRACE_ENV: &str = "WILD_WRITE_TRACE"; +pub const REFERENCE_LINKER_ENV: &str = "WILD_REFERENCE_LINKER"; +pub(crate) const FILES_PER_GROUP_ENV: &str = "WILD_FILES_PER_GROUP"; + +/// Set this environment variable if you get a failure during writing due to too much or too little +/// space being allocated to some section. When set, each time we allocate during layout, we'll +/// check that what we're doing is consistent with writing and fail in a more easy to debug way. i.e +/// we'll report the particular combination of value flags, resolution flags etc that triggered the +/// inconsistency. +pub(crate) const WRITE_VERIFY_ALLOCATIONS_ENV: &str = "WILD_VERIFY_ALLOCATIONS"; + +// These flags don't currently affect our behaviour. TODO: Assess whether we should error or warn if +// these are given. This is tricky though. On the one hand we want to be a drop-in replacement for +// other linkers. On the other, we should perhaps somehow let the user know that we don't support a +// feature. +pub(super) const SILENTLY_IGNORED_FLAGS: &[&str] = &[ + // Just like other modern linkers, we don't need groups in order to resolve cycles. + "start-group", + "end-group", + // TODO: This is supposed to suppress built-in search paths, but I don't think we have any + // built-in search paths. Perhaps we should? + "nostdlib", + // TODO + "no-undefined-version", + "fatal-warnings", + "color-diagnostics", + "undefined-version", + "sort-common", + "stats", +]; +pub(super) const SILENTLY_IGNORED_SHORT_FLAGS: &[&str] = &[ + "(", + ")", + // On Illumos, the Clang driver inserts a meaningless -C flag before calling any non-GNU ld + // linker. + #[cfg(target_os = "illumos")] + "C", +]; + +pub(super) const IGNORED_FLAGS: &[&str] = &[ + "gdb-index", + "fix-cortex-a53-835769", + "fix-cortex-a53-843419", + "discard-all", + "use-android-relr-tags", + "x", // alias for --discard-all +]; + +// These flags map to the default behavior of the linker. +pub(super) const DEFAULT_FLAGS: &[&str] = &[ + "no-call-graph-profile-sort", + "no-copy-dt-needed-entries", + "no-add-needed", + "discard-locals", + "no-fatal-warnings", + "no-use-android-relr-tags", +]; +pub(super) const DEFAULT_SHORT_FLAGS: &[&str] = &[ + "X", // alias for --discard-locals + "EL", // little endian +]; diff --git a/libwild/src/args/linux.rs b/libwild/src/args/linux.rs new file mode 100644 index 000000000..66953dcf3 --- /dev/null +++ b/libwild/src/args/linux.rs @@ -0,0 +1,1852 @@ +//! A handwritten parser for our arguments. +//! +//! We don't currently use a 3rd party library like clap for a few reasons. Firstly, we need to +//! support flags like `--push-state` and `--pop-state`. These need to push and pop a state stack +//! when they're parsed. Some of the other flags then need to manipulate the state of the top of the +//! stack. Positional arguments like input files and libraries to link, then need to have the +//! current state of the stack attached to that file. +//! +//! Secondly, long arguments need to also be accepted with a single '-' in addition to the more +//! common double-dash. +//! +//! Basically, we need to be able to parse arguments in the same way as the other linkers on the +//! platform that we're targeting. + +use super::ArgumentParser; +use super::BSymbolicKind; +use super::CopyRelocations; +use super::CopyRelocationsDisabledReason; +use super::CounterKind; +use super::DefsymValue; +use super::ExcludeLibs; +use super::FileWriteMode; +use super::Input; +use super::InputSpec; +use super::Modifiers; +use super::RelocationModel; +use super::Strip; +use super::UnresolvedSymbols; +use super::VersionMode; +use super::warn_unsupported; +use crate::alignment::Alignment; +use hashbrown::HashSet; +use crate::arch::Architecture; +use crate::bail; +use crate::ensure; +use crate::error::Context as _; +use crate::error::Result; +use crate::linker_script::maybe_forced_sysroot; +use crate::save_dir::SaveDir; +use crate::timing_phase; +use indexmap::IndexSet; +use itertools::Itertools; +use jobserver::Client; +use object::elf::GNU_PROPERTY_X86_ISA_1_BASELINE; +use object::elf::GNU_PROPERTY_X86_ISA_1_V2; +use object::elf::GNU_PROPERTY_X86_ISA_1_V3; +use object::elf::GNU_PROPERTY_X86_ISA_1_V4; +use std::ffi::CString; +use std::mem::take; +use std::num::NonZero; +use std::num::NonZeroU32; +use std::num::NonZeroU64; +use std::num::NonZeroUsize; +use std::path::Path; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::atomic::AtomicI64; + + +/// ELF-specific linker arguments. Common fields (output, arch, strip, gc_sections, etc.) +/// live on `Args`. Access them via direct field access on `Args`, +/// and ELF-specific fields are accessible via `Deref`/`DerefMut`. +#[derive(Debug)] +pub struct ElfArgs { + pub(crate) should_write_eh_frame_hdr: bool, + pub(crate) rpath: Option, + pub(crate) soname: Option, + pub(crate) enable_new_dtags: bool, + pub(crate) build_id: BuildIdOption, + pub(crate) needs_origin_handling: bool, + pub(crate) needs_nodelete_handling: bool, + pub(crate) relro: bool, + pub(crate) auxiliary: Vec, + pub(crate) got_plt_syms: bool, + pub(crate) hash_style: HashStyle, + pub(crate) z_interpose: bool, + pub(crate) z_isa: Option, + pub(crate) z_stack_size: Option, + pub(crate) plugin_path: Option, + pub(crate) plugin_args: Vec, + rpath_set: IndexSet, +} + + +#[derive(Debug)] +pub(crate) enum BuildIdOption { + None, + Fast, + Hex(Vec), + Uuid, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum HashStyle { + Gnu, + Sysv, + Both, +} + + +impl HashStyle { + pub(crate) const fn includes_gnu(self) -> bool { + matches!(self, HashStyle::Gnu | HashStyle::Both) + } + + pub(crate) const fn includes_sysv(self) -> bool { + matches!(self, HashStyle::Sysv | HashStyle::Both) + } +} + + +use super::consts::*; + +impl Default for ElfArgs { + fn default() -> Self { + ElfArgs { + should_write_eh_frame_hdr: false, + rpath: None, + soname: None, + enable_new_dtags: true, + build_id: BuildIdOption::None, + needs_origin_handling: false, + needs_nodelete_handling: false, + relro: true, + auxiliary: Vec::new(), + got_plt_syms: false, + hash_style: HashStyle::Both, + z_interpose: false, + z_isa: None, + z_stack_size: None, + plugin_path: None, + plugin_args: Vec::new(), + rpath_set: Default::default(), + } + } +} + +// Parse the supplied input arguments, which should not include the program name. +pub(crate) fn parse I, S: AsRef, I: Iterator>( + input: F, +) -> Result> { + use crate::input_data::MAX_FILES_PER_GROUP; + + // SAFETY: Should be called early before other descriptors are opened and + // so we open it before the arguments are parsed (can open a file). + let jobserver_client = unsafe { Client::from_env() }; + + let files_per_group = std::env::var(FILES_PER_GROUP_ENV) + .ok() + .map(|s| s.parse()) + .transpose()?; + + if let Some(x) = files_per_group { + ensure!( + x <= MAX_FILES_PER_GROUP, + "{FILES_PER_GROUP_ENV}={x} but maximum is {MAX_FILES_PER_GROUP}" + ); + } + + let mut args = super::Args:: { + files_per_group, + jobserver_client, + ..Default::default() + }; + + args.save_dir = SaveDir::new(&input)?; + + let mut input = input(); + + let mut modifier_stack = vec![Modifiers::default()]; + + if std::env::var(REFERENCE_LINKER_ENV).is_ok() { + args.write_layout = true; + args.write_trace = true; + } + + let arg_parser = setup_argument_parser(); + while let Some(arg) = input.next() { + let arg = arg.as_ref(); + + arg_parser.handle_argument(&mut args, &mut modifier_stack, arg, &mut input)?; + } + + // Copy relocations are only permitted when building executables. + if !args.should_output_executable { + args.copy_relocations = + CopyRelocations::Disallowed(CopyRelocationsDisabledReason::SharedObject); + } + + if !args.rpath_set.is_empty() { + args.rpath = Some(take(&mut args.rpath_set).into_iter().join(":")); + } + + if !args.unrecognized_options.is_empty() { + let options_list = args.unrecognized_options.join(", "); + bail!("unrecognized option(s): {}", options_list); + } + + if !args.auxiliary.is_empty() && args.should_output_executable { + bail!("-f may not be used without -shared"); + } + + Ok(args) +} + +impl super::Args { + pub fn parse_elf I, S: AsRef, I: Iterator>( + input: F, + ) -> Result> { + timing_phase!("Parse args"); + parse(input) + } +} + +impl super::Args { + /// Adds a linker script to our outputs. Note, this is only called for scripts specified via + /// flags like -T. Where a linker script is just listed as an argument, this won't be called. + fn add_script(&mut self, path: &str) { + self.inputs.push(Input { + spec: InputSpec::File(Box::from(Path::new(path))), + search_first: None, + modifiers: Modifiers::default(), + }); + } +} + +fn parse_number(s: &str) -> Result { + crate::parsing::parse_number(s).map_err(|_| crate::error!("Invalid number: {}", s)) +} + +fn parse_defsym_expression(s: &str) -> DefsymValue { + use crate::parsing::ParsedSymbolExpression; + use crate::parsing::parse_symbol_expression; + + match parse_symbol_expression(s) { + ParsedSymbolExpression::Absolute(value) => DefsymValue::Value(value), + ParsedSymbolExpression::SymbolWithOffset(sym, offset) => { + DefsymValue::SymbolWithOffset(sym.to_owned(), offset) + } + } +} + + +fn setup_argument_parser() -> ArgumentParser { + let mut parser = ArgumentParser::new(); + + parser + .declare_with_param() + .prefix("L") + .help("Add directory to library search path") + .execute(|args: &mut super::Args, _modifier_stack, value| { + let handle_sysroot = |path| { + args.sysroot + .as_ref() + .and_then(|sysroot| maybe_forced_sysroot(path, sysroot)) + .unwrap_or_else(|| Box::from(path)) + }; + + let dir = handle_sysroot(Path::new(value)); + args.save_dir.handle_file(value); + args.lib_search_path.push(dir); + Ok(()) + }); + + parser + .declare_with_param() + .prefix("l") + .help("Link with library") + .sub_option_with_value( + ":filename", + "Link with specific file", + |args, modifier_stack, value| { + let stripped = value.strip_prefix(':').unwrap_or(value); + let spec = InputSpec::File(Box::from(Path::new(stripped))); + args.inputs.push(Input { + spec, + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + Ok(()) + }, + ) + .sub_option_with_value( + "libname", + "Link with library libname.so or libname.a", + |args, modifier_stack, value| { + let spec = InputSpec::Lib(Box::from(value)); + args.inputs.push(Input { + spec, + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + Ok(()) + }, + ) + .execute(|args: &mut super::Args, modifier_stack, value| { + let spec = if let Some(stripped) = value.strip_prefix(':') { + InputSpec::Search(Box::from(stripped)) + } else { + InputSpec::Lib(Box::from(value)) + }; + args.inputs.push(Input { + spec, + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + Ok(()) + }); + + parser + .declare_with_param() + .prefix("u") + .help("Force resolution of the symbol") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.undefined.push(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .prefix("m") + .help("Set target architecture") + .sub_option("elf_x86_64", "x86-64 ELF target", |args, _| { + args.arch = Architecture::X86_64; + Ok(()) + }) + .sub_option( + "elf_x86_64_sol2", + "x86-64 ELF target (Solaris)", + |args, _| { + if args.dynamic_linker.is_none() { + args.dynamic_linker = Some(Path::new("/lib/amd64/ld.so.1").into()); + } + args.arch = Architecture::X86_64; + Ok(()) + }, + ) + .sub_option("aarch64elf", "AArch64 ELF target", |args, _| { + args.arch = Architecture::AArch64; + Ok(()) + }) + .sub_option("aarch64linux", "AArch64 ELF target (Linux)", |args, _| { + args.arch = Architecture::AArch64; + Ok(()) + }) + .sub_option("elf64lriscv", "RISC-V 64-bit ELF target", |args, _| { + args.arch = Architecture::RISCV64; + Ok(()) + }) + .sub_option( + "elf64loongarch", + "LoongArch 64-bit ELF target", + |args, _| { + args.arch = Architecture::LoongArch64; + Ok(()) + }, + ) + .execute(|_args: &mut super::Args, _modifier_stack, value| { + bail!("-m {value} is not yet supported"); + }); + + parser + .declare_with_param() + .prefix("z") + .help("Linker option") + .sub_option("now", "Resolve all symbols immediately", |_, _| Ok(())) + .sub_option( + "origin", + "Mark object as requiring immediate $ORIGIN", + |args, _| { + args.needs_origin_handling = true; + Ok(()) + }, + ) + .sub_option("relro", "Enable RELRO program header", |args, _| { + args.relro = true; + Ok(()) + }) + .sub_option("norelro", "Disable RELRO program header", |args, _| { + args.relro = false; + Ok(()) + }) + .sub_option("notext", "Do not report DT_TEXTREL as an error", |_, _| { + Ok(()) + }) + .sub_option("nostart-stop-gc", "Disable start/stop symbol GC", |_, _| { + Ok(()) + }) + .sub_option( + "execstack", + "Mark object as requiring an executable stack", + |args, _| { + args.execstack = true; + Ok(()) + }, + ) + .sub_option( + "noexecstack", + "Mark object as not requiring an executable stack", + |args, _| { + args.execstack = false; + Ok(()) + }, + ) + .sub_option("nocopyreloc", "Disable copy relocations", |args, _| { + args.copy_relocations = + CopyRelocations::Disallowed(CopyRelocationsDisabledReason::Flag); + Ok(()) + }) + .sub_option( + "nodelete", + "Mark shared object as non-deletable", + |args, _| { + args.needs_nodelete_handling = true; + Ok(()) + }, + ) + .sub_option( + "defs", + "Report unresolved symbol references in object files", + |args, _| { + args.no_undefined = true; + Ok(()) + }, + ) + .sub_option( + "undefs", + "Do not report unresolved symbol references in object files", + |args, _| { + args.no_undefined = false; + Ok(()) + }, + ) + .sub_option("muldefs", "Allow multiple definitions", |args, _| { + args.allow_multiple_definitions = true; + Ok(()) + }) + .sub_option("lazy", "Use lazy binding (default)", |_, _| Ok(())) + .sub_option( + "interpose", + "Mark object to interpose all DSOs but executable", + |args, _| { + args.z_interpose = true; + Ok(()) + }, + ) + .sub_option_with_value( + "stack-size=", + "Set size of stack segment", + |args, _, value| { + let size: u64 = parse_number(value)?; + args.z_stack_size = NonZero::new(size); + + Ok(()) + }, + ) + .sub_option( + "x86-64-baseline", + "Mark x86-64-baseline ISA as needed", + |args, _| { + args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_BASELINE); + Ok(()) + }, + ) + .sub_option("x86-64-v2", "Mark x86-64-v2 ISA as needed", |args, _| { + args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V2); + Ok(()) + }) + .sub_option("x86-64-v3", "Mark x86-64-v3 ISA as needed", |args, _| { + args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V3); + Ok(()) + }) + .sub_option("x86-64-v4", "Mark x86-64-v4 ISA as needed", |args, _| { + args.z_isa = NonZero::new(GNU_PROPERTY_X86_ISA_1_V4); + Ok(()) + }) + .sub_option_with_value( + "max-page-size=", + "Set maximum page size for load segments", + |args, _, value| { + let size: u64 = parse_number(value)?; + if !size.is_power_of_two() { + bail!("Invalid alignment {size:#x}"); + } + args.max_page_size = Some(Alignment { + exponent: size.trailing_zeros() as u8, + }); + + Ok(()) + }, + ) + .execute(|_args: &mut super::Args, _modifier_stack, value| { + warn_unsupported(&("-z ".to_owned() + value))?; + Ok(()) + }); + + parser + .declare_with_param() + .prefix("R") + .help("Add runtime library search path") + .execute(|args: &mut super::Args, _modifier_stack, value| { + if Path::new(value).is_file() { + args.unrecognized_options + .push(format!("-R,{value}(filename)")); + } else { + args.rpath_set.insert(value.to_string()); + } + Ok(()) + }); + + parser + .declare_with_param() + .prefix("O") + .execute(|_args: &mut super::Args, _modifier_stack, _value| + // We don't use opt-level for now. + Ok(())); + + parser + .declare() + .long("static") + .long("Bstatic") + .help("Disallow linking of shared libraries") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().allow_shared = false; + Ok(()) + }); + + parser + .declare() + .long("Bdynamic") + .help("Allow linking of shared libraries") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().allow_shared = true; + Ok(()) + }); + + parser + .declare_with_param() + .long("output") + .short("o") + .help("Set the output filename") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.output = Arc::from(Path::new(value)); + Ok(()) + }); + + parser + .declare() + .long("strip-all") + .short("s") + .help("Strip all symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.strip = Strip::All; + Ok(()) + }); + + parser + .declare() + .long("strip-debug") + .short("S") + .help("Strip debug symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.strip = Strip::Debug; + Ok(()) + }); + + parser + .declare() + .long("gc-sections") + .help("Enable removal of unused sections") + .execute(|args: &mut super::Args, _modifier_stack| { + args.gc_sections = true; + Ok(()) + }); + + parser + .declare() + .long("no-gc-sections") + .help("Disable removal of unused sections") + .execute(|args: &mut super::Args, _modifier_stack| { + args.gc_sections = false; + Ok(()) + }); + + parser + .declare() + .long("shared") + .long("Bshareable") + .help("Create a shared library") + .execute(|args: &mut super::Args, _modifier_stack| { + args.should_output_executable = false; + Ok(()) + }); + + parser + .declare() + .long("pie") + .long("pic-executable") + .help("Create a position-independent executable") + .execute(|args: &mut super::Args, _modifier_stack| { + args.relocation_model = RelocationModel::Relocatable; + args.should_output_executable = true; + Ok(()) + }); + + parser + .declare() + .long("no-pie") + .help("Do not create a position-dependent executable (default)") + .execute(|args: &mut super::Args, _modifier_stack| { + args.relocation_model = RelocationModel::NonRelocatable; + args.should_output_executable = true; + Ok(()) + }); + + parser + .declare_with_param() + .long("pack-dyn-relocs") + .help("Specify dynamic relocation packing format") + .execute(|_args: &mut super::Args, _modifier_stack, value| { + if value != "none" { + warn_unsupported(&format!("--pack-dyn-relocs={value}"))?; + } + Ok(()) + }); + + parser + .declare() + .long("help") + .help("Show this help message") + .execute(|_args: &mut super::Args, _modifier_stack| { + use std::io::Write as _; + let parser = setup_argument_parser(); + let mut stdout = std::io::stdout().lock(); + writeln!(stdout, "{}", parser.generate_help())?; + + // The following listing is something autoconf detection relies on. + writeln!(stdout, "wild: supported targets:elf64 -x86-64 elf64-littleaarch64 elf64-littleriscv elf64-loongarch")?; + writeln!(stdout, "wild: supported emulations: elf_x86_64 aarch64elf elf64lriscv elf64loongarch")?; + + std::process::exit(0); + }); + + parser + .declare() + .long("version") + .help("Show version information and exit") + .execute(|args: &mut super::Args, _modifier_stack| { + args.version_mode = VersionMode::ExitAfterPrint; + Ok(()) + }); + + parser + .declare() + .short("v") + .help("Print version and continue linking") + .execute(|args: &mut super::Args, _modifier_stack| { + args.version_mode = VersionMode::Verbose; + Ok(()) + }); + + parser + .declare() + .long("demangle") + .help("Enable symbol demangling") + .execute(|args: &mut super::Args, _modifier_stack| { + args.demangle = true; + Ok(()) + }); + + parser + .declare() + .long("no-demangle") + .help("Disable symbol demangling") + .execute(|args: &mut super::Args, _modifier_stack| { + args.demangle = false; + Ok(()) + }); + + parser + .declare_with_optional_param() + .long("time") + .help("Show timing information") + .execute(|args: &mut super::Args, _modifier_stack, value| { + match value { + Some(v) => args.time_phase_options = Some(parse_time_phase_options(v)?), + None => args.time_phase_options = Some(Vec::new()), + } + Ok(()) + }); + + parser + .declare_with_param() + .long("dynamic-linker") + .help("Set dynamic linker path") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.dynamic_linker = Some(Box::from(Path::new(value))); + Ok(()) + }); + + parser + .declare() + .long("no-dynamic-linker") + .help("Omit the load-time dynamic linker request") + .execute(|args: &mut super::Args, _modifier_stack| { + args.dynamic_linker = None; + Ok(()) + }); + + parser + .declare() + .long("mmap-output-file") + .help("Write output file using mmap (default)") + .execute(|args: &mut super::Args, _modifier_stack| { + args.mmap_output_file = true; + Ok(()) + }); + + parser + .declare() + .long("no-mmap-output-file") + .help("Write output file without mmap") + .execute(|args: &mut super::Args, _modifier_stack| { + args.mmap_output_file = false; + Ok(()) + }); + + parser + .declare_with_param() + .long("entry") + .short("e") + .help("Set the entry point") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.entry = Some(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_optional_param() + .long("threads") + .help("Use multiple threads for linking") + .execute(|args: &mut super::Args, _modifier_stack, value| { + match value { + Some(v) => { + args.num_threads = Some(NonZeroUsize::try_from(v.parse::()?)?); + } + None => { + args.num_threads = None; // Default behaviour + } + } + Ok(()) + }); + + parser + .declare() + .long("no-threads") + .help("Use a single thread") + .execute(|args: &mut super::Args, _modifier_stack| { + args.num_threads = Some(NonZeroUsize::new(1).unwrap()); + Ok(()) + }); + + parser + .declare_with_param() + .long("wild-experiments") + .help("List of numbers. Used to tweak internal parameters. '_' keeps default value.") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.numeric_experiments = value + .split(',') + .map(|p| { + if p == "_" { + Ok(None) + } else { + Ok(Some(p.parse()?)) + } + }) + .collect::>>>()?; + Ok(()) + }); + + parser + .declare() + .long("as-needed") + .help("Set DT_NEEDED if used") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().as_needed = true; + Ok(()) + }); + + parser + .declare() + .long("no-as-needed") + .help("Always set DT_NEEDED") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().as_needed = false; + Ok(()) + }); + + parser + .declare() + .long("whole-archive") + .help("Include all objects from archives") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().whole_archive = true; + Ok(()) + }); + + parser + .declare() + .long("no-whole-archive") + .help("Disable --whole-archive") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().whole_archive = false; + Ok(()) + }); + + parser + .declare() + .long("push-state") + .help("Save current linker flags") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.push(*modifier_stack.last().unwrap()); + Ok(()) + }); + + parser + .declare() + .long("pop-state") + .help("Restore previous linker flags") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.pop(); + if modifier_stack.is_empty() { + bail!("Mismatched --pop-state"); + } + Ok(()) + }); + + parser + .declare() + .long("eh-frame-hdr") + .help("Create .eh_frame_hdr section") + .execute(|args: &mut super::Args, _modifier_stack| { + args.should_write_eh_frame_hdr = true; + Ok(()) + }); + + parser + .declare() + .long("no-eh-frame-hdr") + .help("Don't create .eh_frame_hdr section") + .execute(|args: &mut super::Args, _modifier_stack| { + args.should_write_eh_frame_hdr = false; + Ok(()) + }); + + parser + .declare() + .long("export-dynamic") + .short("E") + .help("Export all dynamic symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.export_all_dynamic_symbols = true; + Ok(()) + }); + + parser + .declare() + .long("no-export-dynamic") + .help("Do not export dynamic symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.export_all_dynamic_symbols = false; + Ok(()) + }); + + parser + .declare_with_param() + .long("soname") + .prefix("h") + .help("Set shared object name") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.soname = Some(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("rpath") + .help("Add directory to runtime library search path") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.rpath_set.insert(value.to_string()); + Ok(()) + }); + + parser + .declare() + .long("no-string-merge") + .help("Disable section merging") + .execute(|args: &mut super::Args, _modifier_stack| { + args.merge_sections = false; + Ok(()) + }); + + parser + .declare() + .long("no-undefined") + .help("Do not allow unresolved symbols in object files") + .execute(|args: &mut super::Args, _modifier_stack| { + args.no_undefined = true; + Ok(()) + }); + + parser + .declare() + .long("allow-multiple-definition") + .help("Allow multiple definitions of symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.allow_multiple_definitions = true; + Ok(()) + }); + + parser + .declare() + .long("relax") + .help("Enable target-specific optimization (instruction relaxation)") + .execute(|args: &mut super::Args, _modifier_stack| { + args.relax = true; + Ok(()) + }); + + parser + .declare() + .long("no-relax") + .help("Disable relaxation") + .execute(|args: &mut super::Args, _modifier_stack| { + args.relax = false; + Ok(()) + }); + + parser + .declare() + .long("validate-output") + .execute(|args: &mut super::Args, _modifier_stack| { + args.validate_output = true; + Ok(()) + }); + + parser + .declare() + .long("write-layout") + .execute(|args: &mut super::Args, _modifier_stack| { + args.write_layout = true; + Ok(()) + }); + + parser + .declare() + .long("write-trace") + .execute(|args: &mut super::Args, _modifier_stack| { + args.write_trace = true; + Ok(()) + }); + + parser + .declare() + .long("got-plt-syms") + .help("Write symbol table entries that point to the GOT/PLT entry for symbols") + .execute(|args: &mut super::Args, _modifier_stack| { + args.got_plt_syms = true; + Ok(()) + }); + + parser + .declare() + .long("Bsymbolic") + .help("Bind global references locally") + .execute(|args: &mut super::Args, _modifier_stack| { + args.b_symbolic = BSymbolicKind::All; + Ok(()) + }); + + parser + .declare() + .long("Bsymbolic-functions") + .help("Bind global function references locally") + .execute(|args: &mut super::Args, _modifier_stack| { + args.b_symbolic = BSymbolicKind::Functions; + Ok(()) + }); + + parser + .declare() + .long("Bsymbolic-non-weak-functions") + .help("Bind non-weak global function references locally") + .execute(|args: &mut super::Args, _modifier_stack| { + args.b_symbolic = BSymbolicKind::NonWeakFunctions; + Ok(()) + }); + + parser + .declare() + .long("Bsymbolic-non-weak") + .help("Bind non-weak global references locally") + .execute(|args: &mut super::Args, _modifier_stack| { + args.b_symbolic = BSymbolicKind::NonWeak; + Ok(()) + }); + + parser + .declare() + .long("Bno-symbolic") + .help("Do not bind global symbol references locally") + .execute(|args: &mut super::Args, _modifier_stack| { + args.b_symbolic = BSymbolicKind::None; + Ok(()) + }); + + parser + .declare_with_param() + .long("thread-count") + .help("Set the number of threads to use") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.num_threads = Some(NonZeroUsize::try_from(value.parse::()?)?); + Ok(()) + }); + + parser + .declare_with_param() + .long("exclude-libs") + .help("Exclude libraries") + .execute(|args: &mut super::Args, _modifier_stack, value| { + for lib in value.split([',', ':']) { + if lib.is_empty() { + continue; + } + + if lib == "ALL" { + args.exclude_libs = ExcludeLibs::All; + return Ok(()); + } + + match &mut args.exclude_libs { + ExcludeLibs::All => {} + ExcludeLibs::None => { + let mut set = HashSet::new(); + set.insert(Box::from(lib)); + args.exclude_libs = ExcludeLibs::Some(set); + } + ExcludeLibs::Some(set) => { + set.insert(Box::from(lib)); + } + } + } + + Ok(()) + }); + + parser + .declare_with_param() + .long("version-script") + .help("Use version script") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.save_dir.handle_file(value); + args.version_script_path = Some(PathBuf::from(value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("script") + .prefix("T") + .help("Use linker script") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.save_dir.handle_file(value); + args.add_script(value); + Ok(()) + }); + + parser + .declare_with_param() + .long("export-dynamic-symbol") + .help("Export dynamic symbol") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.export_list.push(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("export-dynamic-symbol-list") + .help("Export dynamic symbol list") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.export_list_path = Some(PathBuf::from(value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("dynamic-list") + .help("Read the dynamic symbol list from a file") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.b_symbolic = BSymbolicKind::All; + args.export_list_path = Some(PathBuf::from(value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("write-gc-stats") + .help("Write GC statistics") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.write_gc_stats = Some(PathBuf::from(value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("gc-stats-ignore") + .help("Ignore files in GC stats") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.gc_stats_ignore.push(value.to_owned()); + Ok(()) + }); + + parser + .declare() + .long("no-identity-comment") + .help("Don't write the linker name and version in .comment") + .execute(|args: &mut super::Args, _modifier_stack| { + args.should_write_linker_identity = false; + Ok(()) + }); + + parser + .declare_with_param() + .long("debug-address") + .help("Set debug address") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.debug_address = Some(parse_number(value).context("Invalid --debug-address")?); + Ok(()) + }); + + parser + .declare_with_param() + .long("debug-fuel") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.debug_fuel = Some(AtomicI64::new(value.parse()?)); + args.num_threads = Some(NonZeroUsize::new(1).unwrap()); + Ok(()) + }); + + parser + .declare_with_param() + .long("unresolved-symbols") + .help("Specify how to handle unresolved symbols") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.unresolved_symbols = match value { + "report-all" => UnresolvedSymbols::ReportAll, + "ignore-in-shared-libs" => UnresolvedSymbols::IgnoreInSharedLibs, + "ignore-in-object-files" => UnresolvedSymbols::IgnoreInObjectFiles, + "ignore-all" => UnresolvedSymbols::IgnoreAll, + _ => bail!("Invalid unresolved-symbols value {value}"), + }; + Ok(()) + }); + + parser + .declare_with_param() + .long("undefined") + .help("Force resolution of the symbol") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.undefined.push(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("wrap") + .help("Use a wrapper function") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.wrap.push(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("defsym") + .help("Define a symbol alias: --defsym=symbol=value") + .execute(|args: &mut super::Args, _modifier_stack, value| { + let parts: Vec<&str> = value.splitn(2, '=').collect(); + if parts.len() != 2 { + bail!("Invalid --defsym format. Expected: --defsym=symbol=value"); + } + let symbol_name = parts[0].to_owned(); + let value_str = parts[1]; + + let defsym_value = parse_defsym_expression(value_str); + + args.defsym.push((symbol_name, defsym_value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("section-start") + .help("Set start address for a section: --section-start=.section=address") + .execute(|args: &mut super::Args, _modifier_stack, value| { + let parts: Vec<&str> = value.splitn(2, '=').collect(); + if parts.len() != 2 { + bail!("Invalid --section-start format. Expected: --section-start=.section=address"); + } + + let section_name = parts[0].to_owned(); + let address = parse_number(parts[1]).with_context(|| { + format!( + "Invalid address `{}` in --section-start={}", + parts[1], value + ) + })?; + args.section_start.insert(section_name, address); + + Ok(()) + }); + + parser + .declare_with_param() + .long("hash-style") + .help("Set hash style") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.hash_style = match value { + "gnu" => HashStyle::Gnu, + "sysv" => HashStyle::Sysv, + "both" => HashStyle::Both, + _ => bail!("Unknown hash-style `{value}`"), + }; + Ok(()) + }); + + parser + .declare() + .long("enable-new-dtags") + .help("Use DT_RUNPATH and DT_FLAGS/DT_FLAGS_1 (default)") + .execute(|args: &mut super::Args, _modifier_stack| { + args.enable_new_dtags = true; + Ok(()) + }); + + parser + .declare() + .long("disable-new-dtags") + .help("Use DT_RPATH and individual dynamic entries instead of DT_FLAGS") + .execute(|args: &mut super::Args, _modifier_stack| { + args.enable_new_dtags = false; + Ok(()) + }); + + parser + .declare_with_param() + .long("retain-symbols-file") + .help( + "Filter symtab to contain only symbols listed in the supplied file. \ + One symbol per line.", + ) + .execute(|args: &mut super::Args, _modifier_stack, value| { + // The performance this flag is not especially optimised. For one, we copy each string + // to the heap. We also do two lookups in the hashset for each symbol. This is a pretty + // obscure flag that we don't expect to be used much, so at this stage, it doesn't seem + // worthwhile to optimise it. + let contents = std::fs::read_to_string(value) + .with_context(|| format!("Failed to read `{value}`"))?; + args.strip = Strip::Retain( + contents + .lines() + .filter_map(|l| { + if l.is_empty() { + None + } else { + Some(l.as_bytes().to_owned()) + } + }) + .collect(), + ); + Ok(()) + }); + + parser + .declare_with_param() + .long("build-id") + .help("Generate build ID") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.build_id = match value { + "none" => BuildIdOption::None, + "fast" | "md5" | "sha1" => BuildIdOption::Fast, + "uuid" => BuildIdOption::Uuid, + s if s.starts_with("0x") || s.starts_with("0X") => { + let hex_string = &s[2..]; + let decoded_bytes = hex::decode(hex_string) + .with_context(|| format!("Invalid Hex Build Id `0x{hex_string}`"))?; + BuildIdOption::Hex(decoded_bytes) + } + s => bail!( + "Invalid build-id value `{s}` valid values are `none`, `fast`, `md5`, `sha1` and `uuid`" + ), + }; + Ok(()) + }); + + parser + .declare_with_param() + .long("icf") + .help("Enable identical code folding (merge duplicate functions)") + .execute(|_args: &mut super::Args, _modifier_stack, value| { + match value { + "none" => {} + other => warn_unsupported(&format!("--icf={other}"))?, + } + Ok(()) + }); + + parser + .declare_with_param() + .long("sysroot") + .help("Set system root") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.save_dir.handle_file(value); + let sysroot = std::fs::canonicalize(value).unwrap_or_else(|_| PathBuf::from(value)); + args.sysroot = Some(Box::from(sysroot.as_path())); + for path in &mut args.lib_search_path { + if let Some(new_path) = maybe_forced_sysroot(path, &sysroot) { + *path = new_path; + } + } + Ok(()) + }); + + parser + .declare_with_param() + .long("auxiliary") + .short("f") + .help("Set DT_AUXILIARY to a given value") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.auxiliary.push(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("plugin-opt") + .help("Pass options to the plugin") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.plugin_args + .push(CString::new(value).context("Invalid --plugin-opt argument")?); + Ok(()) + }); + + parser + .declare_with_param() + .long("dependency-file") + .help("Write dependency rules") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.dependency_file = Some(PathBuf::from(value)); + Ok(()) + }); + + parser + .declare_with_param() + .long("plugin") + .help("Load plugin") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.plugin_path = Some(value.to_owned()); + Ok(()) + }); + + parser + .declare_with_param() + .long("rpath-link") + .help("Add runtime library search path") + .execute(|_args: &mut super::Args, _modifier_stack, _value| { + // TODO + Ok(()) + }); + + parser + .declare_with_param() + .long("sym-info") + .help("Show symbol information. Accepts symbol name or ID.") + .execute(|args: &mut super::Args, _modifier_stack, value| { + args.sym_info = Some(value.to_owned()); + Ok(()) + }); + + parser + .declare() + .long("start-lib") + .help("Start library group") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().archive_semantics = true; + Ok(()) + }); + + parser + .declare() + .long("end-lib") + .help("End library group") + .execute(|_args: &mut super::Args, modifier_stack| { + modifier_stack.last_mut().unwrap().archive_semantics = false; + Ok(()) + }); + + parser + .declare() + .long("no-fork") + .help("Do not fork while linking") + .execute(|args: &mut super::Args, _modifier_stack| { + args.should_fork = false; + Ok(()) + }); + + parser + .declare() + .long("update-in-place") + .help("Update file in place") + .execute(|args: &mut super::Args, _modifier_stack| { + args.file_write_mode = Some(FileWriteMode::UpdateInPlace); + Ok(()) + }); + + parser + .declare() + .long("no-update-in-place") + .help("Delete and recreate the file") + .execute(|args: &mut super::Args, _modifier_stack| { + args.file_write_mode = Some(FileWriteMode::UnlinkAndReplace); + Ok(()) + }); + + parser + .declare() + .long("EB") + .help("Big-endian (not supported)") + .execute(|_args: &mut super::Args, _modifier_stack| { + bail!("Big-endian target is not supported"); + }); + + parser + .declare() + .long("prepopulate-maps") + .help("Prepopulate maps") + .execute(|args: &mut super::Args, _modifier_stack| { + args.prepopulate_maps = true; + Ok(()) + }); + + parser + .declare() + .long("verbose-gc-stats") + .help("Show GC statistics") + .execute(|args: &mut super::Args, _modifier_stack| { + args.verbose_gc_stats = true; + Ok(()) + }); + + parser + .declare() + .long("allow-shlib-undefined") + .help("Allow undefined symbol references in shared libraries") + .execute(|args: &mut super::Args, _modifier_stack| { + args.allow_shlib_undefined = true; + Ok(()) + }); + + parser + .declare() + .long("no-allow-shlib-undefined") + .help("Disallow undefined symbol references in shared libraries") + .execute(|args: &mut super::Args, _modifier_stack| { + args.allow_shlib_undefined = false; + Ok(()) + }); + + parser + .declare() + .long("error-unresolved-symbols") + .help("Treat unresolved symbols as errors") + .execute(|args: &mut super::Args, _modifier_stack| { + args.error_unresolved_symbols = true; + Ok(()) + }); + + parser + .declare() + .long("warn-unresolved-symbols") + .help("Treat unresolved symbols as warnings") + .execute(|args: &mut super::Args, _modifier_stack| { + args.error_unresolved_symbols = false; + Ok(()) + }); + + add_silently_ignored_flags(&mut parser); + add_default_flags(&mut parser); + + parser +} + +fn add_silently_ignored_flags(parser: &mut ArgumentParser) { + super::add_silently_ignored_flags(parser); +} + +fn add_default_flags(parser: &mut ArgumentParser) { + super::add_default_flags(parser); +} + +fn parse_time_phase_options(input: &str) -> Result> { + input.split(',').map(|s| s.parse()).collect() +} + +impl FromStr for CounterKind { + type Err = crate::error::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "cycles" => CounterKind::Cycles, + "instructions" => CounterKind::Instructions, + "cache-misses" => CounterKind::CacheMisses, + "branch-misses" => CounterKind::BranchMisses, + "page-faults" => CounterKind::PageFaults, + "page-faults-minor" => CounterKind::PageFaultsMinor, + "page-faults-major" => CounterKind::PageFaultsMajor, + "l1d-read" => CounterKind::L1dRead, + "l1d-miss" => CounterKind::L1dMiss, + other => bail!("Unsupported performance counter `{other}`"), + }) + } +} + + +#[cfg(test)] +mod tests { + use super::SILENTLY_IGNORED_FLAGS; + use super::VersionMode; + use crate::args::linux::ElfArgs; + use crate::args::InputSpec; + use itertools::Itertools; + use std::fs::File; + use std::io::BufWriter; + use std::io::Write; + use std::num::NonZeroUsize; + use std::path::Path; + use std::path::PathBuf; + use std::str::FromStr; + use tempfile::NamedTempFile; + + const INPUT1: &[&str] = &[ + "-pie", + "-z", + "relro", + "-zrelro", + "-hash-style=gnu", + "--hash-style=gnu", + "-build-id", + "--build-id", + "--eh-frame-hdr", + "-m", + "elf_x86_64", + "-dynamic-linker", + "/lib64/ld-linux-x86-64.so.2", + "-o", + "/build/target/debug/deps/c1-a212b73b12b6d123", + "/lib/x86_64-linux-gnu/Scrt1.o", + "/lib/x86_64-linux-gnu/crti.o", + "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtbeginS.o", + "-L/build/target/debug/deps", + "-L/tool/lib/rustlib/x86_64/lib", + "-L/tool/lib/rustlib/x86_64/lib", + "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12", + "-L/usr/bin/../lib/gcc/x86_64-linux-gnu/12/../../../../lib64", + "-L/lib/x86_64-linux-gnu", + "-L/lib/../lib64", + "-L/usr/lib/x86_64-linux-gnu", + "-L/usr/lib/../lib64", + "-L", + "/lib", + "-L/usr/lib", + "/tmp/rustcDcR20O/symbols.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.1.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.2.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.3.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.4.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.5.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.6.rcgu.o", + "/build/target/debug/deps/c1-a212b73b12b6d123.7.rcgu.o", + "--as-needed", + "-as-needed", + "-Bstatic", + "/tool/lib/rustlib/x86_64/lib/libstd-6498d8891e016dca.rlib", + "/tool/lib/rustlib/x86_64/lib/libpanic_unwind-3debdee1a9058d84.rlib", + "/tool/lib/rustlib/x86_64/lib/libobject-8339c5bd5cbc92bf.rlib", + "/tool/lib/rustlib/x86_64/lib/libmemchr-160ebcebb54c11ba.rlib", + "/tool/lib/rustlib/x86_64/lib/libaddr2line-95c75789f1b65e37.rlib", + "/tool/lib/rustlib/x86_64/lib/libgimli-7e8094f2d6258832.rlib", + "/tool/lib/rustlib/x86_64/lib/librustc_demangle-bac9783ef1b45db0.rlib", + "/tool/lib/rustlib/x86_64/lib/libstd_detect-a1cd87df2f2d8e76.rlib", + "/tool/lib/rustlib/x86_64/lib/libhashbrown-7fd06d468d7dba16.rlib", + "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_alloc-5ac19487656e05bf.rlib", + "/tool/lib/rustlib/x86_64/lib/libminiz_oxide-c7c35d32cf825c11.rlib", + "/tool/lib/rustlib/x86_64/lib/libadler-c523f1571362e70b.rlib", + "/tool/lib/rustlib/x86_64/lib/libunwind-85f17c92b770a911.rlib", + "/tool/lib/rustlib/x86_64/lib/libcfg_if-598d3ba148dadcea.rlib", + "/tool/lib/rustlib/x86_64/lib/liblibc-a58ec2dab545caa4.rlib", + "/tool/lib/rustlib/x86_64/lib/liballoc-f9dda8cca149f0fc.rlib", + "/tool/lib/rustlib/x86_64/lib/librustc_std_workspace_core-7ba4c315dd7a3503.rlib", + "/tool/lib/rustlib/x86_64/lib/libcore-5ac2993e19124966.rlib", + "/tool/lib/rustlib/x86_64/lib/libcompiler_builtins-df2fb7f50dec519a.rlib", + "-Bdynamic", + "-lgcc_s", + "-lutil", + "-lrt", + "-lpthread", + "-lm", + "-ldl", + "-lc", + "--eh-frame-hdr", + "-z", + "noexecstack", + "-znoexecstack", + "--gc-sections", + "-z", + "relro", + "-z", + "now", + "-z", + "lazy", + "-soname=fpp", + "-soname", + "bar", + "/usr/bin/../lib/gcc/x86_64-linux-gnu/12/crtendS.o", + "/lib/x86_64-linux-gnu/crtn.o", + "--version-script", + "a.ver", + "--no-threads", + "--no-add-needed", + "--no-copy-dt-needed-entries", + "--discard-locals", + "--use-android-relr-tags", + "--pack-dyn-relocs=relr", + "-X", + "-EL", + "-O", + "1", + "-O3", + "-v", + "--sysroot=/usr/aarch64-linux-gnu", + "--demangle", + "--no-demangle", + "-l:lib85caec4suo0pxg06jm2ma7b0o.so", + "-rpath", + "foo/", + "-rpath=bar/", + "-Rbaz", + "-R", + "somewhere", + // Adding the same rpath multiple times should not create duplicates + "-rpath", + "foo/", + "-x", + "--discard-all", + "--dependency-file=deps.d", + ]; + + const FILE_OPTIONS: &[&str] = &["-pie"]; + + const INLINE_OPTIONS: &[&str] = &["-L", "/lib"]; + + fn write_options_to_file(file: &File, options: &[&str]) { + let mut writer = BufWriter::new(file); + for option in options { + writeln!(writer, "{option}").expect("Failed to write to temporary file"); + } + } + + #[track_caller] + fn assert_contains(c: &[Box], v: &str) { + assert!(c.iter().any(|p| p.as_ref() == Path::new(v))); + } + + fn input1_assertions(args: &super::super::Args) { + assert_eq!( + args.inputs + .iter() + .filter_map(|i| match &i.spec { + InputSpec::File(_) | InputSpec::Search(_) => None, + InputSpec::Lib(lib_name) => Some(lib_name.as_ref()), + }) + .collect_vec(), + &["gcc_s", "util", "rt", "pthread", "m", "dl", "c"] + ); + assert_contains(&args.lib_search_path, "/lib"); + assert_contains(&args.lib_search_path, "/usr/lib"); + assert!(!args.inputs.iter().any(|i| match &i.spec { + InputSpec::File(f) => f.as_ref() == Path::new("/usr/bin/ld"), + InputSpec::Lib(_) | InputSpec::Search(_) => false, + })); + assert_eq!( + args.version_script_path, + Some(PathBuf::from_str("a.ver").unwrap()) + ); + assert_eq!(args.soname, Some("bar".to_owned())); + assert_eq!(args.num_threads, Some(NonZeroUsize::new(1).unwrap())); + assert_eq!(args.version_mode, VersionMode::Verbose); + assert_eq!( + args.sysroot, + Some(Box::from(Path::new("/usr/aarch64-linux-gnu"))) + ); + assert!(args.inputs.iter().any(|i| match &i.spec { + InputSpec::File(_) | InputSpec::Lib(_) => false, + InputSpec::Search(lib) => lib.as_ref() == "lib85caec4suo0pxg06jm2ma7b0o.so", + })); + assert_eq!(args.rpath.as_deref(), Some("foo/:bar/:baz:somewhere")); + assert_eq!( + args.dependency_file, + Some(PathBuf::from_str("deps.d").unwrap()) + ); + } + + fn inline_and_file_options_assertions(args: &super::super::Args) { + assert_contains(&args.lib_search_path, "/lib"); + } + + #[test] + fn test_parse_inline_only_options() { + let args = super::parse(|| INPUT1.iter()).unwrap(); + input1_assertions(&args); + } + + #[test] + fn test_parse_file_only_options() { + // Create a temporary file containing the same options (one per line) as INPUT1 + let file = NamedTempFile::new().expect("Could not create temp file"); + write_options_to_file(file.as_file(), INPUT1); + + // pass the name of the file where options are as the only inline option "@filename" + let inline_options = [format!("@{}", file.path().to_str().unwrap())]; + let args = super::parse(|| inline_options.iter()).unwrap(); + input1_assertions(&args); + } + + #[test] + fn test_parse_mixed_file_and_inline_options() { + // Create a temporary file containing some options + let file = NamedTempFile::new().expect("Could not create temp file"); + write_options_to_file(file.as_file(), FILE_OPTIONS); + + // create an inline option referring to "@filename" + let file_option = format!("@{}", file.path().to_str().unwrap()); + // start with the set of inline options + let mut inline_options = INLINE_OPTIONS.to_vec(); + // and extend with the "@filename" option + inline_options.push(&file_option); + + // confirm that this works and the resulting set of options is correct + let args = super::parse(|| inline_options.iter()).unwrap(); + inline_and_file_options_assertions(&args); + } + + #[test] + fn test_parse_overlapping_file_and_inline_options() { + // Create a set of file options that has a duplicate of an inline option + let mut file_options = FILE_OPTIONS.to_vec(); + file_options.append(&mut INLINE_OPTIONS.to_vec()); + // and save them to a file + let file = NamedTempFile::new().expect("Could not create temp file"); + write_options_to_file(file.as_file(), &file_options); + + // pass the name of the file where options are, as an inline option "@filename" + let file_option = format!("@{}", file.path().to_str().unwrap()); + // start with the set of inline options + let mut inline_options = INLINE_OPTIONS.to_vec(); + // and extend with the "@filename" option + inline_options.push(&file_option); + + // confirm that this works and the resulting set of options is correct + let args = super::parse(|| inline_options.iter()).unwrap(); + inline_and_file_options_assertions(&args); + } + + #[test] + fn test_parse_recursive_file_option() { + // Create a temporary file containing a @file option + let file1 = NamedTempFile::new().expect("Could not create temp file"); + let file2 = NamedTempFile::new().expect("Could not create temp file"); + let file_option = format!("@{}", file2.path().to_str().unwrap()); + write_options_to_file(file1.as_file(), &[&file_option]); + write_options_to_file(file2.as_file(), INPUT1); + + // pass the name of the file where options are, as an inline option "@filename" + let inline_options = [format!("@{}", file1.path().to_str().unwrap())]; + + // confirm that this works and the resulting set of options is correct + let args = super::parse(|| inline_options.iter()) + .expect("Recursive @file options should parse correctly but be ignored"); + input1_assertions(&args); + } + + #[test] + fn test_arguments_from_string() { + use crate::args::arguments_from_string; + + assert!(arguments_from_string("").unwrap().is_empty()); + assert!(arguments_from_string("''").unwrap().is_empty()); + assert!(arguments_from_string("\"\"").unwrap().is_empty()); + assert_eq!( + arguments_from_string(r#""foo" "bar""#).unwrap(), + ["foo", "bar"] + ); + assert_eq!( + arguments_from_string(r#""foo\"" "\"b\"ar""#).unwrap(), + ["foo\"", "\"b\"ar"] + ); + assert_eq!( + arguments_from_string(" foo bar ").unwrap(), + ["foo", "bar"] + ); + assert!(arguments_from_string("'foo''bar'").is_err()); + assert_eq!( + arguments_from_string("'foo' 'bar' baz").unwrap(), + ["foo", "bar", "baz"] + ); + assert_eq!(arguments_from_string("foo\nbar").unwrap(), ["foo", "bar"]); + assert_eq!( + arguments_from_string(r#"'foo' "bar" baz"#).unwrap(), + ["foo", "bar", "baz"] + ); + assert_eq!(arguments_from_string("'foo bar'").unwrap(), ["foo bar"]); + assert_eq!( + arguments_from_string("'foo \" bar'").unwrap(), + ["foo \" bar"] + ); + #[cfg(not(target_os = "windows"))] + assert!(arguments_from_string("foo\\").is_err()); + #[cfg(target_os = "windows")] + assert_eq!(arguments_from_string("foo\\").unwrap(), ["foo\\"]); + assert!(arguments_from_string("'foo").is_err()); + assert!(arguments_from_string("foo\"").is_err()); + } + + #[test] + fn test_ignored_flags() { + for flag in SILENTLY_IGNORED_FLAGS { + assert!(!flag.starts_with('-')); + } + } +} diff --git a/libwild/src/args/windows.rs b/libwild/src/args/windows.rs new file mode 100644 index 000000000..b7e35ffb5 --- /dev/null +++ b/libwild/src/args/windows.rs @@ -0,0 +1,1502 @@ +use super::ArgumentParser; +use super::Input; +use super::InputSpec; +use super::Modifiers; +use super::add_default_flags; +use super::add_silently_ignored_flags; +use super::consts::FILES_PER_GROUP_ENV; +use super::consts::REFERENCE_LINKER_ENV; +use crate::arch::Architecture; +use crate::bail; +use crate::ensure; +use crate::error::Result; +use crate::save_dir::SaveDir; +use jobserver::Client; +use std::num::NonZeroUsize; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum WindowsSubsystem { + Console, + Windows, + Native, + Posix, + BootApplication, + EfiApplication, + EfiBootServiceDriver, + EfiRom, + EfiRuntimeDriver, +} + +/// PE/COFF-specific linker arguments. Common fields (output, arch, inputs, etc.) +/// live on `Args`. Access them via direct field access on `Args`, +/// and PE-specific fields are accessible via `Deref`/`DerefMut`. +#[derive(Debug)] +pub struct PeArgs { + // Windows-specific fields + pub(crate) base_address: Option, + pub(crate) subsystem: Option, + pub(crate) heap_size: Option, + pub(crate) stack_size: Option, + pub(crate) is_dll: bool, + pub(crate) debug_info: bool, + pub(crate) def_file: Option, + pub(crate) import_lib: Option, + pub(crate) manifest_file: Option, + pub(crate) map_file: Option, + pub(crate) pdb_file: Option, + pub(crate) version: Option, + pub(crate) large_address_aware: bool, + pub(crate) dynamic_base: bool, + pub(crate) nx_compat: bool, + pub(crate) terminal_server_aware: bool, + pub(crate) high_entropy_va: bool, + pub(crate) no_default_libs: Vec, + pub(crate) ignore_all_default_libs: bool, +} + +impl Default for PeArgs { + fn default() -> Self { + Self { + base_address: None, + subsystem: None, + heap_size: None, + stack_size: None, + is_dll: false, + debug_info: false, + def_file: None, + import_lib: None, + manifest_file: None, + map_file: None, + pdb_file: None, + version: None, + large_address_aware: true, + dynamic_base: true, + nx_compat: true, + terminal_server_aware: true, + high_entropy_va: true, + no_default_libs: Vec::new(), + ignore_all_default_libs: false, + } + } +} + +impl super::Args { + /// Check if a specific library should be ignored due to /NODEFAULTLIB + pub fn should_ignore_default_lib(&self, lib_name: &str) -> bool { + self.ignore_all_default_libs || self.no_default_libs.contains(&lib_name.to_string()) + } + + /// Get the list of specifically ignored default libraries + pub fn ignored_default_libs(&self) -> &[String] { + &self.no_default_libs + } + + /// Check if all default libraries should be ignored + pub fn ignores_all_default_libs(&self) -> bool { + self.ignore_all_default_libs + } +} + +/// Parse Windows linker arguments from the given input iterator. +pub(crate) fn parse I, S: AsRef, I: Iterator>( + input: F, +) -> Result> { + use crate::input_data::MAX_FILES_PER_GROUP; + + // SAFETY: Should be called early before other descriptors are opened. + let jobserver_client = unsafe { Client::from_env() }; + + let files_per_group: Option = std::env::var(FILES_PER_GROUP_ENV) + .ok() + .map(|s| s.parse()) + .transpose()?; + + if let Some(x) = files_per_group { + ensure!( + x <= MAX_FILES_PER_GROUP, + "{FILES_PER_GROUP_ENV}={x} but maximum is {MAX_FILES_PER_GROUP}" + ); + } + + let mut args = super::Args:: { + output: Arc::from(Path::new("a.exe")), + should_write_linker_identity: false, + files_per_group, + jobserver_client, + ..Default::default() + }; + + args.save_dir = SaveDir::new(&input)?; + + let mut input = input(); + + let mut modifier_stack = vec![Modifiers::default()]; + + if std::env::var(REFERENCE_LINKER_ENV).is_ok() { + args.write_layout = true; + args.write_trace = true; + } + + let arg_parser = setup_windows_argument_parser(); + while let Some(arg) = input.next() { + let arg = arg.as_ref(); + arg_parser.handle_argument(&mut args, &mut modifier_stack, arg, &mut input)?; + } + + if !args.unrecognized_options.is_empty() { + let options_list = args.unrecognized_options.join(", "); + bail!("unrecognized option(s): {}", options_list); + } + + Ok(args) +} + +pub(crate) fn setup_windows_argument_parser() -> ArgumentParser { + // Helper function for unimplemented options + fn unimplemented_option(option: &str) -> Result<()> { + crate::bail!("Option {} is not yet implemented", option) + } + + let mut parser = ArgumentParser::new_case_insensitive(); + // /ALIGN - Specifies the alignment of each section. + parser + .declare_with_param() + .long("ALIGN") + .help("/ALIGN - Specifies the alignment of each section.") + .execute(|_args: &mut super::Args, _modifier_stack, _value| { + unimplemented_option("/ALIGN") + }); + // /ALLOWBIND - Specifies that a DLL can't be bound. + parser + .declare() + .long("ALLOWBIND") + .help("/ALLOWBIND - Specifies that a DLL can't be bound.") + .execute(|_, _| unimplemented_option("/ALLOWBIND")); + // /ALLOWISOLATION - Specifies behavior for manifest lookup. + parser + .declare() + .long("ALLOWISOLATION") + .help("/ALLOWISOLATION - Specifies behavior for manifest lookup.") + .execute(|_, _| unimplemented_option("/ALLOWISOLATION")); + // /APPCONTAINER - Specifies whether the app must run within an appcontainer process environment. + parser + .declare() + .long("APPCONTAINER") + .help("/APPCONTAINER - Specifies whether the app must run within an appcontainer process environment.") + .execute(|_, _| unimplemented_option("/APPCONTAINER")); + // /ARM64XFUNCTIONPADMINX64 - Specifies the minimum number of bytes of padding between x64 functions in ARM64X images. 17.8 + parser + .declare_with_param() + .long("ARM64XFUNCTIONPADMINX64") + .help("/ARM64XFUNCTIONPADMINX64 - Specifies the minimum number of bytes of padding between x64 functions in ARM64X images. 17.8") + .execute(|_, _, _| unimplemented_option("/ARM64XFUNCTIONPADMINX64")); + // /ASSEMBLYDEBUG - Adds the DebuggableAttribute to a managed image. + parser + .declare() + .long("ASSEMBLYDEBUG") + .help("/ASSEMBLYDEBUG - Adds the DebuggableAttribute to a managed image.") + .execute(|_, _| unimplemented_option("/ASSEMBLYDEBUG")); + // /ASSEMBLYLINKRESOURCE - Creates a link to a managed resource. + parser + .declare_with_param() + .long("ASSEMBLYLINKRESOURCE") + .help("/ASSEMBLYLINKRESOURCE - Creates a link to a managed resource.") + .execute(|_, _, _| unimplemented_option("/ASSEMBLYLINKRESOURCE")); + // /ASSEMBLYMODULE - Specifies that a Microsoft intermediate language (MSIL) module should be imported into the assembly. + parser + .declare_with_param() + .long("ASSEMBLYMODULE") + .help("/ASSEMBLYMODULE - Specifies that a Microsoft intermediate language (MSIL) module should be imported into the assembly.") + .execute(|_, _, _| unimplemented_option("/ASSEMBLYMODULE")); + // /ASSEMBLYRESOURCE - Embeds a managed resource file in an assembly. + parser + .declare_with_param() + .long("ASSEMBLYRESOURCE") + .help("/ASSEMBLYRESOURCE - Embeds a managed resource file in an assembly.") + .execute(|_, _, _| unimplemented_option("/ASSEMBLYRESOURCE")); + // /BASE - Sets a base address for the program. + parser + .declare_with_param() + .long("BASE") + .help("/BASE - Sets a base address for the program.") + .execute(|args, _modifier_stack, value| { + // Parse hexadecimal base address + let base = if value.starts_with("0x") || value.starts_with("0X") { + u64::from_str_radix(&value[2..], 16) + } else { + value.parse::() + }; + + match base { + Ok(addr) => { + args.base_address = Some(addr); + Ok(()) + } + Err(_) => { + crate::bail!("Invalid base address: {}", value); + } + } + }); + // /CETCOMPAT - Marks the binary as CET Shadow Stack compatible. + parser + .declare() + .long("CETCOMPAT") + .help("/CETCOMPAT - Marks the binary as CET Shadow Stack compatible.") + .execute(|_, _| unimplemented_option("/CETCOMPAT")); + // /CGTHREADS - Sets number of cl.exe threads to use for optimization and code generation when link-time code generation is specified. + parser + .declare_with_param() + .long("CGTHREADS") + .help("/CGTHREADS - Sets number of cl.exe threads to use for optimization and code generation when link-time code generation is specified.") + .execute(|args, _modifier_stack, value| { + match value.parse::() { + Ok(threads) => { + if threads > 0 { + args.num_threads = NonZeroUsize::new(threads); + } + Ok(()) + } + Err(_) => { + crate::bail!("Invalid thread count: {}", value); + } + } + }); + // /CLRIMAGETYPE - Sets the type (IJW, pure, or safe) of a CLR image. + parser + .declare_with_param() + .long("CLRIMAGETYPE") + .help("/CLRIMAGETYPE - Sets the type (IJW, pure, or safe) of a CLR image.") + .execute(|_, _, _| unimplemented_option("/CLRIMAGETYPE")); + // /CLRSUPPORTLASTERROR - Preserves the last error code of functions that are called through the P/Invoke mechanism. + parser + .declare() + .long("CLRSUPPORTLASTERROR") + .help("/CLRSUPPORTLASTERROR - Preserves the last error code of functions that are called through the P/Invoke mechanism.") + .execute(|_, _| unimplemented_option("/CLRSUPPORTLASTERROR")); + // /CLRTHREADATTRIBUTE - Specifies the threading attribute to apply to the entry point of your CLR program. + parser + .declare_with_param() + .long("CLRTHREADATTRIBUTE") + .help("/CLRTHREADATTRIBUTE - Specifies the threading attribute to apply to the entry point of your CLR program.") + .execute(|_, _, _| unimplemented_option("/CLRTHREADATTRIBUTE")); + // /CLRUNMANAGEDCODECHECK - Specifies whether the linker applies the SuppressUnmanagedCodeSecurity attribute to linker-generated P/Invoke stubs that call from managed code into native DLLs. + parser + .declare() + .long("CLRUNMANAGEDCODECHECK") + .help("/CLRUNMANAGEDCODECHECK - Specifies whether the linker applies the SuppressUnmanagedCodeSecurity attribute to linker-generated P/Invoke stubs that call from managed code into native DLLs.") + .execute(|_, _| unimplemented_option("/CLRUNMANAGEDCODECHECK")); + // /DEBUG - Creates debugging information. + parser + .declare_with_optional_param() + .long("DEBUG") + .help("/DEBUG - Creates debugging information.") + .sub_option("FULL", "Full debugging information.", |args, _| { + args.debug_info = true; + Ok(()) + }) + .sub_option( + "FASTLINK", + "Produces a PDB with limited debug information.", + |args, _| { + args.debug_info = true; + Ok(()) + }, + ) + .execute(|args, _, _value| { + args.debug_info = true; + Ok(()) + }); + // /DEBUGTYPE - Specifies which data to include in debugging information. + parser + .declare_with_param() + .long("DEBUGTYPE") + .help("/DEBUGTYPE - Specifies which data to include in debugging information.") + .execute(|_, _, _| unimplemented_option("/DEBUGTYPE")); + // /DEF - Passes a module-definition (.def) file to the linker. + parser + .declare_with_param() + .long("DEF") + .help("/DEF - Passes a module-definition (.def) file to the linker.") + .execute(|args, _modifier_stack, value| { + args.def_file = Some(PathBuf::from(value)); + Ok(()) + }); + // /DEFAULTLIB - Searches the specified library when external references are resolved. + parser + .declare_with_optional_param() + .long("DEFAULTLIB") // Add lowercase version for case-insensitive matching + .help("/DEFAULTLIB - Searches the specified library when external references are resolved.") + .execute(|args, _modifier_stack, value| { + if let Some(lib_name) = value { + // Add library to inputs + args.inputs.push(Input { + spec: InputSpec::Lib(lib_name.into()), + search_first: None, + modifiers: Modifiers::default(), + }); + } + Ok(()) + }); + // /DELAY - Controls the delayed loading of DLLs. + parser + .declare_with_optional_param() + .long("DELAY") + .help("/DELAY - Controls the delayed loading of DLLs.") + .execute(|_, _, _| unimplemented_option("/DELAY")); + // /DELAYLOAD - Causes the delayed loading of the specified DLL. + parser + .declare_with_optional_param() + .long("DELAYLOAD") + .help("/DELAYLOAD - Causes the delayed loading of the specified DLL.") + .execute(|_, _, _| unimplemented_option("/DELAYLOAD")); + // /DELAYSIGN - Partially signs an assembly. + parser + .declare_with_optional_param() + .long("DELAYSIGN") + .help("/DELAYSIGN - Partially signs an assembly.") + .execute(|_, _, _| unimplemented_option("/DELAYSIGN")); + // /DEPENDENTLOADFLAG - Sets default flags on dependent DLL loads. + parser + .declare_with_optional_param() + .long("DEPENDENTLOADFLAG") + .help("/DEPENDENTLOADFLAG - Sets default flags on dependent DLL loads.") + .execute(|_, _, _| unimplemented_option("/DEPENDENTLOADFLAG")); + // /DLL - Builds a DLL. + parser + .declare() + .long("DLL") + .help("/DLL - Builds a DLL.") + .execute(|args, _modifier_stack| { + args.is_dll = true; + args.should_output_executable = false; + Ok(()) + }); + // /DRIVER - Creates a kernel mode driver. + parser + .declare_with_param() + .long("DRIVER") + .help("/DRIVER - Creates a kernel mode driver.") + .sub_option("UPONLY", "Runs only on a uniprocessor system.", |_, _| { + unimplemented_option("/DRIVER:UPONLY") + }) + .sub_option("WDM", "Creates a Windows Driver Model driver.", |_, _| { + unimplemented_option("/DRIVER:WDM") + }) + .execute(|_, _, _| unimplemented_option("/DRIVER")); + // /DYNAMICBASE - Specifies whether to generate an executable image that's rebased at load time by using the address space layout randomization (ASLR) feature. + parser + .declare_with_optional_param() + .long("DYNAMICBASE") + .help("/DYNAMICBASE - Specifies whether to generate an executable image that's rebased at load time by using the address space layout randomization (ASLR) feature.") + .execute(|args, _modifier_stack, value| { + match value { + Some("NO") => args.dynamic_base = false, + _ => args.dynamic_base = true, + } + Ok(()) + }); + // /DYNAMICDEOPT - Enable C++ Dynamic Debugging (Preview) and step in anywhere with on-demand function deoptimization. + parser + .declare_with_optional_param() + .long("DYNAMICDEOPT") + .help("/DYNAMICDEOPT - Enable C++ Dynamic Debugging (Preview) and step in anywhere with on-demand function deoptimization.") + .execute(|_, _, _| unimplemented_option("/DYNAMICDEOPT")); + // /ENTRY - Sets the starting address. Also accepts -e (used by clang driver). + parser + .declare_with_param() + .long("ENTRY") + .short("e") + .help("/ENTRY - Sets the starting address.") + .execute(|args, _modifier_stack, value| { + args.entry = Some(value.to_string()); + Ok(()) + }); + // /ERRORREPORT - Deprecated. Error reporting is controlled by Windows Error Reporting (WER) settings. + parser + .declare_with_optional_param() + .long("ERRORREPORT") + .help("/ERRORREPORT - Deprecated. Error reporting is controlled by Windows Error Reporting (WER) settings.") + .execute(|_, _, _| unimplemented_option("/ERRORREPORT")); + // /EXPORT - Exports a function. + parser + .declare_with_param() + .long("EXPORT") + .help("/EXPORT - Exports a function.") + .execute(|_args, _modifier_stack, _value| unimplemented_option("/EXPORT")); + // /FILEALIGN - Aligns sections within the output file on multiples of a specified value. + parser + .declare_with_param() + .long("FILEALIGN") + .help("/FILEALIGN - Aligns sections within the output file on multiples of a specified value.") + .execute(|_args, _modifier_stack, _value| unimplemented_option("/FILEALIGN")); + // /FIXED - Creates a program that can be loaded only at its preferred base address. + parser + .declare_with_optional_param() + .long("FIXED") + .help("/FIXED - Creates a program that can be loaded only at its preferred base address.") + .execute(|_, _, _| unimplemented_option("/FIXED")); + // /FORCE - Forces a link to complete even with unresolved symbols or symbols defined more than once. + parser + .declare_with_optional_param() + .long("FORCE") + .help("/FORCE - Forces a link to complete even with unresolved symbols or symbols defined more than once.") + .execute(|_, _, _| unimplemented_option("/FORCE")); + // /FUNCTIONPADMIN - Creates an image that can be hot patched. + parser + .declare_with_optional_param() + .long("FUNCTIONPADMIN") + .help("/FUNCTIONPADMIN - Creates an image that can be hot patched.") + .execute(|_, _, _| unimplemented_option("/FUNCTIONPADMIN")); + // /GENPROFILE , /FASTGENPROFILE - Both of these options specify generation of a .pgd file by the linker to support profile-guided optimization (PGO). /GENPROFILE and /FASTGENPROFILE use different default parameters. + parser + .declare_with_optional_param() + .long("GENPROFILE") + .help("/GENPROFILE , /FASTGENPROFILE - Both of these options specify generation of a .pgd file by the linker to support profile-guided optimization (PGO). /GENPROFILE and /FASTGENPROFILE use different default parameters.") + .execute(|_, _, _| unimplemented_option("/GENPROFILE")); + // /GUARD - Enables Control Flow Guard protection. + parser + .declare_with_optional_param() + .long("GUARD") + .help("/GUARD - Enables Control Flow Guard protection.") + .execute(|_, _, _| unimplemented_option("/GUARD")); + // /HEAP - Sets the size of the heap, in bytes. + parser + .declare_with_optional_param() + .long("HEAP") + .help("/HEAP - Sets the size of the heap, in bytes.") + .execute(|args, _modifier_stack, value| { + if let Some(heap_value) = value { + // Parse heap size format: size[,reserve] + let heap_size_str = heap_value.split(',').next().unwrap_or(heap_value); + match heap_size_str.parse::() { + Ok(size) => { + args.heap_size = Some(size); + Ok(()) + } + Err(_) => { + crate::bail!("Invalid heap size: {}", heap_value); + } + } + } else { + // Default heap size or just enable heap specification + Ok(()) + } + }); + // /HIGHENTROPYVA - Specifies support for high-entropy 64-bit address space layout randomization (ASLR). + parser + .declare_with_optional_param() + .long("HIGHENTROPYVA") + .help("/HIGHENTROPYVA - Specifies support for high-entropy 64-bit address space layout randomization (ASLR).") + .execute(|args, _modifier_stack, value| { + match value { + Some("NO") => args.high_entropy_va = false, + _ => args.high_entropy_va = true, + } + Ok(()) + }); + // /IDLOUT - Specifies the name of the .idl file and other MIDL output files. + parser + .declare_with_optional_param() + .long("IDLOUT") + .help("/IDLOUT - Specifies the name of the .idl file and other MIDL output files.") + .execute(|_, _, _| unimplemented_option("/IDLOUT")); + // /IGNORE - Suppresses output of specified linker warnings. + parser + .declare_with_optional_param() + .long("IGNORE") + .help("/IGNORE - Suppresses output of specified linker warnings.") + .execute(|_, _, _| unimplemented_option("/IGNORE")); + // /IGNOREIDL - Prevents the processing of attribute information into an .idl file. + parser + .declare_with_optional_param() + .long("IGNOREIDL") + .help("/IGNOREIDL - Prevents the processing of attribute information into an .idl file.") + .execute(|_, _, _| unimplemented_option("/IGNOREIDL")); + // /ILK - Overrides the default incremental database file name. + parser + .declare_with_optional_param() + .long("ILK") + .help("/ILK - Overrides the default incremental database file name.") + .execute(|_, _, _| unimplemented_option("/ILK")); + // /IMPLIB - Overrides the default import library name. + parser + .declare_with_param() + .long("IMPLIB") + .help("/IMPLIB - Overrides the default import library name.") + .execute(|args, _modifier_stack, value| { + args.import_lib = Some(PathBuf::from(value)); + Ok(()) + }); + // /INCLUDE - Forces symbol references. + parser + .declare_with_param() + .long("INCLUDE") + .help("/INCLUDE - Forces symbol references.") + .execute(|_args, _modifier_stack, _value| { + // TODO: Implement symbol forcing + Ok(()) + }); + // /INCREMENTAL - Controls incremental linking. + parser + .declare_with_optional_param() + .long("INCREMENTAL") + .help("/INCREMENTAL - Controls incremental linking.") + .sub_option("NO", "Disable incremental linking.", |_, _| { + unimplemented_option("/INCREMENTAL:NO") + }) + .sub_option("YES", "Enable incremental linking.", |_, _| { + unimplemented_option("/INCREMENTAL:YES") + }) + .execute(|_, _, _| unimplemented_option("/INCREMENTAL")); + // /INFERASANLIBS - Uses inferred sanitizer libraries. + parser + .declare_with_optional_param() + .long("INFERASANLIBS") + .help("/INFERASANLIBS - Uses inferred sanitizer libraries.") + .execute(|_, _, _| unimplemented_option("/INFERASANLIBS")); + // /INTEGRITYCHECK - Specifies that the module requires a signature check at load time. + parser + .declare_with_optional_param() + .long("INTEGRITYCHECK") + .help( + "/INTEGRITYCHECK - Specifies that the module requires a signature check at load time.", + ) + .execute(|_, _, _| unimplemented_option("/INTEGRITYCHECK")); + // /KERNEL - Create a kernel mode binary. + parser + .declare_with_optional_param() + .long("KERNEL") + .help("/KERNEL - Create a kernel mode binary.") + .execute(|_, _, _| unimplemented_option("/KERNEL")); + // /KEYCONTAINER - Specifies a key container to sign an assembly. + parser + .declare_with_optional_param() + .long("KEYCONTAINER") + .help("/KEYCONTAINER - Specifies a key container to sign an assembly.") + .execute(|_, _, _| unimplemented_option("/KEYCONTAINER")); + // /KEYFILE - Specifies a key or key pair to sign an assembly. + parser + .declare_with_optional_param() + .long("KEYFILE") + .help("/KEYFILE - Specifies a key or key pair to sign an assembly.") + .execute(|_, _, _| unimplemented_option("/KEYFILE")); + // /LARGEADDRESSAWARE - Tells the compiler that the application supports addresses larger than 2 gigabytes + parser + .declare_with_optional_param() + .long("LARGEADDRESSAWARE") + .help("/LARGEADDRESSAWARE - Tells the compiler that the application supports addresses larger than 2 gigabytes") + .execute(|args, _modifier_stack, value| { + match value { + Some("NO") => args.large_address_aware = false, + _ => args.large_address_aware = true, + } + Ok(()) + }); + // /LIBPATH - Specifies a path to search before the environmental library path. + parser + .declare_with_param() + .long("LIBPATH") + .help("/LIBPATH - Specifies a path to search before the environmental library path.") + .execute(|args, _modifier_stack, value| { + let path = Path::new(value).into(); + args.lib_search_path.push(path); + Ok(()) + }); + // /LINKREPRO - Specifies a path to generate link repro artifacts in. + parser + .declare_with_optional_param() + .long("LINKREPRO") + .help("/LINKREPRO - Specifies a path to generate link repro artifacts in.") + .execute(|_, _, _| unimplemented_option("/LINKREPRO")); + // /LINKREPROFULLPATHRSP - Generates a response file containing the absolute paths to all the files that the linker took as input. + parser + .declare_with_optional_param() + .long("LINKREPROFULLPATHRSP") + .help("/LINKREPROFULLPATHRSP - Generates a response file containing the absolute paths to all the files that the linker took as input.") + .execute(|_, _, _| unimplemented_option("/LINKREPROFULLPATHRSP")); + // /LINKREPROTARGET - Generates a link repro only when producing the specified target. 16.1 + parser + .declare_with_optional_param() + .long("LINKREPROTARGET") + .help("/LINKREPROTARGET - Generates a link repro only when producing the specified target. 16.1") + .execute(|_, _, _| unimplemented_option("/LINKREPROTARGET")); + // /LTCG - Specifies link-time code generation. + parser + .declare_with_optional_param() + .long("LTCG") + .help("/LTCG - Specifies link-time code generation.") + .sub_option("NOSTATUS", "Do not display progress.", |_, _| { + unimplemented_option("/LTCG:NOSTATUS") + }) + .sub_option("STATUS", "Display progress.", |_, _| { + unimplemented_option("/LTCG:STATUS") + }) + .sub_option("INCREMENTAL", "Enable incremental LTCG.", |_, _| { + unimplemented_option("/LTCG:INCREMENTAL") + }) + .execute(|_, _, _| unimplemented_option("/LTCG")); + // /MACHINE - Specifies the target platform. + parser + .declare_with_param() + .long("MACHINE") + .help("/MACHINE - Specifies the target platform.") + .sub_option("ARM", "ARM", |args, _| { + args.arch = Architecture::AArch64; + Ok(()) + }) + .sub_option("ARM64", "ARM64", |args, _| { + args.arch = Architecture::AArch64; + Ok(()) + }) + .sub_option("ARM64EC", "ARM64EC", |args, _| { + args.arch = Architecture::AArch64; + Ok(()) + }) + .sub_option("EBC", "EBC", |_args, _| { + // EFI Byte Code - not commonly supported + Ok(()) + }) + .sub_option("X64", "X64", |args, _| { + args.arch = Architecture::X86_64; + Ok(()) + }) + .sub_option("X86", "X86", |args, _| { + args.arch = Architecture::X86_64; // Treat as X86_64 for simplicity + Ok(()) + }) + .execute(|args, _, value| { + // Handle direct architecture specification + match value.to_uppercase().as_str() { + "ARM" | "ARM64" | "ARM64EC" => args.arch = Architecture::AArch64, + "X64" | "X86" => args.arch = Architecture::X86_64, + _ => {} // Ignore unknown architectures + } + Ok(()) + }); + // /MANIFEST - Creates a side-by-side manifest file and optionally embeds it in the binary. + parser + .declare_with_optional_param() + .long("MANIFEST") + .help("/MANIFEST - Creates a side-by-side manifest file and optionally embeds it in the binary.") + .execute(|_, _, _| unimplemented_option("/MANIFEST")); + // /MANIFESTDEPENDENCY - Specifies a section in the manifest file. + parser + .declare_with_optional_param() + .long("MANIFESTDEPENDENCY") + .help("/MANIFESTDEPENDENCY - Specifies a section in the manifest file.") + .execute(|_, _, _| unimplemented_option("/MANIFESTDEPENDENCY")); + // /MANIFESTFILE - Changes the default name of the manifest file. + parser + .declare_with_param() + .long("MANIFESTFILE") + .help("/MANIFESTFILE - Changes the default name of the manifest file.") + .execute(|args, _modifier_stack, value| { + args.manifest_file = Some(PathBuf::from(value)); + Ok(()) + }); + // /MANIFESTINPUT - Specifies a manifest input file for the linker to process and embed in the binary. You can use this option multiple times to specify more than one manifest input file. + parser + .declare_with_optional_param() + .long("MANIFESTINPUT") + .help("/MANIFESTINPUT - Specifies a manifest input file for the linker to process and embed in the binary. You can use this option multiple times to specify more than one manifest input file.") + .execute(|_, _, _| unimplemented_option("/MANIFESTINPUT")); + // /MANIFESTUAC - Specifies whether User Account Control (UAC) information is embedded in the program manifest. + parser + .declare_with_optional_param() + .long("MANIFESTUAC") + .help("/MANIFESTUAC - Specifies whether User Account Control (UAC) information is embedded in the program manifest.") + .execute(|_, _, _| unimplemented_option("/MANIFESTUAC")); + // /MAP - Creates a mapfile. + parser + .declare_with_optional_param() + .long("MAP") + .help("/MAP - Creates a mapfile.") + .execute(|args, _modifier_stack, value| { + match value { + Some(filename) => args.map_file = Some(PathBuf::from(filename)), + None => { + // Default map file name based on output name + let output_stem = args + .output + .file_stem() + .unwrap_or_else(|| std::ffi::OsStr::new("output")); + let mut map_name = output_stem.to_os_string(); + map_name.push(".map"); + args.map_file = Some(PathBuf::from(map_name)); + } + } + Ok(()) + }); + // /MAPINFO - Includes the specified information in the mapfile. + parser + .declare_with_optional_param() + .long("MAPINFO") + .help("/MAPINFO - Includes the specified information in the mapfile.") + .execute(|_, _, _| unimplemented_option("/MAPINFO")); + // /MERGE - Combines sections. + parser + .declare_with_optional_param() + .long("MERGE") + .help("/MERGE - Combines sections.") + .execute(|_, _, _| unimplemented_option("/MERGE")); + // /MIDL - Specifies MIDL command-line options. + parser + .declare_with_optional_param() + .long("MIDL") + .help("/MIDL - Specifies MIDL command-line options.") + .execute(|_, _, _| unimplemented_option("/MIDL")); + // /NATVIS - Adds debugger visualizers from a Natvis file to the program database (PDB). + parser + .declare_with_optional_param() + .long("NATVIS") + .help( + "/NATVIS - Adds debugger visualizers from a Natvis file to the program database (PDB).", + ) + .execute(|_, _, _| unimplemented_option("/NATVIS")); + // /NOASSEMBLY - Suppresses the creation of a .NET Framework assembly. + parser + .declare_with_optional_param() + .long("NOASSEMBLY") + .help("/NOASSEMBLY - Suppresses the creation of a .NET Framework assembly.") + .execute(|_, _, _| unimplemented_option("/NOASSEMBLY")); + // /NODEFAULTLIB - Ignores all (or the specified) default libraries when external references are resolved. + parser + .declare_with_optional_param() + .long("NODEFAULTLIB") + .help("/NODEFAULTLIB - Ignores all (or the specified) default libraries when external references are resolved.") + .execute(|args, _modifier_stack, value| { + match value { + Some(lib_name) => { + // Ignore specific library + args.no_default_libs.push(lib_name.to_string()); + } + None => { + // Ignore all default libraries + args.ignore_all_default_libs = true; + } + } + Ok(()) + }); + // /NOENTRY - Creates a resource-only DLL. + parser + .declare_with_optional_param() + .long("NOENTRY") + .help("/NOENTRY - Creates a resource-only DLL.") + .execute(|_, _, _| unimplemented_option("/NOENTRY")); + // /NOFUNCTIONPADSECTION - Disables function padding for functions in the specified section. 17.8 + parser + .declare_with_optional_param() + .long("NOFUNCTIONPADSECTION") + .help("/NOFUNCTIONPADSECTION - Disables function padding for functions in the specified section. 17.8") + .execute(|_, _, _| unimplemented_option("/NOFUNCTIONPADSECTION")); + // /NOLOGO - Suppresses the startup banner. + parser + .declare_with_optional_param() + .long("NOLOGO") + .help("/NOLOGO - Suppresses the startup banner.") + .execute(|_, _, _| Ok(())); + // /NXCOMPAT - Marks an executable as verified to be compatible with the Windows Data Execution Prevention feature. + parser + .declare_with_optional_param() + .long("NXCOMPAT") + .help("/NXCOMPAT - Marks an executable as verified to be compatible with the Windows Data Execution Prevention feature.") + .execute(|args, _modifier_stack, value| { + match value { + Some("NO") => args.nx_compat = false, + _ => args.nx_compat = true, + } + Ok(()) + }); + // /OPT - Controls LINK optimizations. + parser + .declare_with_param() + .long("OPT") + .help("/OPT - Controls LINK optimizations.") + .sub_option( + "REF", + "Eliminate unreferenced functions and data.", + |_, _| unimplemented_option("/OPT:REF"), + ) + .sub_option("NOREF", "Keep unreferenced functions and data.", |_, _| { + unimplemented_option("/OPT:NOREF") + }) + .sub_option("ICF", "Fold identical COMDATs.", |_, _| { + unimplemented_option("/OPT:ICF") + }) + .sub_option("NOICF", "Disable identical COMDAT folding.", |_, _| { + unimplemented_option("/OPT:NOICF") + }) + .sub_option( + "LBR", + "Enable profile guided optimizations (LBR).", + |_, _| unimplemented_option("/OPT:LBR"), + ) + .sub_option( + "NOLBR", + "Disable profile guided optimizations (no LBR).", + |_, _| unimplemented_option("/OPT:NOLBR"), + ) + .execute(|_, _, _| unimplemented_option("/OPT")); + // /ORDER - Places COMDATs into the image in a predetermined order. + parser + .declare_with_optional_param() + .long("ORDER") + .help("/ORDER - Places COMDATs into the image in a predetermined order.") + .execute(|_, _, _| unimplemented_option("/ORDER")); + // /OUT - Specifies the output file name. + parser + .declare_with_param() + .long("OUT") + .help("/OUT - Specifies the output file name.") + .execute(|args, _modifier_stack, value| { + args.output = Arc::from(Path::new(value)); + Ok(()) + }); + // /PDB - Creates a PDB file. + parser + .declare_with_optional_param() + .long("PDB") + .help("/PDB - Creates a PDB file.") + .execute(|args, _modifier_stack, value| { + match value { + Some(filename) => args.pdb_file = Some(PathBuf::from(filename)), + None => { + // Default PDB file name based on output name + let output_stem = args + .output + .file_stem() + .unwrap_or_else(|| std::ffi::OsStr::new("output")); + let mut pdb_name = output_stem.to_os_string(); + pdb_name.push(".pdb"); + args.pdb_file = Some(PathBuf::from(pdb_name)); + } + } + Ok(()) + }); + // /PDBALTPATH - Uses an alternate location to save a PDB file. + parser + .declare_with_optional_param() + .long("PDBALTPATH") + .help("/PDBALTPATH - Uses an alternate location to save a PDB file.") + .execute(|_, _, _| unimplemented_option("/PDBALTPATH")); + // /PDBSTRIPPED - Creates a PDB file that has no private symbols. + parser + .declare_with_optional_param() + .long("PDBSTRIPPED") + .help("/PDBSTRIPPED - Creates a PDB file that has no private symbols.") + .execute(|_, _, _| unimplemented_option("/PDBSTRIPPED")); + // /PGD - Specifies a .pgd file for profile-guided optimizations. + parser + .declare_with_optional_param() + .long("PGD") + .help("/PGD - Specifies a .pgd file for profile-guided optimizations.") + .execute(|_, _, _| unimplemented_option("/PGD")); + // /POGOSAFEMODE - Obsolete Creates a thread-safe PGO instrumented build. + parser + .declare_with_optional_param() + .long("POGOSAFEMODE") + .help("/POGOSAFEMODE - Obsolete Creates a thread-safe PGO instrumented build.") + .execute(|_, _, _| unimplemented_option("/POGOSAFEMODE")); + // /PROFILE - Produces an output file that can be used with the Performance Tools profiler. + parser + .declare_with_optional_param() + .long("PROFILE") + .help("/PROFILE - Produces an output file that can be used with the Performance Tools profiler.") + .execute(|_, _, _| unimplemented_option("/PROFILE")); + // /RELEASE - Sets the Checksum in the .exe header. + parser + .declare_with_optional_param() + .long("RELEASE") + .help("/RELEASE - Sets the Checksum in the .exe header.") + .execute(|_, _, _| unimplemented_option("/RELEASE")); + // /SAFESEH - Specifies that the image will contain a table of safe exception handlers. + parser + .declare_with_optional_param() + .long("SAFESEH") + .help( + "/SAFESEH - Specifies that the image will contain a table of safe exception handlers.", + ) + .execute(|_, _, _| unimplemented_option("/SAFESEH")); + // /SECTION - Overrides the attributes of a section. + parser + .declare_with_optional_param() + .long("SECTION") + .help("/SECTION - Overrides the attributes of a section.") + .execute(|_, _, _| unimplemented_option("/SECTION")); + // /SOURCELINK - Specifies a SourceLink file to add to the PDB. + parser + .declare_with_optional_param() + .long("SOURCELINK") + .help("/SOURCELINK - Specifies a SourceLink file to add to the PDB.") + .execute(|_, _, _| unimplemented_option("/SOURCELINK")); + // /STACK - Sets the size of the stack in bytes. + parser + .declare_with_optional_param() + .long("STACK") + .help("/STACK - Sets the size of the stack in bytes.") + .execute(|args, _modifier_stack, value| { + if let Some(stack_value) = value { + // Parse stack size format: size[,reserve] + let stack_size_str = stack_value.split(',').next().unwrap_or(stack_value); + match stack_size_str.parse::() { + Ok(size) => { + args.stack_size = Some(size); + Ok(()) + } + Err(_) => { + crate::bail!("Invalid stack size: {}", stack_value); + } + } + } else { + // Default stack size or just enable stack specification + Ok(()) + } + }); + // /STUB - Attaches an MS-DOS stub program to a Win32 program. + parser + .declare_with_optional_param() + .long("STUB") + .help("/STUB - Attaches an MS-DOS stub program to a Win32 program.") + .execute(|_, _, _| unimplemented_option("/STUB")); + // /SUBSYSTEM - Tells the operating system how to run the .exe file. + parser + .declare_with_param() + .long("SUBSYSTEM") + .help("/SUBSYSTEM - Tells the operating system how to run the .exe file.") + .sub_option("BOOT_APPLICATION", "Boot application", |args, _| { + args.subsystem = Some(WindowsSubsystem::BootApplication); + Ok(()) + }) + .sub_option("CONSOLE", "Console", |args, _| { + args.subsystem = Some(WindowsSubsystem::Console); + Ok(()) + }) + .sub_option("WINDOWS", "Windows GUI", |args, _| { + args.subsystem = Some(WindowsSubsystem::Windows); + Ok(()) + }) + .sub_option("NATIVE", "Native", |args, _| { + args.subsystem = Some(WindowsSubsystem::Native); + Ok(()) + }) + .sub_option("POSIX", "POSIX", |args, _| { + args.subsystem = Some(WindowsSubsystem::Posix); + Ok(()) + }) + .sub_option("EFI_APPLICATION", "EFI application", |args, _| { + args.subsystem = Some(WindowsSubsystem::EfiApplication); + Ok(()) + }) + .sub_option( + "EFI_BOOT_SERVICE_DRIVER", + "EFI boot service driver", + |args, _| { + args.subsystem = Some(WindowsSubsystem::EfiBootServiceDriver); + Ok(()) + }, + ) + .sub_option("EFI_ROM", "EFI ROM", |args, _| { + args.subsystem = Some(WindowsSubsystem::EfiRom); + Ok(()) + }) + .sub_option("EFI_RUNTIME_DRIVER", "EFI runtime driver", |args, _| { + args.subsystem = Some(WindowsSubsystem::EfiRuntimeDriver); + Ok(()) + }) + .execute(|args, _, value| { + // Handle direct subsystem specification + match value.to_uppercase().as_str() { + "BOOT_APPLICATION" => args.subsystem = Some(WindowsSubsystem::BootApplication), + "CONSOLE" => args.subsystem = Some(WindowsSubsystem::Console), + "WINDOWS" => args.subsystem = Some(WindowsSubsystem::Windows), + "NATIVE" => args.subsystem = Some(WindowsSubsystem::Native), + "POSIX" => args.subsystem = Some(WindowsSubsystem::Posix), + "EFI_APPLICATION" => args.subsystem = Some(WindowsSubsystem::EfiApplication), + "EFI_BOOT_SERVICE_DRIVER" => { + args.subsystem = Some(WindowsSubsystem::EfiBootServiceDriver) + } + "EFI_ROM" => args.subsystem = Some(WindowsSubsystem::EfiRom), + "EFI_RUNTIME_DRIVER" => args.subsystem = Some(WindowsSubsystem::EfiRuntimeDriver), + _ => {} // Ignore unknown subsystems + } + Ok(()) + }); + // /SWAPRUN - Tells the operating system to copy the linker output to a swap file before it's run. + parser + .declare_with_optional_param() + .long("SWAPRUN") + .help("/SWAPRUN - Tells the operating system to copy the linker output to a swap file before it's run.") + .execute(|_, _, _| unimplemented_option("/SWAPRUN")); + // /TIME - Output linker pass timing information. + parser + .declare_with_optional_param() + .long("TIME") + .help("/TIME - Output linker pass timing information.") + .execute(|_, _, _| unimplemented_option("/TIME")); + // /TLBID - Specifies the resource ID of the linker-generated type library. + parser + .declare_with_optional_param() + .long("TLBID") + .help("/TLBID - Specifies the resource ID of the linker-generated type library.") + .execute(|_, _, _| unimplemented_option("/TLBID")); + // /TLBOUT - Specifies the name of the .tlb file and other MIDL output files. + parser + .declare_with_optional_param() + .long("TLBOUT") + .help("/TLBOUT - Specifies the name of the .tlb file and other MIDL output files.") + .execute(|_, _, _| unimplemented_option("/TLBOUT")); + // /TSAWARE - Creates an application that is designed specifically to run under Terminal Server. + parser + .declare_with_optional_param() + .long("TSAWARE") + .help("/TSAWARE - Creates an application that is designed specifically to run under Terminal Server.") + .execute(|args, _modifier_stack, value| { + match value { + Some("NO") => args.terminal_server_aware = false, + _ => args.terminal_server_aware = true, + } + Ok(()) + }); + // /USEPROFILE - Uses profile-guided optimization training data to create an optimized image. + parser + .declare_with_optional_param() + .long("USEPROFILE") + .help("/USEPROFILE - Uses profile-guided optimization training data to create an optimized image.") + .execute(|_, _, _| unimplemented_option("/USEPROFILE")); + // /VERBOSE - Prints linker progress messages. + parser + .declare_with_optional_param() + .long("VERBOSE") + .help("/VERBOSE - Prints linker progress messages.") + .execute(|_, _, _| unimplemented_option("/VERBOSE")); + // /VERSION - Assigns a version number. + parser + .declare_with_param() + .long("VERSION") + .help("/VERSION - Assigns a version number.") + .execute(|args, _modifier_stack, value| { + args.version = Some(value.to_string()); + Ok(()) + }); + // /WHOLEARCHIVE - Includes every object file from specified static libraries. + parser + .declare_with_optional_param() + .long("WHOLEARCHIVE") + .help("/WHOLEARCHIVE - Includes every object file from specified static libraries.") + .execute(|_, _, _| unimplemented_option("/WHOLEARCHIVE")); + // /WINMD - Enables generation of a Windows Runtime Metadata file. + parser + .declare_with_optional_param() + .long("WINMD") + .help("/WINMD - Enables generation of a Windows Runtime Metadata file.") + .execute(|_, _, _| unimplemented_option("/WINMD")); + // /WINMDFILE - Specifies the file name for the Windows Runtime Metadata (winmd) output file that's generated by the /WINMD linker option. + parser + .declare_with_optional_param() + .long("WINMDFILE") + .help("/WINMDFILE - Specifies the file name for the Windows Runtime Metadata (winmd) output file that's generated by the /WINMD linker option.") + .execute(|_, _, _| unimplemented_option("/WINMDFILE")); + // /WINMDKEYFILE - Specifies a key or key pair to sign a Windows Runtime Metadata file. + parser + .declare_with_optional_param() + .long("WINMDKEYFILE") + .help( + "/WINMDKEYFILE - Specifies a key or key pair to sign a Windows Runtime Metadata file.", + ) + .execute(|_, _, _| unimplemented_option("/WINMDKEYFILE")); + // /WINMDKEYCONTAINER - Specifies a key container to sign a Windows Metadata file. + parser + .declare_with_optional_param() + .long("WINMDKEYCONTAINER") + .help("/WINMDKEYCONTAINER - Specifies a key container to sign a Windows Metadata file.") + .execute(|_, _, _| unimplemented_option("/WINMDKEYCONTAINER")); + // /WINMDDELAYSIGN - Partially signs a Windows Runtime Metadata ( .winmd ) file by placing the public key in the winmd file. + parser + .declare_with_optional_param() + .long("WINMDDELAYSIGN") + .help("/WINMDDELAYSIGN - Partially signs a Windows Runtime Metadata ( .winmd ) file by placing the public key in the winmd file.") + .execute(|_, _, _| unimplemented_option("/WINMDDELAYSIGN")); + // /WX - Treats linker warnings as errors. + parser + .declare_with_optional_param() + .long("WX") + .help("/WX - Treats linker warnings as errors.") + .execute(|_args: &mut super::Args, _modifier_stack, _value| { + unimplemented_option("/WX") + }); + + add_silently_ignored_flags(&mut parser); + add_default_flags(&mut parser); + + parser +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::args::InputSpec; + use std::path::Path; + + // Example Windows linker flags from Rust compilation + const WINDOWS_LINKER_ARGS: [&str; 28] = [ + "--target=x86_64-pc-windows-msvc", + r#"C:\Users\Samuel\AppData\Local\Temp\rustc7RL5Io\symbols.o"#, + "dummy.dummy.6cfbe55db138f4b-cgu.0.rcgu.o", + "dummy.3wxfnlvokcqcl6j45c8xeicgz.rcgu.o", + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libstd-efa6c7783284bd31.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libpanic_unwind-43468c47cff21662.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libwindows_targets-3935b75a1bd1c449.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\librustc_demangle-cc0fa0adec36251f.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libstd_detect-22f2c46a93af1174.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libhashbrown-c835068eb56f6efb.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\librustc_std_workspace_alloc-abe24411cb8f5bd4.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libunwind-b5e24931eb1ae1bd.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libcfg_if-8dc64876e32b9d07.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\librustc_std_workspace_core-214bcacef209824d.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\liballoc-3e14ad51a3206bab.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libcore-a55e6b132b0b5f5d.rlib"#, + r#"C:\Users\Samuel\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\x86_64-pc-windows-msvc\lib\libcompiler_builtins-b994e165f6ecc9e9.rlib"#, + "kernel32.lib", + "kernel32.lib", + "kernel32.lib", + "ntdll.lib", + "userenv.lib", + "ws2_32.lib", + "dbghelp.lib", + "/defaultlib:msvcrt", + "/NXCOMPAT", + "/OUT:dummy.exe", + "/DEBUG", + ]; + + #[track_caller] + fn assert_contains_file(inputs: &[Input], file_path: &str) { + assert!(inputs.iter().any(|input| match &input.spec { + InputSpec::File(path) => path.as_ref() == Path::new(file_path), + _ => false, + })); + } + + #[track_caller] + fn assert_contains_lib(inputs: &[Input], lib_name: &str) { + assert!(inputs.iter().any(|input| match &input.spec { + InputSpec::Lib(name) => name.as_ref() == lib_name, + _ => false, + })); + } + + /// Extract Args from unified Args, panicking if it's not the Pe variant. + #[track_caller] + fn unwrap_pe(args: crate::args::Args) -> crate::args::Args { + args.map_target(|t| match t { + crate::args::TargetArgs::Pe(pe) => pe, + other => panic!( + "Expected Pe variant, got {:?}", + std::mem::discriminant(&other) + ), + }) + } + + #[test] + fn test_parse_windows_linker_args() { + let args = + unwrap_pe(crate::args::Args::parse(|| WINDOWS_LINKER_ARGS.into_iter()).unwrap()); + + // Test that key flags were parsed correctly + assert!(args.debug_info); // /DEBUG flag + assert!(args.nx_compat); // /NXCOMPAT flag + + // Test that output file was set + assert_eq!(args.output.as_ref(), Path::new("dummy.exe")); + + // Test that input files were collected + assert_contains_file(&args.inputs, "dummy.dummy.6cfbe55db138f4b-cgu.0.rcgu.o"); + assert_contains_file(&args.inputs, "dummy.3wxfnlvokcqcl6j45c8xeicgz.rcgu.o"); + + // Test that library files were collected + assert_contains_file(&args.inputs, "kernel32.lib"); + assert_contains_file(&args.inputs, "ntdll.lib"); + assert_contains_file(&args.inputs, "userenv.lib"); + assert_contains_file(&args.inputs, "ws2_32.lib"); + assert_contains_file(&args.inputs, "dbghelp.lib"); + + // Test that rlib files were collected + assert!(args.inputs.iter().any(|input| { + match &input.spec { + InputSpec::File(path) => path + .to_string_lossy() + .contains("libstd-efa6c7783284bd31.rlib"), + _ => false, + } + })); + assert!(args.inputs.iter().any(|input| { + match &input.spec { + InputSpec::File(path) => path + .to_string_lossy() + .contains("libcore-a55e6b132b0b5f5d.rlib"), + _ => false, + } + })); + + // Test that /defaultlib was handled and added library to inputs + assert_contains_lib(&args.inputs, "msvcrt"); + + // Verify some key libraries are present + let lib_names: Vec<&str> = args + .inputs + .iter() + .filter_map(|input| match &input.spec { + InputSpec::Lib(lib_name) => Some(lib_name.as_ref()), + _ => None, + }) + .collect(); + assert!(lib_names.contains(&"msvcrt")); + } + + #[test] + fn test_minimal_windows_args() { + let minimal_args = [ + "--target=x86_64-pc-windows-msvc", + "/OUT:test.exe", + "/DEBUG", + "test.obj", + ]; + + let args = unwrap_pe(crate::args::Args::parse(|| minimal_args.into_iter()).unwrap()); + + assert_eq!(args.output.as_ref(), Path::new("test.exe")); + println!("Debug info value: {}", args.debug_info); + assert!(args.debug_info); + assert_contains_file(&args.inputs, "test.obj"); + } + + #[test] + fn test_debug_flag_simple() { + let minimal_args = ["--target=x86_64-pc-windows-msvc", "/DEBUG"]; + + let result = crate::args::Args::parse(|| minimal_args.into_iter()); + match result { + Ok(args) => { + let windows_args = unwrap_pe(args); + println!( + "Simple debug test - Debug info value: {}", + windows_args.debug_info + ); + println!( + "Unrecognized options: {:?}", + windows_args.unrecognized_options + ); + assert!(windows_args.debug_info); + } + Err(e) => { + println!("Parse error: {:?}", e); + panic!("Failed to parse arguments: {:?}", e); + } + } + } + + #[test] + fn test_defaultlib_parsing() { + let minimal_args = ["--target=x86_64-pc-windows-msvc", "/defaultlib:msvcrt"]; + + let args = unwrap_pe(crate::args::Args::parse(|| minimal_args.into_iter()).unwrap()); + + let lib_names: Vec<&str> = args + .inputs + .iter() + .filter_map(|input| match &input.spec { + InputSpec::Lib(lib_name) => Some(lib_name.as_ref()), + _ => None, + }) + .collect(); + + println!("Found libraries: {:?}", lib_names); + println!("Unrecognized options: {:?}", args.unrecognized_options); + + assert_contains_lib(&args.inputs, "msvcrt"); + } + + #[test] + fn test_required_parameters() { + // Test that IMPLIB requires a parameter + let implib_args = ["--target=x86_64-pc-windows-msvc", "/IMPLIB"]; + + let result = crate::args::Args::parse(|| implib_args.into_iter()); + match result { + Ok(_) => panic!("Expected error for IMPLIB without parameter"), + Err(e) => { + let error_msg = format!("{:?}", e); + assert!( + error_msg.contains("Missing argument") || error_msg.contains("IMPLIB"), + "Error should mention missing argument for IMPLIB: {}", + error_msg + ); + } + } + + // Test that EXPORT requires a parameter + let export_args = ["--target=x86_64-pc-windows-msvc", "/EXPORT"]; + + let result = crate::args::Args::parse(|| export_args.into_iter()); + match result { + Ok(_) => panic!("Expected error for EXPORT without parameter"), + Err(e) => { + let error_msg = format!("{:?}", e); + assert!( + error_msg.contains("Missing argument") || error_msg.contains("EXPORT"), + "Error should mention missing argument for EXPORT: {}", + error_msg + ); + } + } + + // Test that VERSION requires a parameter + let version_args = ["--target=x86_64-pc-windows-msvc", "/VERSION"]; + + let result = crate::args::Args::parse(|| version_args.into_iter()); + match result { + Ok(_) => panic!("Expected error for VERSION without parameter"), + Err(e) => { + let error_msg = format!("{:?}", e); + assert!( + error_msg.contains("Missing argument") || error_msg.contains("VERSION"), + "Error should mention missing argument for VERSION: {}", + error_msg + ); + } + } + } + + #[test] + fn test_unimplemented_options() { + // Test that unimplemented options return proper error messages + let appcontainer_args = ["--target=x86_64-pc-windows-msvc", "/APPCONTAINER"]; + + let result = crate::args::Args::parse(|| appcontainer_args.into_iter()); + match result { + Ok(_) => panic!("Expected error for unimplemented APPCONTAINER option"), + Err(e) => { + let error_msg = format!("{:?}", e); + assert!( + error_msg.contains("not yet implemented") && error_msg.contains("APPCONTAINER"), + "Error should mention APPCONTAINER is not implemented: {}", + error_msg + ); + } + } + + // Test another unimplemented option + let assemblydebug_args = ["--target=x86_64-pc-windows-msvc", "/ASSEMBLYDEBUG"]; + + let result = crate::args::Args::parse(|| assemblydebug_args.into_iter()); + match result { + Ok(_) => panic!("Expected error for unimplemented ASSEMBLYDEBUG option"), + Err(e) => { + let error_msg = format!("{:?}", e); + assert!( + error_msg.contains("not yet implemented") + && error_msg.contains("ASSEMBLYDEBUG"), + "Error should mention ASSEMBLYDEBUG is not implemented: {}", + error_msg + ); + } + } + } + + #[test] + fn test_case_insensitive_parsing() { + // Test uppercase /ENTRY:main and /OUT:test.exe + let args_upper = [ + "--target=x86_64-pc-windows-msvc", + "/ENTRY:main", + "/OUT:test.exe", + ]; + let result_upper = unwrap_pe(crate::args::Args::parse(|| args_upper.into_iter()).unwrap()); + assert_eq!(result_upper.entry, Some("main".to_string())); + assert_eq!(result_upper.output.as_ref(), Path::new("test.exe")); + + // Test lowercase /entry:main and /out:test.exe + let args_lower = [ + "--target=x86_64-pc-windows-msvc", + "/entry:main", + "/out:test.exe", + ]; + let result_lower = unwrap_pe(crate::args::Args::parse(|| args_lower.into_iter()).unwrap()); + assert_eq!(result_lower.entry, Some("main".to_string())); + assert_eq!(result_lower.output.as_ref(), Path::new("test.exe")); + + // Test mixed case /Entry:main and /Out:test.exe + let args_mixed = [ + "--target=x86_64-pc-windows-msvc", + "/Entry:main", + "/Out:test.exe", + ]; + let result_mixed = unwrap_pe(crate::args::Args::parse(|| args_mixed.into_iter()).unwrap()); + assert_eq!(result_mixed.entry, Some("main".to_string())); + assert_eq!(result_mixed.output.as_ref(), Path::new("test.exe")); + } + + #[test] + fn test_nodefaultlib_parsing() { + // Test /NODEFAULTLIB without parameter (ignore all default libraries) + let args_all = ["--target=x86_64-pc-windows-msvc", "/NODEFAULTLIB"]; + let result_all = unwrap_pe(crate::args::Args::parse(|| args_all.into_iter()).unwrap()); + assert!(result_all.ignore_all_default_libs); + assert!(result_all.no_default_libs.is_empty()); + + // Test /NODEFAULTLIB with specific library name + let args_specific = ["--target=x86_64-pc-windows-msvc", "/NODEFAULTLIB:msvcrt"]; + let result_specific = unwrap_pe(crate::args::Args::parse(|| args_specific.into_iter()).unwrap()); + assert!(!result_specific.ignore_all_default_libs); + assert_eq!(result_specific.no_default_libs, vec!["msvcrt"]); + + // Test multiple specific libraries + let args_multiple = [ + "--target=x86_64-pc-windows-msvc", + "/NODEFAULTLIB:msvcrt", + "/NODEFAULTLIB:kernel32", + ]; + let result_multiple = unwrap_pe(crate::args::Args::parse(|| args_multiple.into_iter()).unwrap()); + assert!(!result_multiple.ignore_all_default_libs); + assert_eq!(result_multiple.no_default_libs, vec!["msvcrt", "kernel32"]); + + // Test case-insensitive matching + let args_case_insensitive = ["--target=x86_64-pc-windows-msvc", "/nodefaultlib:msvcrt"]; + let result_case_insensitive = + unwrap_pe(crate::args::Args::parse(|| args_case_insensitive.into_iter()).unwrap()); + assert!(!result_case_insensitive.ignore_all_default_libs); + assert_eq!(result_case_insensitive.no_default_libs, vec!["msvcrt"]); + } + + #[test] + fn test_nodefaultlib_helper_methods() { + // Test helper methods for ignore all default libraries + let args_all = ["--target=x86_64-pc-windows-msvc", "/NODEFAULTLIB"]; + let result_all = unwrap_pe(crate::args::Args::parse(|| args_all.into_iter()).unwrap()); + + assert!(result_all.ignores_all_default_libs()); + assert!(result_all.should_ignore_default_lib("msvcrt")); + assert!(result_all.should_ignore_default_lib("kernel32")); + assert!(result_all.ignored_default_libs().is_empty()); + + // Test helper methods for specific libraries + let args_specific = [ + "--target=x86_64-pc-windows-msvc", + "/NODEFAULTLIB:msvcrt", + "/NODEFAULTLIB:kernel32", + ]; + let result_specific = unwrap_pe(crate::args::Args::parse(|| args_specific.into_iter()).unwrap()); + + assert!(!result_specific.ignores_all_default_libs()); + assert!(result_specific.should_ignore_default_lib("msvcrt")); + assert!(result_specific.should_ignore_default_lib("kernel32")); + assert!(!result_specific.should_ignore_default_lib("user32")); + assert_eq!( + result_specific.ignored_default_libs(), + &["msvcrt", "kernel32"] + ); + } +} diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index 3413fd975..64812d5c0 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -97,7 +97,7 @@ use tracing_subscriber::util::SubscriberInitExt; /// Runs the linker and cleans up associated resources. Only use this function if you've OK with /// waiting for cleanup. -pub fn run(args: Args) -> error::Result { +pub fn run(args: Args) -> error::Result { // Note, we need to setup tracing before we activate the thread pool. In particular, we need to // initialise the timing module before the worker threads are started, otherwise the threads // won't contribute to counters such as --time=cycles,instructions etc. @@ -113,7 +113,7 @@ pub fn run(args: Args) -> error::Result { /// Sets up whatever tracing, if any, is indicated by the supplied arguments. This can only be /// called once and only if nothing else has already set the global tracing dispatcher. Calling this /// is optional. If it isn't called, no tracing-based features will function. e.g. --time. -pub fn setup_tracing(args: &Args) -> Result<(), AlreadyInitialised> { +pub fn setup_tracing(args: &Args) -> Result<(), AlreadyInitialised> { if let Some(opts) = args.time_phase_options.as_ref() { timing::init_tracing(opts) } else if args.print_allocations.is_some() { @@ -178,7 +178,7 @@ impl Linker { /// return, the output file should be usable. pub fn run<'layout_inputs>( &'layout_inputs self, - args: &'layout_inputs ActivatedArgs, + args: &'layout_inputs ActivatedArgs, ) -> error::Result> { let args = &args.args; match args.version_mode { @@ -209,7 +209,7 @@ impl Linker { fn link_for_arch<'data, P: Platform = crate::elf::File<'data>>>( &'data self, - args: &'data Args, + args: &'data Args, ) -> error::Result> { let mut file_loader = input_data::FileLoader::new(&self.inputs_arena); @@ -238,7 +238,7 @@ impl Linker { fn load_inputs_and_link<'data, P: Platform = crate::elf::File<'data>>>( &'data self, file_loader: &mut FileLoader<'data>, - args: &'data Args, + args: &'data Args, ) -> error::Result> { let mut plugin = linker_plugins::LinkerPlugin::from_args(args, &self.linker_plugin_arena, &self.herd)?; diff --git a/libwild/src/linker_plugins_disabled.rs b/libwild/src/linker_plugins_disabled.rs index 7570c686b..21fe7e6ff 100644 --- a/libwild/src/linker_plugins_disabled.rs +++ b/libwild/src/linker_plugins_disabled.rs @@ -35,7 +35,7 @@ impl<'data> LinkerPlugin<'data> { } pub(crate) fn from_args( - _args: &'data crate::Args, + _args: &'data crate::Args, _linker_plugin_arena: &colosseum::sync::Arena, _herd: &bumpalo_herd::Herd, ) -> Result> { diff --git a/libwild/src/save_dir.rs b/libwild/src/save_dir.rs index 32d9bebc2..dd4524d57 100644 --- a/libwild/src/save_dir.rs +++ b/libwild/src/save_dir.rs @@ -129,10 +129,10 @@ impl SaveDirState { /// Finalise the save directory. Makes sure that all `filenames` have been copied, writes the /// `run-with` file and if the environment variable is set to indicate that we should skip /// linking, then exit. - fn finish<'a, I: Iterator>( + fn finish<'a, I: Iterator, T>( &self, filenames: I, - parsed_args: &Args, + parsed_args: &Args, ) -> Result { for filename in filenames { self.copy_file(&std::path::absolute(filename)?, parsed_args)?; @@ -148,7 +148,7 @@ impl SaveDirState { Ok(()) } - fn write_args_file(&self, run_file: &Path, args: &Args) -> Result { + fn write_args_file(&self, run_file: &Path, args: &Args) -> Result { let mut file = std::fs::File::create(run_file)?; let mut out = BufWriter::new(&mut file); out.write_all(PRELUDE.as_bytes())?; @@ -229,7 +229,7 @@ impl SaveDirState { } /// Copies `source_path` to our output directory. - fn copy_file(&self, source_path: &Path, parsed_args: &Args) -> Result { + fn copy_file(&self, source_path: &Path, parsed_args: &Args) -> Result { let dest_path = self.output_path(source_path); if dest_path.exists() || !source_path.exists() { @@ -331,7 +331,7 @@ impl SaveDirState { } /// Copies the files listed by the thin archive. - fn handle_thin_archive(&self, path: &Path, parsed_args: &Args) -> Result { + fn handle_thin_archive(&self, path: &Path, parsed_args: &Args) -> Result { let file_bytes = std::fs::read(path)?; let parent_path = path.parent().unwrap(); @@ -459,7 +459,7 @@ fn to_output_relative_path(path: &Path) -> PathBuf { /// Saves certain environment variables into the script. We only propagate environment variables /// that are known to be used for communication between the compiler and say linker plugins. -fn write_env(out: &mut BufWriter<&mut std::fs::File>, args: &Args) -> Result { +fn write_env(out: &mut BufWriter<&mut std::fs::File>, args: &Args) -> Result { for var in &["COLLECT_GCC", "COLLECT_GCC_OPTIONS"] { if let Ok(mut value) = std::env::var(var) { // COLLECT_GCC_OPTIONS has things like "-o /path/to/output-file" in it. Update these so diff --git a/libwild/src/subprocess.rs b/libwild/src/subprocess.rs index 61ed90e48..b2d2bc75c 100644 --- a/libwild/src/subprocess.rs +++ b/libwild/src/subprocess.rs @@ -20,7 +20,7 @@ use std::ffi::c_void; /// # Safety /// Must not be called once threads have been spawned. Calling this function from main is generally /// the best way to ensure this. -pub unsafe fn run_in_subprocess(args: Args) -> ! { +pub unsafe fn run_in_subprocess(args: Args) -> ! { let exit_code = match subprocess_result(args) { Ok(code) => code, Err(error) => crate::error::report_error_and_exit(&error), @@ -28,7 +28,7 @@ pub unsafe fn run_in_subprocess(args: Args) -> ! { std::process::exit(exit_code); } -fn subprocess_result(args: Args) -> Result { +fn subprocess_result(args: Args) -> Result { let mut fds: [c_int; 2] = [0; 2]; // create the pipe used to communicate between the parent and child processes - exit on failure make_pipe(&mut fds).context("make_pipe")?; diff --git a/wild/src/main.rs b/wild/src/main.rs index 4095d4610..8124e9b73 100644 --- a/wild/src/main.rs +++ b/wild/src/main.rs @@ -18,13 +18,28 @@ fn run() -> libwild::error::Result { libwild::init_timing()?; - let args = libwild::Args::parse(|| std::env::args().skip(1))?; + let args = libwild::Args::parse(|| std::env::args())?; - if args.should_fork() { - // Safety: We haven't spawned any threads yet. - unsafe { libwild::run_in_subprocess(args) }; - } else { - // Run the linker in this process without forking. - libwild::run(args) + fn runner(args: libwild::Args) -> libwild::error::Result<()> { + if args.should_fork { + // Safety: We haven't spawned any threads yet. + unsafe { libwild::run_in_subprocess(args) }; + } else { + // Run the linker in this process without forking. + libwild::run(args) + } + } + + match args.target_args { + libwild::args::TargetArgs::Elf(_) => { + let args = args.map_target(|a| match a { + libwild::args::TargetArgs::Elf(elf_args) => elf_args, + _ => unreachable!(), + }); + runner(args) + }, + libwild::args::TargetArgs::Pe(_) => { + panic!("PE linking is not supported yet"); + }, } }