LD.LLD(1)

πŸ’– NAME πŸ’–

ld.lld β€” the ELF linker from the LLVM project ✨


πŸŽ€ SYNOPSIS πŸŽ€

ld.lld [options] objfile ...

πŸ¦‹ DESCRIPTION πŸ¦‹

okay so bestie, imagine you've got a whole pile of object files, archives and libraries just sitting there like, completely unlinked, tragic, no cap πŸ’€. A linker takes all of them and combines them into one single output file β€” could be an executable, could be a shared library, could be another object file, whatever you're feeling that day. It relocates code and data from the input files and resolves the symbol references between them. That's the whole job. Iconic behaviour honestly. πŸ‘‘

ld.lld is a drop-in replacement for the GNU BFD and gold linkers. It accepts most of the same command line arguments and linker scripts as GNU linkers. So if you're switching over, lowkey everything you already know still works. πŸ’…

ld.lld currently supports i386, x86-64, ARM, AArch64, LoongArch, PowerPC32, PowerPC64, MIPS32, MIPS64, RISC-V, AMDGPU, Hexagon and SPARC V9 targets. And get this β€” ld.lld acts as a Microsoft link.exe-compatible linker if invoked as lld-link, and as macOS's ld if invoked as ld.ld64. Not me having main character energy in every flavour 😭🫢

All these targets are always supported however ld.lld was built, so you can always use ld.lld as a native linker as well as a cross linker. fr fr, no build-config drama.

✨ OPTIONS ✨

So many of these have both a single-letter and a long form, which is giving "why not both" energy. Here's the tea on the long forms: when you use a long form option other than those beginning with the letter o, you may specify it using either one or two dashes preceding the option name. But long options beginning with o require two dashes, to avoid confusion with the -o path option. iykyk πŸ’…

  • --allow-multiple-definition β€” Do not error if a symbol is defined multiple times. The first definition will be used.
  • --allow-shlib-undefined β€” Allow unresolved references in shared libraries. This option is enabled by default when linking a shared library.
  • --apply-dynamic-relocs β€” Apply link-time values for dynamic relocations.
  • --as-needed β€” Only set DT_NEEDED for shared libraries if used.
  • --auxiliary=value β€” Set the DT_AUXILIARY field to the specified name.
  • --Bdynamic, --dy β€” Link against shared libraries.
  • --Bstatic, --static, --dn β€” Do not link against shared libraries.
  • -Bno-symbolic β€” Don't bind default visibility defined symbols locally for -shared (default).
  • -Bsymbolic β€” Bind default visibility defined symbols locally for -shared. Also set the DF_SYMBOLIC flag.
  • -Bsymbolic-non-weak β€” Bind default visibility defined STB_GLOBAL symbols locally for -shared.
  • -Bsymbolic-functions β€” Bind default visibility defined function symbols locally for -shared.
  • -Bsymbolic-non-weak-functions β€” Bind default visibility defined STB_GLOBAL function symbols locally for -shared.
  • --be8 β€” Write a Big Endian ELF File using BE8 format (AArch32 only).
  • --branch-to-branch β€” Enable the branch-to-branch optimizations: a branch whose target is another branch instruction is rewritten to point to the latter branch target (AArch64 and X86_64 only). Enabled by default at -O2.
  • --build-id=value β€” Generate a build ID note. value may be one of fast, md5, sha1, tree, uuid, 0xhex-string, and none. tree is an alias for sha1. Build-IDs of type fast, md5, sha1, and tree are calculated from the object contents. fast is not intended to be cryptographically secure.
  • --build-id β€” Synonym for --build-id=sha1.
  • --call-graph-profile-sort=algorithm β€” algorithm may be:
    • none β€” Ignore call graph profile.
    • hfsort β€” Use hfsort.
    • cdsort β€” Use cdsort (default).
  • --color-diagnostics=value β€” Use colors in diagnostics. value may be one of always, auto, and never. auto enables color if and only if output is to a terminal.
  • --color-diagnostics β€” Alias for --color-diagnostics=auto.
  • --compress-debug-sections=value β€” Compress DWARF debug sections. The sections remain uncompressed if compressed content would be larger. value may be
    • none β€” No compression.
    • zlib β€” The default compression level is 1 (fastest) as the debug info usually compresses well at that level.
    • zstd β€” Use the default compression level in zstd.
  • --compress-sections=section-glob={none,zlib,zstd}[:level] β€” Compress output sections that match the glob and do not have the SHF_ALLOC flag. The matched sections remain uncompressed if compressed content would be larger. The compression level is level (if specified) or a default speed-focused level. This is like a generalized --compress-debug-sections. It's giving "but make it customizable" πŸŽ€
  • --cref β€” Output cross reference table. If -Map is specified, print to the map file.
  • --debug-names β€” Generate a merged .debug_names section.
  • --default-script=file, -dT file β€” In the absence of --script, read this default linker script.
  • --defsym=symbol=expression β€” Define a symbol alias. expression may be another symbol or a linker script expression. For example, --defsym=foo=bar or --defsym=foo=bar+0x100.
  • --demangle β€” Demangle symbol names. (because reading mangled names is not the vibe πŸ™ˆ)
  • --disable-new-dtags β€” Disable new dynamic tags.
  • --discard-all, -x β€” Delete all local symbols.
  • --discard-locals, -X β€” Delete temporary local symbols.
  • --discard-none β€” Keep all symbols in the symbol table.
  • --dynamic-linker=value β€” Specify the dynamic linker to be used for a dynamically linked executable. This is recorded in an ELF segment of type PT_INTERP.
  • --dynamic-list=file β€” Similar to --export-dynamic-symbol-list. When creating a shared object, implies -Bsymbolic but does not set DF_SYMBOLIC.
  • --EB β€” Select the big-endian format in the OUTPUT_FORMAT command.
  • --EL β€” Select the little-endian format in the OUTPUT_FORMAT command.
  • --eh-frame-hdr β€” Request creation of .eh_frame_hdr section and PT_GNU_EH_FRAME segment header.
  • --emit-relocs, -q β€” Generate relocations in the output.
  • --enable-new-dtags β€” Enable new dynamic tags.
  • --enable-non-contiguous-regions β€” Spill input sections to later matching output sections to avoid memory region overflow.
  • --end-lib β€” End a grouping of objects that should be treated as if they were together in an archive.
  • --entry=entry β€” Name of entry point symbol.
  • --error-limit=value β€” Maximum number of errors to emit before stopping. A value of zero indicates that there is no limit.
  • --error-unresolved-symbols β€” Report unresolved symbols as errors.
  • --error-handing-script=script_path β€” Call script script_path upon some error, with tag as first argument, and an extra parameter as second argument. The script is expected to return 0 on success. Any other value is considered a generic error. tag may be missing-lib followed by the name of the missing library. undefined-symbol followed by the name of the undefined symbol.
  • --execute-only β€” Mark executable sections unreadable. This option is currently only supported on AArch64.
  • --exclude-libs=value β€” Exclude static libraries from automatic export.
  • --export-dynamic, -E β€” Put symbols in the dynamic symbol table.
  • --export-dynamic-symbol=glob β€” (executable) Put matched non-local defined symbols to the dynamic symbol table. (shared object) References to matched non-local STV_DEFAULT symbols shouldn't be bound to definitions within the shared object even if they would otherwise be due to -Bsymbolic , -Bsymbolic-functions or --dynamic-list.
  • --export-dynamic-symbol-list=file β€” Read a list of dynamic symbol patterns from file. Apply --export-dynamic-symbol on each pattern.
  • --fatal-warnings β€” Treat warnings as errors. No more "it's just a warning bestie" 😭
  • --filter=value, -F value β€” Set the DT_FILTER field to the specified value.
  • --fini=symbol β€” Specify a finalizer function.
  • --force-group-allocation β€” Only meaningful for -r. Section groups are discarded. If two section group members are placed to the same output section, combine their relocations as well.
  • --format=input-format, -b input-format β€” Specify the format of the inputs following this option. input-format may be one of binary, elf, and default. default is a synonym for elf.
  • --gc-sections β€” Enable garbage collection of unused sections. bye bye unused sections, you will not be missed πŸ—‘οΈβœ¨
  • --gdb-index β€” Generate .gdb_index section.
  • --hash-style=value β€” Specify hash style. value may be sysv, gnu, or both. both is the default.
  • --help β€” Print a help message.
  • --icf=all β€” Enable identical code folding.
  • --icf=safe β€” Enable safe identical code folding.
  • --icf=none β€” Disable identical code folding.
  • --ignore-data-address-equality β€” Ignore address equality of data. C/C++ requires each data to have a unique address. This option allows lld to do unsafe optimization that breaks the requirement: create copies of read-only data or merge two or more read-only data that happen to have the same value.
⚠️ okay this one is literally called unsafe in the description, so like, read the whole thing before you go turning it on bestie.
  • --ignore-function-address-equality β€” Ignore address equality of functions. This option allows non-PIC calls to a function with non-default visibility in a shared object. The function may have different addresses within the executable and within the shared object.
  • --image-base=value β€” Set the base address to value.
  • --init=symbol β€” Specify an initializer function.
  • --keep-unique=symbol β€” Do not fold symbol during ICF.
  • -l libName, --library=libName β€” Root name of library to use.
  • -L dir, --library-path=dir β€” Add a directory to the library search path.
  • --lto-aa-pipeline=value β€” AA pipeline to run during LTO. Used in conjunction with --lto-newpm-passes.
  • --lto-newpm-passes=value β€” Passes to run during LTO.
  • --lto-Oopt-level β€” Optimization level for LTO.
  • --lto-partitions=value β€” Number of LTO codegen partitions.
  • -m value β€” Set target emulation.
  • --Map=file, -M file β€” Print a link map to file.
  • --nmagic, -n β€” Do not page align sections, link against static libraries.
  • --no-allow-shlib-undefined β€” Do not allow unresolved references in shared libraries. This option is enabled by default when linking an executable.
  • --no-as-needed β€” Always set DT_NEEDED for shared libraries.

🌸 more parts coming, bestie β€” don't go anywhere 🌸
  • --no-color-diagnostics β€” pls no rainbow in my error messages, bestie. Diagnostics come out plain. 🚫🌈
  • --no-demangle β€” leave those symbol names in their raw, un-glammed-up form. No name decoding. πŸ’€
  • --no-dynamic-linker β€” don't emit an .interp section at all. Like, that whole "which loader am I" thing? Skipped. ✨
  • --no-fortran-common β€” stop digging through archive members looking for definitions to override COMMON symbols. Not today. πŸ™…
  • --no-gc-sections β€” turn OFF garbage collection of unused sections. Nothing gets yeeted. Everything stays, even the flops. πŸ’…
  • --no-gnu-unique β€” disable STB_GNU_UNIQUE symbol binding. Yeah, that one. It's off now. πŸ¦‹
  • --no-merge-exidx-entries β€” stop merging .ARM.exidx entries. They stay separate, individually iconic. ✨
  • --no-nmagic β€” page align sections. That's the whole vibe. πŸ“
  • --no-omagic β€” do NOT make the text/data sections writable, but DO page align sections. πŸŽ€
  • --no-relax β€” disable target-specific relaxations. Lowkey important detail: for x86-64 this turns off the R_X86_64_GOTPCRELX and R_X86_64_REX_GOTPCRELX GOT optimization. No chill, no relaxing. 😀
  • --no-rosegment β€” don't give read-only non-executable sections their own separate segment. They can share, it's fine. 🫢
  • --undefined-version β€” don't come for version scripts that reference undefined symbols. Let them live. πŸ™ˆ
  • --no-undefined β€” report unresolved symbols even when the linker is out here building a shared library. Yes, even then. Snitches get... reported. πŸ”₯
  • --no-warn-mismatch β€” unknown section types? Not rejected. We move. πŸ’…
  • --no-warn-symbol-ordering β€” don't nag about problems with the symbol ordering file or the call graph profile. Silence is golden. ✨
  • --no-warnings, -w β€” suppress warnings AND cancel --fatal-warnings. Full mute mode. 🀫
  • --no-whole-archive β€” restores the default behavior of loading archive members. The classic, the original recipe. πŸŽ€
  • --no-pie, --no-pic-executable β€” do not create a position independent executable. Fixed-address era. πŸ“
  • --noinhibit-exec β€” keep the executable output file whenever it's still usable, even if stuff went sideways. Salvage the queen. πŸ‘‘
  • --nostdlib β€” only search the directories you named on the command line. Nowhere else. Rude but efficient. πŸ’…
  • -o path β€” write the output executable, library, or object to path. No path given? Then a.out is the default, periodt. πŸ“
  • -Ovalue β€” optimize the output file. value can be:
    • 0 β€” disable string merging.
    • 1 β€” enable string merging.
    • 2 β€” enable string tail merging and branch-to-branch optimization.

-O1 is the default. So if you don't say anything, you get the level-1 slay. πŸ”₯

  • --oformat=format β€” pick the format for the output object file. The ONLY supported format is binary, which gives you output with no ELF header at all. Raw. Unfiltered. πŸ’€
  • --omagic, -N β€” set the text and data sections to be readable AND writable, do NOT page align sections, and link against static libraries. Chaotic good energy. πŸŒ€
  • --opt-remarks-filename file β€” write optimization remarks in YAML format to file. πŸ“„
  • --opt-remarks-passes pass-regex β€” filter those optimization remarks by only allowing the passes matching pass-regex. Picky, and we love that. 🎯
  • --opt-remarks-with-hotness β€” include hotness info in the optimization remarks file. Spicy metrics. πŸ”₯
  • --orphan-handling=mode β€” control how orphan sections get handled. An orphan section is one that's not specifically mentioned in a linker script. (Yes, they exist, and they need a home.) mode can be:
    • place β€” place orphan sections in suitable output sections.
    • warn β€” place orphan sections as for place, and also report a warning.
    • error β€” place orphan sections as for place, and also report an error.

place is the default. 🏠

  • --pack-dyn-relocs=format β€” pack dynamic relocations in the given format. format can be:
    • none β€” do not pack. Dynamic relocations are encoded in SHT_REL(A).
    • android β€” pack dynamic relocations in SHT_ANDROID_REL(A).
    • relr β€” pack relative relocations in SHT_RELR, and the rest of dynamic relocations in SHT_REL(A).
    • android+relr β€” pack relative relocations in SHT_RELR, and the rest of dynamic relocations in SHT_ANDROID_REL(A).

none is the default. Also important: if --use-android-relr-tags is specified, use SHT_ANDROID_RELR instead of SHT_RELR. 🧚

  • --package-metadata β€” emit a percent-encoded string to the .note.package section. Example so you get it: %25 decodes to a single %. ✨
  • --pic-veneer β€” always generate position independent thunks. No exceptions, bestie. πŸ’…
  • --pie, --pic-executable β€” create a position independent executable. It's giving modern. 🌈
  • --power10-stubs=mode β€” whether to use Power10 instructions in call stubs for R_PPC64_REL24_NOTOC and TOC/NOTOC interworking. mode can be:
    • yes β€” (default) Use.
    • auto β€” currently the same as yes.
    • no β€” don't use.

πŸ‘‘

  • --print-gc-sections β€” list the removed unused sections. Show me what got cut. βœ‚οΈ
  • --print-gc-sections=file β€” same energy, but list the removed unused sections to the specified file. πŸ“
  • --print-icf-sections β€” list identical folded sections. Twin reveal. πŸ‘―
  • --print-map β€” print a link map to the standard output. πŸ—ΊοΈ
  • --print-archive-stats=file β€” write archive usage statistics to the specified file. Prints the numbers of members and fetched members for each archive. Receipts, basically. 🧾
  • --push-state β€” save the current state of --as-needed, --static, and --whole-archive. Bookmark it. πŸ”–
  • --pop-state β€” restore the states saved by --push-state. And we're back. βͺ
  • --randomize-section-padding=seed β€” randomly insert padding between input sections and at the start of each segment using the given seed. Padding goes into output sections whose names match these patterns: .bss, .data, .data.rel.ro, .lbss, .ldata, .lrodata, .ltext, .rodata and .text*. Chaos, but make it deterministic. 🎲
  • ---relax-gp β€” enable global pointer relaxation for RISC-V. (Yes, that's THREE dashes. Type it exactly, bestie, I'm not playing.) 🌸
  • --relocatable, -r β€” create a relocatable object file. 🧩
  • --remap-inputs=from-glob=to-file β€” input files matching from-glob get mapped to to-file. Want to ignore an input file entirely? Use /dev/null. Bye. πŸ’€
  • --remap-inputs-file=file β€” remap input files based on patterns in file. Each line in the remap file is of the format from-glob=to-file, or a comment starting with #. πŸ“
  • --reproduce=path β€” write a tar file to path, containing all the input files needed to reproduce the link, a text file called response.txt with the command line options, and a text file called version.txt containing the output of ld.lld --version. Once unpacked, that archive can re-run the linker with the same options and input files. It's a time capsule. πŸ•°οΈβœ¨
  • --retain-symbols-file=file β€” retain ONLY the symbols listed in file. Everyone else? Gone. πŸ‘‘
  • --rpath=value, -R value β€” add a DT_RUNPATH to the output. πŸ›£οΈ
  • --rsp-quoting=value β€” quoting style for response files. The supported *value*s are windows and posix. πŸͺŸπŸ§
  • --script=file, -T file β€” read the linker script from file. If multiple linker scripts are given, they're processed as if they were concatenated in the order they appeared on the command line. Order matters, always. πŸ“œ
  • --section-start=section=address β€” set the address of section. πŸ“
  • --shared, --Bsharable β€” build a shared object. (Yes, it's spelled --Bsharable. Copy it exactly, I beg.) 🫢
  • --shuffle-sections=seed β€” shuffle matched sections using the given seed before mapping them to the output sections. If -1, reverse the section order. If 0, use a random seed. 🎰
  • --soname=value, -h value β€” set DT_SONAME to value. Name your child. πŸ’–
  • --sort-common β€” this option is ignored for GNU compatibility. Yeah. It just sits there. It does nothing and I respect it. πŸ’€
  • --sort-section=value β€” specifies the sections sorting rule when a linkerscript is used. πŸ—‚οΈ
  • --start-lib β€” start a grouping of objects that should be treated as if they were together in an archive. Squad formation. πŸ§‘β€πŸ€β€πŸ§‘
  • --strip-all, -s β€” strip ALL symbols. Implies --strip-debug. Nothing left but vibes. βœ‚οΈβœ¨
  • --strip-debug, -S β€” strip debugging information. The bug-hunting breadcrumbs? Deleted. πŸ›βž‘οΈπŸš«
  • --symbol-ordering-file=file β€” lay out sections in the order specified by file. Lineup, in formation. πŸ“‹
  • --sysroot=value β€” set the system root. 🌳
  • --target1-abs β€” interpret R_ARM_TARGET1 as R_ARM_ABS32. 🎯
  • --target1-rel β€” interpret R_ARM_TARGET1 as R_ARM_REL32. 🎯
  • --target2=type β€” interpret R_ARM_TARGET2 as type, where type is one of rel, abs, or got-rel. πŸŽ€
  • --Tbss=value β€” same as --section-start with .bss as the sectionname. 🫧
  • --Tdata=value β€” same as --section-start with .data as the sectionname. πŸ“Š
  • --Ttext=value β€” same as --section-start with .text as the sectionname. πŸ“
  • --thinlto-cache-dir=value β€” path to the ThinLTO cached object file directory. πŸ—„οΈ
  • --thinlto-cache-policy=value β€” pruning policy for the ThinLTO cache. Spring cleaning, but for object files. 🧹
  • --thinlto-jobs=value β€” number of ThinLTO jobs. πŸ‘·
  • --threads=N β€” number of threads. all (the default) means all of the concurrent threads supported. 1 disables multi-threading. Go forth and parallelize. πŸƒβ€β™€οΈπŸ’¨
  • --fat-lto-objects β€” use the .llvm.lto section, which contains LLVM bitcode, in fat LTO object files to perform LTO. πŸ”
  • --no-fat-lto-objects β€” ignore the .llvm.lto section in relocatable object files (default). πŸš«πŸ”
  • --time-trace β€” record time trace. Where did the seconds go? Now you'll know. ⏱️
  • --time-trace-file=file β€” write time trace output to file. πŸ“„
  • --time-trace-granularity=value β€” minimum time granularity (in microseconds) traced by the time profiler. 🧐
  • --trace β€” print the names of the input files. Narration mode: ON. πŸ—£οΈ
  • --trace-symbol=symbol, -y symbol β€” trace references to symbol. Following it everywhere like a detective. πŸ”
  • --undefined=symbol, -u symbol β€” if symbol is not defined after symbol resolution, and there's a static library that contains an object file defining the symbol, load the member to include that object file in the output file. Summoned from the archives. πŸ“šβœ¨
  • --undefined-glob=pattern β€” synonym for --undefined, except it takes a glob pattern. In a glob pattern, * matches zero or more characters, ? matches any single character, and [...] matches the characters within brackets. All symbols that match a given pattern are handled as if they were given as arguments of --undefined. Fancy. πŸŽ€
  • --unique β€” creates a separate output section for EVERY orphan input section. Everyone gets their own room. πŸšͺ
  • --unresolved-symbols=value β€” determine how to handle unresolved symbols. value may be:
    • report-all β€” report unresolved symbols (default).
    • ignore-in-object-files β€” only report unresolved symbols contained in shared libraries.
    • ignore-in-shared-libs β€” only report unresolved symbols contained in object files.
    • ignore-all β€” do not report unresolved symbols.

Pick your level of awareness, bestie. πŸ‘€

  • --use-android-relr-tags β€” use SHT_ANDROID_RELR / DT_ANDROID_RELR* tags instead of SHT_RELR / DT_RELR*. 🧚✨

  • -v, -V β€” shows the version number, and then keeps going with the linking if you actually passed it some object files πŸ’…
  • --version β€” shows the version number and dips. That's it, that's the whole job πŸ‘‘
  • --verbose β€” verbose mode, bestie. It tells you stuff. Lots of stuff. ✨
  • --version-script=file β€” reads the version script in from file πŸ“œ
  • --warn-backrefs β€” yells at you about reverse or cyclic dependencies to or between static archives. Lowkey a vibe-check for your link order β€” use it if you want your linker invocation to stay compatible with traditional Unix-like linkers 🫢
  • --warn-backrefs-exclude=glob β€” a glob describing an archive (or an object file inside --start-lib) that should be ignored for --warn-backrefs. Like a do-not-disturb sign for one specific archive πŸ™ˆ
  • --warn-common β€” warns you when common symbols are duplicated πŸ‘―
  • --warn-ifunc-textrel β€” warns about using ifunc symbols together with text relocations. So here's the lore: older versions of the glibc library (2.28 and earlier) have a bug that makes the segment containing ifunc symbols get marked as not executable once they're relocated. Meaning: the program compiles and links just fine, and then gives you a segmentation fault the moment the instruction pointer reaches an ifunc symbol 😭 Use -warn-ifunc-textrel to let lld give you a warning if your code may include ifunc symbols, may do text relocations, and is getting linked against an older glibc version. Otherwise there's genuinely no need for it, because the default value does not give a warning. Heads up: this flag showed up in late 2018, has no counterpart in the ld and gold linkers, and may be removed in the future πŸ’€
  • --warn-unresolved-symbols β€” unresolved symbols get reported as warnings instead of errors πŸ“’
  • --whole-archive β€” force-loads every member of a static library. No skips, no cuts, the whole archive eats 🍽️
  • --why-extract=file β€” prints out, into a file, why archive members are getting extracted. Receipts, basically 🧾
  • --why-live=glob β€” reports a chain of references that's preventing garbage collection, for every symbol matching the glob ⛓️
  • --wrap=symbol β€” redirects references to symbol over to __wrap_symbol, and references to __real_symbol over to symbol. Iconic little switcheroo πŸ”„
  • -z option β€” linker option extensions. The main-character list of extras:
    • dead-reloc-in-nonalloc=section_glob=value β€” resolves a relocation in a matched non-SHF_ALLOC section that references a discarded symbol, replacing it with value. It accepts globs, and if a section matches more than one option, the last option takes precedence. An order of least specific to most specific match is recommended 🌸
    • execstack β€” makes the main stack executable. Stack permissions get recorded in the PT_GNU_STACK segment.
    • bti-report=[none|warning|error] β€” specifies how to report the missing GNU_PROPERTY_AARCH64_FEATURE_1_BTI property. none is the default, so the linker won't report the missing property at all; otherwise it gets reported as a warning or an error.
    • cet-report=[none|warning|error] β€” specifies how to report the missing GNU_PROPERTY_X86_FEATURE_1_IBT or GNU_PROPERTY_X86_FEATURE_1_SHSTK properties. none is the default, so the linker won't report the missing property; otherwise it's reported as a warning or an error.
    • gcs-report=[none|warning|error] β€” specifies how to report the missing GNU_PROPERTY_AARCH64_FEATURE_1_GCS property. GNU_PROPERTY_AARCH64_FEATURE_1_GCS indicates object file support for the Guarded Control Stack security feature. none is the default, so the linker won't report the missing property; otherwise it's reported as a warning or an error.
    • dynamic-undefined-weak β€” makes undefined weak symbols dynamic when the dynamic symbol table is present, if they're referenced from relocatable object files and not forced local by symbol visibility or versioning. They don't get made dynamic when nodynamic-undefined-weak is specified. dynamic-undefined-weak is the default when building a shared object, or when an input shared object is present.
    • pauth-report=[none|warning|error] β€” specifies how to report the missing GNU_PROPERTY_AARCH64_FEATURE_PAUTH property. none is the default, so the linker won't report the missing property; otherwise it's reported as a warning or an error.
    • force-bti β€” force-enables the AArch64 BTI instruction in the PLT, and warns if an input ELF file doesn't have the GNU_PROPERTY_AARCH64_FEATURE_1_BTI property.
    • force-ibt β€” force-enables Intel Indirect Branch Tracking in the PLT, and warns if an input ELF file doesn't have the GNU_PROPERTY_X86_FEATURE_1_IBT property.
    • global β€” sets the DF_1_GLOBAL flag in the DYNAMIC section. Different loaders can decide how to handle this flag on their own 🀷
    • ifunc-noplt β€” doesn't emit PLT entries for ifunc symbols. Instead it emits text relocations referencing the resolver. This is an experimental optimization and only suitable for standalone environments where text relocations don't have the usual drawbacks. It must be combined with the -z notext option ⚠️
    • initfirst β€” sets the DF_1_INITFIRST flag to indicate the module should be initialized first πŸ₯‡
    • interpose β€” sets the DF_1_INTERPOSE flag to tell the runtime linker that the object is an interposer. During symbol resolution, interposers get searched after the application but before other dependencies.
    • lrodata-after-bss β€” places .lrodata after .bss.
    • muldefs β€” doesn't error out if a symbol is defined multiple times; the first definition gets used. This is a synonym for --allow-multiple-definition.
    • nocombreloc β€” disables combining and sorting multiple relocation sections.
    • nocopyreloc β€” disables the creation of copy relocations.
    • nodefaultlib β€” sets the DF_1_NODEFLIB flag to indicate that default library search paths should be ignored.
    • nodelete β€” sets the DF_1_NODELETE flag to indicate that the object cannot be unloaded from a process.
    • nodlopen β€” sets the DF_1_NOOPEN flag to indicate that the object may not be opened by dlopen(3).
    • nognustack β€” doesn't emit the PT_GNU_STACK segment.
    • norelro β€” doesn't indicate that portions of the object should be mapped read-only after initial relocation processing. The object will omit the PT_GNU_RELRO segment.
    • nosectionheader β€” doesn't generate the section header table.
    • notext β€” allows relocations against read-only segments. Sets the DT_TEXTREL flag in the DYNAMIC section.
    • now β€” sets the DF_BIND_NOW flag to indicate that the run-time loader should perform all relocation processing as part of object initialization. By default, relocations may be performed on demand.
    • origin β€” sets the DF_ORIGIN flag to indicate that the object requires $ORIGIN processing.
    • pac-plt β€” AArch64 only, uses pointer authentication in the PLT.
    • pack-relative-relocs β€” similar to -pack-dyn-relocs=relr, except it synthesizes the GLIBC_ABI_DT_RELR version dependency if there's a GLIBC_2.* version dependency. glibc's ld.so rejects loading a dynamically linked object that doesn't have the GLIBC_ABI_DT_RELR version dependency.
    • rel β€” uses the REL format for dynamic relocations.
    • rela β€” uses the RELA format for dynamic relocations.
    • retpolineplt β€” emits retpoline format PLT entries as a mitigation for CVE-2017-5715 πŸ›‘οΈ
    • rodynamic β€” makes the .dynamic section read-only. The DT_DEBUG tag will not be emitted.
    • separate-loadable-segments, separate-code, noseparate-code β€” these three specify whether two adjacent PT_LOAD segments are allowed to overlap in pages. noseparate-code (the default) allows overlap. separate-code allows overlap between two executable segments, or between two non-executable segments. separate-loadable-segments disallows overlap entirely.
    • shstk β€” x86 only, uses shadow stack.
    • gcs=[implicit|never|always] β€” specifies how the GNU_PROPERTY_AARCH64_FEATURE_1_GCS bit is set in the output ELF file. When set, it indicates support for the Guarded Control Stack (GCS) security feature. implicit (the default): the GCS feature bit is set if all input relocatable files have a .note.gnu.property section containing the GNU_PROPERTY_AARCH64_FEATURE_1_GCS bit. never: clears the GCS feature bit regardless of input relocatable files' markings. always: sets the GCS feature bit regardless of input object markings.
    • stack-size=size β€” sets the main thread's stack size to size. The stack size is recorded as the size of the PT_GNU_STACK program segment.
    • start-stop-gc β€” doesn't let __start_/__stop_ references retain the associated C identifier name sections (this is the default).
    • nostart-stop-gc β€” lets __start_/__stop_ references retain the associated C identifier name sections.
    • text β€” doesn't allow relocations against read-only segments. This is the default.
    • nobtcfi β€” creates a PT_OPENBSD_NOBTCFI segment.
    • wxneeded β€” creates a PT_OPENBSD_WXNEEDED segment.

🌸 ENVIRONMENT VARIABLES 🌸

okay bestie these are the env vars ld.lld actually looks at, and there are literally only two, so this is a chill section πŸ’–

  • LLD_REPRODUCE β€” set this and ld.lld will make a reproduce tarball with whatever filename you gave it. and yes, the lore matters here: if you also passed --reproduce on the command line, then --reproduce wins and takes precedence, no cap. so the flag beats the env var, periodt. πŸ‘‘
  • LLD_VERSION β€” so ld.lld creates a section named .comment that contains the LLD version string, right? and this env var can override that string. why would you even want that? because it's useful to eliminate differences in the binary caused by LLD version number differences β€” like when you're diffing two builds and the only thing wigging out is the version number 🫠. set LLD_VERSION and boom, those diffs are gone.

πŸ¦‹ IMPLEMENTATION NOTES πŸ¦‹

okay so this is the part where ld.lld lowkey does its own thing and you need to know about it before you come for it. sit down, grab a snack πŸ“

πŸ’… how ld.lld treats archives (the whole vibe)

ld.lld's handing of archive files (those with a .a file extension) is different from traditional linkers used on Unix-like systems.

so let's talk about how the traditional girlies do it, because it's a whole different energy:

Traditional linkers maintain a set of undefined symbols during linking. The linker processes each file in the order in which it appears on the command line, until the set of undefined symbols becomes empty. An object file is linked into the output object when it is encountered, with its undefined symbols added to the set. Upon encountering an archive file a traditional linker searches the objects contained therein, and processes those that satisfy symbols in the unresolved set.

translation bestie: it's one pass, in order, first-come-first-served and if your symbol's already been passed, sorry babe, too late 😭

πŸ˜΅πŸ’« the mutually dependent archive problem

Handling mutually dependent archives may be awkward when using a traditional linker. Archive files may have to be specified multiple times, or the special command line options --start-group and --end-group may be used to have the linker loop over the files in the group until no new symbols are added to the set.

yeah. you literally have to list the same archive over and over, OR wrap it all in --start-group / --end-group so the linker just keeps looping over the files in the group until no new symbols are added to the set. it's giving "just keep trying until it works" πŸ™ˆ iconic behavior honestly

✨ how ld.lld does it instead

ld.lld records all symbols found in objects and archives as it iterates over command line arguments. When ld.lld encounters an undefined symbol that can be resolved by an object file contained in a previously processed archive file, it immediately extracts and links it into the output object.

okay so the glow-up here is that it remembers everything as it iterates over command line arguments. and then β€” plot twist β€” when it hits an undefined symbol that could be resolved by an object file inside an archive it already processed? it goes back and immediately extracts and links it into the output object. like it never even left 🧚✨. no --start-group gymnastics required.

🫢 the honest disclaimer

With certain archive inputs ld.lld may produce different results compared to traditional linkers. In practice, large bodies of third party software have been linked with ld.lld without material issues.

so like, yes, with certain archive inputs it may produce different results compared to traditional linkers. but fr fr β€” in practice, large bodies of third party software have been linked with ld.lld without material issues. so it's fine. it's slaying even πŸ’–

if you're nervous about whether your linker invocation might be incompatible with traditional Unix-like linker behavior, the --warn-backrefs option may be used to identify a linker invocation that may be incompatible with traditional Unix-like linker behavior. it's literally built for exactly this vibe check πŸ”

and that's the whole page, bestie!! πŸŽ€βœ¨ this one came from the Debian crew, dated Jul 25, 2023 β€” so if you're citing it, that's the source and the date, no cap πŸ‘‘πŸ’–. now go link something iconic. mwah πŸ’…πŸŒˆ

πŸ“¦ where this page comes from