LD(1)

๐Ÿ“ฆ version 2.45, in ubuntu questing

โœจ LD(1) โœจ

okay bestie, this is the manual page for ld, the GNU linker ๐Ÿฆ‹ It's from the GNU Development Tools crew, so that's the manual name and section (1) ๐Ÿ’…

๐Ÿ’– NAME ๐Ÿ’–

ld โ€” the GNU linker ๐Ÿ‘‘

๐Ÿงš SYNOPSIS ๐Ÿงš

ld [options] objfile ...

๐ŸŒธ DESCRIPTION ๐ŸŒธ

okay so ld is the friend who combines a bunch of object and archive files, relocates their data, and ties up symbol references โœจ Usually the very last step when you compile a program is running ld ๐Ÿ”ฅ

ld also takes Linker Command Language files, written in a superset of AT&T's Link Editor Command Language syntax ๐Ÿ’œ This gives you explicit and total control over the linking process. literally the whole vibe is control ๐Ÿ˜ญ

ngl this page does not cover the command language ๐Ÿ™ˆ For the full tea on the command language and everything else about the GNU linker, check the ld entry in "info" ๐Ÿ“–

This version of ld runs on the general purpose BFD libraries to work with object files ๐ŸŒˆ That means it can read, combine, and write object files in many different formats, like COFF or "a.out". Different formats can be linked together to make any available kind of object file ๐ŸŽ€

Besides being super flexible, the GNU linker is way more helpful than other linkers when it comes to diagnostic information ๐Ÿ’… A lot of linkers just quit the second they hit an error. Whenever possible, ld keeps going so you can find other errors (or, in some cases, still get an output file even with the error) ๐Ÿซถ

The GNU linker ld is built to cover a broad range of situations and to be as compatible as possible with other linkers โœจ So you get a bunch of choices to control how it behaves ๐Ÿ“

๐ŸŽ€ OPTIONS ๐ŸŽ€

okay the linker has a plethora of command-line options, but in real life you'll only touch a few of them in any given situation ๐Ÿ’– For example, a super common move is linking standard Unix object files on a standard, supported Unix system. On a system like that, to link a file called "hello.o":

        ld -o <output> /lib/crt0.o hello.o -lc

This tells ld to make a file called output by linking "/lib/crt0.o" with "hello.o" and the library "libc.a", which comes from the standard search directories ๐Ÿฆ‹ (Check the discussion of the -l option below for more on that one ๐Ÿ‘€)

Some of the command-line options to ld can go anywhere on the line โœจ But options that refer to files, like -l or -T, make the file get read at the point where the option shows up on the command line, relative to the object files and other file options ๐Ÿง  Repeating a non-file option with a different argument will either do nothing extra, or override earlier occurrences (the ones further left on the command line) of that option ๐Ÿ’… Options that can be meaningfully given more than once are noted in the descriptions below ๐ŸŒˆ

Non-option arguments are object files or archives you want linked together ๐Ÿซถ They can come after, before, or mixed in with the options, except that an object file argument can't be stuck between an option and its argument ๐Ÿ™ˆ

Usually you invoke the linker with at least one object file, but you can also specify other kinds of binary input files using -l, -R, and the script command language ๐Ÿ’– If you don't specify any binary input files at all, the linker won't produce any output and it'll just say: No input files ๐Ÿ’€

If the linker can't recognize the format of an object file, it'll assume it's a linker script ๐Ÿ‘€ A script passed this way augments the main linker script used for the link (either the default linker script or the one you pick with -T) โœจ This lets you link against a file that looks like an object or an archive but really just defines some symbol values, or uses "INPUT" or "GROUP" to load other objects ๐Ÿฆ‹ Passing a script like this only augments the main linker script, with the extra commands placed after the main script. If you want to replace the default linker script entirely, use the -T option, but keep an eye on the effect of the "INSERT" command โš ๏ธ

โš ๏ธ bestie, the -T option is the one that replaces the default linker script, while passing a script without it only adds to the main one. don't mix those up ๐Ÿ˜ญ

For options with single-letter names, the option argument has to either follow the letter with no whitespace in between, or be given as a separate argument right after the option that needs it ๐Ÿ’œ

For options with multiple-letter names, you can put either one dash or two in front of the name ๐ŸŒธ For example, -trace-symbol and --trace-symbol are equivalent. BUT there's one exception, and it's a big one ๐Ÿ™ˆ Multiple-letter options that start with a lowercase 'o' can only be preceded by two dashes. This cuts down on confusion with the -o option. So -omagic sets the output file name to magic, whereas --omagic sets the NMAGIC flag on the output ๐Ÿšจ

Arguments to multiple-letter options have to be separated from the option name by an equals sign, OR given as separate arguments right after the option that needs them ๐ŸŽ€ For example, --trace-symbol foo and --trace-symbol=foo are equivalent. Unique abbreviations of multiple-letter option names are accepted too โœจ

Note, though: if the linker is being invoked indirectly through a compiler driver (like gcc), then all the linker command-line options should be prefixed with -Wl, (or whatever's appropriate for your particular compiler driver) like this:

          gcc -Wl,--start-group foo.o bar.o -Wl,--end-group

This is super important, because otherwise the compiler driver might silently drop the linker options and you'll get a bad link ๐Ÿ’€ Confusion can also pop up when you pass options that need values through a driver, because a space between the option and its argument acts as a separator. That makes the driver pass only the option to the linker and the argument to the compiler ๐Ÿ˜ญ In this case, the simplest move is to use the joined forms of both single- and multiple-letter options, like so:

          gcc foo.o bar.o -Wl,-eENTRY -Wl,-Map=a.map

Here's a table, well, a list, of the generic command-line switches the GNU linker accepts ๐ŸŒˆ

  • @file okay so this one reads command-line options from file ๐Ÿ“œ The options it reads get inserted in place of the original @file option. If file doesn't exist or can't be read, the option gets treated literally and isn't removed ๐Ÿ™ˆ

Options inside file are separated by whitespace. To include a whitespace character in an option, wrap the whole option in either single or double quotes. You can include any character (including a backslash) by putting a backslash in front of it ๐Ÿ’… The file can also contain more @file options, and those get processed recursively ๐Ÿ”

  • -a keyword this one's here for HP/UX compatibility ๐Ÿฆ‹ The keyword argument has to be one of the strings archive, shared, or default. -aarchive is functionally equivalent to -Bstatic, and the other two keywords are functionally equivalent to -Bdynamic โœจ You can use this option any number of times ๐Ÿ’œ
  • --audit AUDITLIB adds AUDITLIB to the "DT_AUDIT" entry of the dynamic section ๐ŸŽง AUDITLIB doesn't get checked for existence, and it won't use the DT_SONAME specified in the library ๐Ÿ™… If you specify it multiple times, "DT_AUDIT" will hold a colon-separated list of audit interfaces to use. If the linker finds an object with an audit entry while searching for shared libraries, it'll add a matching "DT_DEPAUDIT" entry to the output file ๐ŸŒธ This option only means something on ELF platforms that support the rtld-audit interface ๐Ÿ’…
  • -b input-format, --format=input-format ld can be configured to support more than one kind of object file ๐Ÿงš If yours is set up that way, use -b to specify the binary format for the input object files that follow this option on the command line. Even when ld supports alternative formats, you usually don't need to specify this, since ld should be configured to expect the most usual format on each machine as its default input format ๐Ÿซถ input-format is a text string, the name of a particular format supported by the BFD libraries. (You can list the available binary formats with objdump -i) ๐Ÿ‘€

You might want this if you're linking files with an unusual binary format ๐Ÿฆ‹ You can also use -b to switch formats explicitly (when linking object files of different formats) by putting -b input-format before each group of object files in a particular format ๐Ÿ’–

The default format comes from the environment variable "GNUTARGET" ๐ŸŒˆ

You can also define the input format from a script, using the command "TARGET"; ๐Ÿ“œ

  • -c MRI-commandfile, --mri-script=MRI-commandfile for compatibility with linkers made by MRI, ld accepts script files written in an alternate, restricted command language ๐Ÿ’… That's described in the MRI Compatible Script Files section of the GNU ld documentation ๐Ÿ“– Introduce MRI script files with the option -c, and use the -T option to run linker scripts written in the general-purpose ld scripting language ๐ŸŽ€ If MRI-cmdfile doesn't exist, ld looks for it in the directories specified by any -L options ๐Ÿ”
  • -d, -dc, -dp these three options are equivalent ๐ŸŒธ Multiple forms are supported just for compatibility with other linkers. They assign space to common symbols even if a relocatable output file is specified (with -r) โœจ The script command "FORCE_COMMON_ALLOCATION" has the same effect ๐Ÿ’œ
  • --depaudit AUDITLIB, -P AUDITLIB adds AUDITLIB to the "DT_DEPAUDIT" entry of the dynamic section ๐ŸŽง Same deal as before: AUDITLIB isn't checked for existence, and it won't use the DT_SONAME specified in the library ๐Ÿ™… If you specify it multiple times, "DT_DEPAUDIT" will hold a colon-separated list of audit interfaces to use ๐ŸŒˆ This option only means something on ELF platforms that support the rtld-audit interface. The -P option is provided for Solaris compatibility ๐Ÿฆ‹
  • --enable-linker-version turns on the "LINKER_VERSION" linker script directive, which is described in Output Section Data ๐Ÿ“œ If this directive is used in a linker script and this option has been enabled, a string containing the linker version gets inserted at the current point โœจ
โš ๏ธ the location of this option on the linker command line matters! It only affects linker scripts that come after it on the command line, or ones that are built into the linker ๐Ÿ™ˆ
  • --disable-linker-version turns off the "LINKER_VERSION" linker script directive, so it doesn't insert a version string ๐Ÿ’… This is the default ๐Ÿซถ
  • --enable-non-contiguous-regions ๐Ÿ’– โ€” okay so this one stops the linker from throwing a fit when an input section doesn't fit its matching output section. Instead, it tries the next matching output sections in line, and only errors if none of them are big enough. Super useful when you've got a bunch of non-contiguous memory regions and the section doesn't care which one it lands in. โœจ The order input sections get evaluated in stays exactly the same. Check out this example:
MEMORY {
  MEM1 (rwx) : ORIGIN = 0x1000, LENGTH = 0x14
  MEM2 (rwx) : ORIGIN = 0x1000, LENGTH = 0x40
  MEM3 (rwx) : ORIGIN = 0x2000, LENGTH = 0x40
}
SECTIONS {
  mem1 : { *(.data.*); } > MEM1
  mem2 : { *(.data.*); } > MEM2
  mem3 : { *(.data.*); } > MEM3
}

with input sections:
.data.1: size 8
.data.2: size 0x10
.data.3: size 4

So .data.1 gets affected to mem1, and .data.2 and .data.3 both get affected to mem2, even though .data.3 would've fit in mem3. Lowkey wild ๐Ÿ™ˆ

โš ๏ธ This option is incompatible with INSERT statements, because it changes the way input sections get mapped to output sections. Don't mix them, bestie ๐Ÿ˜ญ
  • --enable-non-contiguous-regions-warnings ๐Ÿ”” โ€” turns on warnings for when --enable-non-contiguous-regions makes possibly unexpected matches in sections mapping. The vibe here is that a section could get quietly discarded instead of the linker failing, just because it didn't fit any output region. Warnings catch that sneaky stuff ๐Ÿ‘€
  • -e entry, --entry=entry ๐Ÿš€ โ€” sets entry as the explicit symbol where your program starts executing, instead of the default entry point. If there's no symbol named entry, the linker tries to read entry as a number and uses that as the entry address. The number is base 10 by default, but you can add a leading 0x for base 16, or a leading 0 for base 8. For i386 PE, entry can also be the original function name, minus the leading underscore and/or the trailing stdcall @number when that applies ๐Ÿงš
  • --exclude-libs lib,lib,... ๐Ÿ™…โ€โ™€๏ธ โ€” gives a list of archive libraries whose symbols should not get auto-exported. The library names can be separated by commas or colons. Passing --exclude-libs ALL excludes symbols from all archive libraries from auto export. This one's only available for the i386 PE targeted port and for ELF targeted ports. On i386 PE, symbols explicitly listed in a .def file still get exported no matter what, but on ELF targeted ports, the affected symbols get treated as hidden ๐Ÿซฃ
  • --exclude-modules-for-implib module,module,... ๐Ÿ“ฆ โ€” lists object files or archive members whose symbols should not be auto-exported, but which should still get copied wholesale into the import library generated during the link. Module names can be separated by commas or colons and have to match exactly the filenames ld used to open the files. For archive members, that's just the member name. For object files, the name you list must include and precisely match any path you used on the linker's command line. This one's only available for the i386 PE targeted port. Symbols explicitly listed in a .def file still get exported regardless ๐Ÿ’…
  • -E, --export-dynamic, --no-export-dynamic ๐ŸŒธ โ€” when you're creating a dynamically linked executable, -E or --export-dynamic tells the linker to add all symbols to the dynamic symbol table. That table is the set of symbols visible to dynamic objects at run time.

If you skip both of these (or use --no-export-dynamic to get the default back), the dynamic symbol table will normally only hold the symbols referenced by some dynamic object mentioned in the link.

If you use dlopen to load a dynamic object that needs to refer back to symbols defined by your program (instead of some other dynamic object), you'll probably need this option when linking the program itself ๐Ÿฆ‹

You can also use the dynamic list to control which symbols get added to the dynamic symbol table, if the output format supports it. See the description of --dynamic-list.

Heads up: this option is specific to ELF targeted ports. PE targets have a similar move for exporting all symbols from a DLL or EXE, which you can read about in the description of --export-all-symbols below โœจ

  • --export-dynamic-symbol=glob ๐ŸŽฏ โ€” when creating a dynamically linked executable, symbols matching glob get added to the dynamic symbol table. When creating a shared library, references to symbols matching glob won't be bound to the definitions inside the shared library. This is a no-op when creating a shared library unless -Bsymbolic or --dynamic-list is specified. It's only meaningful on ELF platforms that support shared libraries ๐ŸŒˆ
  • --export-dynamic-symbol-list=file ๐Ÿ“ โ€” gives a --export-dynamic-symbol for each pattern in the file. The file format is the same as a version node without the scope and node name. Check out VERSION for more info ๐Ÿ’œ
  • -EB ๐Ÿ‰ โ€” links big-endian objects. This affects the default output format.
  • -EL ๐Ÿฃ โ€” links little-endian objects. This affects the default output format.
  • -f name, --auxiliary=name ๐ŸŽญ โ€” when creating an ELF shared object, this sets the internal DT_AUXILIARY field to the name you give. It tells the dynamic linker that the shared object's symbol table should be used as an auxiliary filter on the symbol table of the shared object name.

If you later link a program against this filter object, then when you run the program, the dynamic linker will see the DT_AUXILIARY field. If it resolves any symbols from the filter object, it'll first check whether there's a definition in the shared object name. If there is one, that gets used instead of the definition in the filter object. The shared object name doesn't even need to exist! So it's a neat way to offer an alternative implementation of certain functions, maybe for debugging or machine-specific performance ๐Ÿ”

You can specify this more than once. The DT_AUXILIARY entries get created in the order they show up on the command line.

  • -F name, --filter=name ๐Ÿซง โ€” when creating an ELF shared object, this sets the internal DT_FILTER field to the name you give. It tells the dynamic linker that the shared object being created should act as a filter on the symbol table of the shared object name.

If you later link a program against this filter object, the dynamic linker will see the DT_FILTER field when you run the program. It resolves symbols using the filter object's symbol table as usual, but it actually links to the definitions found in the shared object name. So the filter object can be used to pick a subset of the symbols provided by name.

Some older linkers used -F across a whole compilation toolchain to specify the object-file format for both input and output object files. The GNU linker uses other mechanisms for that instead: the -b, --format, --oformat options, the "TARGET" command in linker scripts, and the "GNUTARGET" environment variable. The GNU linker will ignore -F when it's not creating an ELF shared object ๐Ÿ™ƒ

  • -fini=name ๐Ÿ‘‹ โ€” when creating an ELF executable or shared object, this calls NAME when the executable or shared object gets unloaded, by setting DT_FINI to the address of the function. By default, the linker uses "_fini" as the function to call.
  • -g ๐Ÿคทโ€โ™€๏ธ โ€” ignored. It's only here for compatibility with other tools.
  • -G value, --gpsize=value ๐Ÿงฒ โ€” sets the maximum size of objects to be optimized using the GP register to size. This only matters for object file formats like MIPS ELF that support putting large and small objects into different sections. It's ignored for other object file formats.
  • -h name, -soname=name ๐Ÿท๏ธ โ€” when creating an ELF shared object, this sets the internal DT_SONAME field to the name you give. When an executable is linked with a shared object that has a DT_SONAME field, the dynamic linker will try to load the shared object named in DT_SONAME when you run the executable, instead of using the file name you gave the linker ๐Ÿ’…
  • -i ๐Ÿ” โ€” performs an incremental link (same as option -r).
  • -init=name ๐ŸŒฑ โ€” when creating an ELF executable or shared object, this calls NAME when the executable or shared object gets loaded, by setting DT_INIT to the address of the function. By default, the linker uses "_init" as the function to call.
  • -l namespec, --library=namespec ๐Ÿ“š โ€” adds the archive or object file specified by namespec to the list of files to link. You can use this as many times as you want. If namespec is in the form :filename, ld searches the library path for a file called filename. Otherwise, it searches the library path for a file called libnamespec.a.

On systems that support shared libraries, ld may also search for files other than libnamespec.a. Specifically, on ELF and SunOS systems, ld searches a directory for a library called libnamespec.so before searching for one called libnamespec.a. (By convention, a .so extension means it's a shared library ๐Ÿง) Note that this behaviour doesn't apply to :filename, which always means a file called filename.

The linker only searches an archive once, at the spot where it's specified on the command line. If the archive defines a symbol that was undefined in some object that came before the archive on the command line, the linker will include the appropriate file(s) from the archive. But an undefined symbol in an object appearing later on the command line won't make the linker search the archive again ๐Ÿ˜ค

If you want to force the linker to search archives multiple times, check out the -( option.

You're also allowed to list the same archive multiple times on the command line ๐Ÿซถ

Hold up, bestie, this is the tail end of the -l vibe, and it's giving "archive searching, but make it Unix" ๐Ÿ’…

This type of archive searching is standard for Unix linkers โœจ BUT if you're using ld on AIX, lowkey know that it acts different from the AIX linker ๐Ÿ™ˆ


  • -L searchdir, --library-path=searchdir โ€” okay this one is so useful ๐Ÿ—บ๏ธ It adds the path searchdir to the list of places ld checks for archive libraries and linker control scripts. Slay it as many times as you want. The directories get searched in the order you typed them on the command line, and any directories you give on the command line get searched before the default ones. Every -L applies to every -l, no matter where they sit in the command, so the order doesn't matter there ๐Ÿ’œ Also, -L options don't change how ld looks for a linker script unless you also use the -T option.

If searchdir starts with = or $SYSROOT, that prefix gets swapped out for the sysroot prefix. That prefix is set with the --sysroot option, or when the linker was configured ๐ŸŒธ

The default paths (the ones ld checks when you don't use -L) depend on which emulation mode ld is in, and sometimes on how it was configured too. It's giving "it depends" energy ๐Ÿซ 

You can also add paths inside a link script with the SEARCH_DIR command. Those directories get searched right where the linker script shows up on the command line ๐Ÿงš

  • -m emulation โ€” this one makes ld pretend to be a particular emulation linker ๐ŸŽ€ To see which emulations you've got, use --verbose or -V.

If you skip -m, the emulation comes from the LDEMULATION environment variable, but only if that's actually defined ๐Ÿ‘‘

If neither of those is set, the default emulation depends on how the linker was configured ๐Ÿ“

  • --remap-inputs=pattern=filename, --remap-inputs-file=file โ€” main character move alert ๐Ÿฆ‹ These let you rename input files before the linker tries to open them. For example, --remap-inputs=foo.o=bar.o means any attempt to load foo.o will actually load bar.o instead. Wildcards work in the first filename too, so --remap-inputs=foo*.o=bar.o renames any input file matching foo*.o to bar.o โœจ

The other way to do it is --remap-inputs-file=filename, which reads your remappings from a file. Each line can hold one remapping. Blank lines get ignored, and anything from a hash character (#) to the end of a line counts as a comment and gets ignored too ๐Ÿ’… The pattern and the filename can be separated by whitespace or an equals (=) sign.

You can use these options as many times as you want, and they stack up. Remappings get processed in the order they show up on the command line, and if they come from a file, in the order they're written in that file. Once something matches, no more checking happens for that filename ๐Ÿ’–

If the replacement filename is /dev/null or just NUL, the input file gets ignored completely. This is a cute way to test what happens when you yeet input files out of a messy build setup ๐Ÿ™ˆ

Heads up, these options are position dependent. They only affect filenames that come after them on the command line. So this:

ld foo.o --remap-inputs=foo.o=bar.o

will have no effect, whereas this:

ld --remap-inputs=foo.o=bar.o foo.o

will rename the input file foo.o to bar.o ๐Ÿซถ

Note: these options also affect files referenced by INPUT statements in linker scripts. But linker scripts get processed after the whole command line is read, so where the remap options sit on the command line doesn't matter for those.

If the verbose option is turned on, any mappings that match get reported. Just remember that verbose has to be enabled on the command line before the remapped filenames show up ๐Ÿ”ฅ

If -Map or --print-map is enabled, the remapping list gets included in the map output too ๐Ÿ—บ๏ธ

  • -M, --print-map โ€” this prints a link map to the standard output, and it's basically the tea on your whole link ๐Ÿต A link map tells you about:
    • Where object files get mapped into memory.
    • How common symbols get allocated.
    • All the archive members pulled into the link, plus the symbol that caused each one to get dragged in.
    • The values assigned to symbols.
Note: symbols whose values are computed by an expression that references a previous value of the same symbol might not show the right result in the link map ๐Ÿ˜ญ The linker throws away intermediate results and only keeps the final value of an expression. In that case, the linker shows the final value wrapped in square brackets. So a linker script containing:
foo = 1
foo = foo * 4
foo = foo + 8

will produce this in the link map if -M is used:

0x00000001                foo = 0x1
[0x0000000c]                foo = (foo * 0x4)
[0x0000000c]                foo = (foo + 0x8)

Check out Expressions for more info on expressions in linker scripts ๐Ÿ’œ

  • How GNU properties get merged.

When the linker merges input .note.gnu.property sections into one output .note.gnu.property section, some properties get removed or updated, and the link map reports those actions. For example:

Removed property 0xc0000002 to merge foo.o (0x1) and bar.o (not found)

This means property 0xc0000002 gets removed from the output while merging properties in foo.o, whose property 0xc0000002 value is 0x1, and bar.o, which doesn't have property 0xc0000002 at all.

Updated property 0xc0010001 (0x1) to merge foo.o (0x1) and bar.o (0x1)

This means the property 0xc0010001 value gets updated to 0x1 in the output while merging foo.o, whose 0xc0010001 property value is 0x1, and bar.o, whose 0xc0010001 property value is also 0x1.

  • On some ELF targets, a list of fixups inserted by --relax:
foo.o: Adjusting branch at 0x00000008 towards "far" in section .text

This means the branch at 0x00000008 in foo.o, which targets the symbol "far" in section .text, got replaced by a trampoline ๐Ÿฆ‹

  • --print-map-discarded, --no-print-map-discarded โ€” prints (or doesn't print) the list of discarded and garbage collected sections in the link map. It's on by default, so you only need the --no- version if you want it gone ๐Ÿงน
  • --print-map-locals, --no-print-map-locals โ€” prints (or doesn't print) local symbols in the link map. Local symbols get the word (local) in front of their name, and they're listed after all the global symbols in a given section. Temporary local symbols (usually the ones starting with .L) stay out of the output ๐Ÿ™ˆ This one is off by default.
  • -n, --nmagic โ€” turns off page alignment of sections and disables linking against shared libraries ๐ŸŒ™ If the output format supports Unix style magic numbers, the output gets marked as "NMAGIC".
  • -N, --omagic โ€” makes the text and data sections readable and writable ๐Ÿ’… It also stops page-aligning the data segment and disables linking against shared libraries. If the output format supports Unix style magic numbers, the output gets marked as "OMAGIC". Note: a writable text section is allowed for PE-COFF targets, but it doesn't follow the format spec Microsoft published, so be careful ๐Ÿซ 
  • --no-omagic โ€” this one basically undoes most of what -N does ๐Ÿ”„ It sets the text section back to read-only and forces the data segment to be page-aligned. Note: this option does not turn on linking against shared libraries. For that, use -Bdynamic ๐Ÿ’–
  • -o output, --output=output โ€” names the program ld produces as output ๐ŸŽ€ If you don't use this option, the default name is a.out. The OUTPUT script command can also set the output file name.
Note: the linker deletes the output file before it starts writing to it. It does this even if the link ends up failing because of errors, so no cap, don't panic when your old binary vanishes ๐Ÿ˜ญ

Also, the linker checks that the output file name doesn't match any of the input file names, and that's all it checks. It won't complain if the output file might overwrite a source file or some other important file ๐Ÿ’€ So in build systems, it's recommended to put the -o option last on the linker command line. For example, consider:

ld -o $(EXE) $(OBJS)
ld $(OBJS) -o $(EXE)

If the EXE variable isn't defined for some reason, the first version could end up deleting one of the object files (the first one in the OBJS list), whereas the second version will throw an error message and delete nothing ๐Ÿฅบ

  • --dependency-file=depfile โ€” writes a dependency file to depfile ๐Ÿงš This file has a rule that make can read, describing the output file and every input file that got read to produce it. The output is similar to the compiler's output with -M -MP. There's no equivalent of the compiler's -MM to exclude "system files" (that's not a well-specified concept in the linker, unlike "system headers" in the compiler), so the output from --dependency-file is always specific to the exact state of the installation where it was made. Don't copy it into distributed makefiles without careful editing โš ๏ธ
  • -O level โ€” if level is a numeric value greater than zero, ld optimizes the output โœจ This might take significantly longer, so it probably should only be turned on for the final binary. Right now, this option only affects ELF shared library generation. Future releases of the linker may make more use of it. Also, there's currently no difference in the linker's behaviour between different non-zero values of this option, and that might change in future releases too ๐ŸŒธ

-plugin name โ€” okay so this one lets you slide a plugin into the linking process ๐Ÿ’… The name is the absolute filename of the plugin. Usually the compiler adds it automatically when you're doing link time optimization, but you're totally allowed to bring your own plugins if you're feeling iconic ๐Ÿ”Œโœจ

Heads up tho, the compiler's plugins live somewhere different from where ar, nm and ranlib look for theirs ๐Ÿ™ˆ So if you want those commands to use a compiler based plugin, you gotta copy it into ${libdir}/bfd-plugins first. The good news is every gcc based linker plugin is backward compatible, so copying in just the newest one is enough ๐Ÿ’œ

  • --push-state โ€” this one is a total lifesaver ๐Ÿซถ It saves the current state of the flags that control how input files get handled, so they can all be brought back later with one matching --pop-state. The flags it covers are: -Bdynamic, -Bstatic, -dn, -dy, -call_shared, -non_shared, -static, -N, -n, --whole-archive, --no-whole-archive, -r, -Ur, --copy-dt-needed-entries, --no-copy-dt-needed-entries, --as-needed, --no-as-needed, and -a.
    • Pkg-config specs are a big reason this exists ๐Ÿงš When you use the --libs option, it lists every library that might be needed and then links all of them all the time, which is giving chaos ๐Ÿ’€ So it's better to write it like this:
-Wl,--push-state,--as-needed -libone -libtwo -Wl,--pop-state
  • --pop-state โ€” undoes the effect of --push-state and restores the previous values of the flags that govern input file handling ๐ŸŽ€
  • -q, --emit-relocs โ€” keeps the relocation sections and their contents in fully linked executables ๐Ÿ” Post link analysis and optimization tools might need this info to make correct changes to executables. Fair warning, it makes your executables bigger ๐Ÿ‘ It's currently only supported on ELF platforms.
  • --force-dynamic โ€” forces the output file to have dynamic sections ๐Ÿฆ‹ This one is specific to VxWorks targets, so it's not for everyone.
  • -r, --relocatable โ€” generates relocatable output, aka an output file that can be fed right back into ld as input ๐Ÿ” This is often called partial linking. In environments that support standard Unix magic numbers, it also sets the output file's magic number to "OMAGIC" โœจ If you don't use this option, you get an absolute file instead. When linking C++ programs, this won't resolve references to constructors, so if you need that, use -Ur ๐Ÿ’…
    • If an input file isn't the same format as the output, partial linking only works when that input has no relocations at all ๐Ÿ˜ญ Some "a.out"-based formats don't support partial linking with input files in other formats at all, so be careful bestie.
    • When the relocatable output has both stuff that needs link-time optimization (LTO) and stuff that doesn't, a .gnu_object_only section gets created to hold a relocatable object file. It's as if -r got applied to all the relocatable inputs that don't need LTO ๐ŸŒธ When the linker processes a relocatable input that has a .gnu_object_only section, it pulls that section out as its own separate input.
    • Be warned: since -r groups some sections from different input files together, it can hurt code size and locality in the final executable or shared library ๐Ÿฅบ
    • This option does the same thing as -i.
  • -R filename, --just-symbols=filename โ€” reads symbol names and their addresses from filename, but doesn't relocate it or include it in the output ๐Ÿงš This lets your output file symbolically point at absolute memory locations defined in other programs. You can use this option more than once ๐Ÿ’–
    • For compatibility with other ELF linkers, if you follow -R with a directory name instead of a file name, it gets treated as the -rpath option ๐Ÿ—‚๏ธ
  • --rosegment, --no-rosegment โ€” tries to make sure only a single read-only, non-code segment gets created ๐Ÿ“ This is only useful together with the -z separate-code option, and the binaries should come out smaller than if you used -z separate-code by itself. Without this option, or if you pass --no-rosegment, -z separate-code creates two read-only segments: one before the code segment and one after it.
    • The names are kinda misleading, ngl ๐Ÿ™ˆ But they were picked so the linker stays compatible with the LLD and GOLD linkers.
    • These options are only supported by ELF targets.
  • -s, --strip-all โ€” omits all symbol information from the output file ๐Ÿ”ฅ
  • -S, --strip-debug โ€” omits debugger symbol information (but not all symbols) from the output file ๐Ÿช„
  • --strip-discarded, --no-strip-discarded โ€” omits (or doesn't omit) global symbols defined in discarded sections ๐Ÿ—‘๏ธ This one is enabled by default, so you get it unless you say otherwise.
  • -plugin-save-temps โ€” stores the plugin "temporary" intermediate files permanently ๐Ÿ’พ
  • -t, --trace โ€” prints the names of the input files as ld processes them ๐Ÿ‘€ If you give -t twice, members inside archives get printed too. The -t output is super useful for generating a list of all the object files and scripts involved in a link, like when you're packaging stuff up for a linker bug report ๐Ÿ›
  • -T scriptfile, --script=scriptfile โ€” uses scriptfile as the linker script ๐Ÿ“œ This one replaces ld's default linker script instead of adding to it, unless the script contains "INSERT", so your commandfile has to spell out everything needed to describe the output file.
    • If scriptfile isn't in the current directory, "ld" goes looking for it in the directories given by any -L options that come before it ๐Ÿ”
    • Command line options that show up before -T can affect the script, but ones that show up after it don't ๐Ÿ™…โ€โ™€๏ธ
    • Multiple -T options will pile up if they're augmenting the current script. Otherwise, the last non-augmenting -T option is the one that gets used ๐Ÿ’œ
    • There are other ways of specifying linker scripts. See
  • -dT scriptfile, --default-script=scriptfile โ€” uses scriptfile as the default linker script ๐Ÿง This is kinda like --script, except the processing of the script gets delayed until after the rest of the command line has been handled. That means options placed after --default-script can change how the linker script behaves ๐Ÿช„ This matters a lot when the command line isn't directly in your control, like when another tool (such as gcc) is building it for you.
  • -u symbol, --undefined=symbol โ€” forces symbol to be entered in the output file as an undefined symbol ๐Ÿฆ‹ Doing this can trigger linking of extra modules from standard libraries, for example. You can repeat -u with different arguments to add more undefined symbols. This option is equivalent to the "EXTERN" linker script command.
    • If you're using this to force extra modules into the link, and it would be an error for the symbol to stay undefined, use --require-defined instead ๐Ÿซถ
  • --require-defined=symbol โ€” requires that symbol is defined in the output file ๐Ÿ›ก๏ธ It's the same as --undefined, except if symbol isn't defined in the output file, the linker throws an error and exits. You can get the same effect in a linker script by using "EXTERN", "ASSERT" and "DEFINED" together. You can use this option multiple times to require more symbols.
  • -Ur โ€” for programs that don't use constructors or destructors, or for ELF based systems, this is equivalent to -r: it generates relocatable output, aka an output file that can serve as input to ld again ๐Ÿ” For other binaries, though, -Ur is similar to -r but it also resolves references to constructors and destructors โœจ
    • On systems where -r and -Ur behave differently, you can't use -Ur on files that were already linked with -Ur. Once the constructor table has been built, it can't be added to ๐Ÿ˜ญ So use -Ur only for the last partial link, and -r for the others.
  • --orphan-handling=MODE โ€” controls how orphan sections get handled ๐ŸŒˆ An orphan section is one that isn't specifically mentioned in a linker script.
    • MODE can be any of these values:
      • "place" โ€” orphan sections get placed into a suitable output section, following the strategy described in Orphan Sections. The --unique option also affects how sections are placed.
      • "discard" โ€” all orphan sections get thrown out by placing them in the /DISCARD/ section ๐Ÿ—‘๏ธ
      • "warn" โ€” the linker places the orphan section like "place" does, and also issues a warning โš ๏ธ
      • "error" โ€” the linker exits with an error if any orphan section is found ๐Ÿ’€
    • If you don't give --orphan-handling, the default is "place" โœจ
  • --unique[=SECTION] โ€” creates a separate output section for every input section matching SECTION. If the optional wildcard SECTION argument is missing, it does this for every orphan input section instead. An orphan section is one not specifically mentioned in a linker script ๐Ÿงฉ You can use this option multiple times on the command line. It stops the normal merging of input sections with the same name, and it overrides output section assignments in a linker script ๐Ÿ™…โ€โ™€๏ธ
  • -v, --version, -V โ€” displays the version number for ld ๐Ÿ’– -V also lists the supported emulations. Check out the description of --enable-linker-version in Options,,Command-line Options too, which can insert the linker version string into a binary ๐ŸŽ€
  • -x, --discard-all โ€” deletes all local symbols ๐Ÿงน
  • -X, --discard-locals โ€” deletes all temporary local symbols ๐Ÿซง (These symbols start with system-specific local label prefixes, typically .L for ELF systems or L for traditional a.out systems.)
  • -y symbol, --trace-symbol=symbol โ€” prints the name of each linked file that symbol shows up in ๐Ÿ•ต๏ธโ€โ™€๏ธ You can give this option any number of times. On many systems you need to prepend an underscore to the symbol name ๐ŸŒธ

This one is a lifesaver when you've got an undefined symbol in your link but no clue where the reference is coming from ๐Ÿฅน

  • -Y path โ€” okay so this one adds path to the default library search path ๐Ÿ—บ๏ธ It's only here for Solaris compatibility, so it's a little nostalgic. ๐ŸŒธ
  • -z keyword โ€” bestie, this is the big -z buffet ๐Ÿฐ These are the recognized keywords:
    • call-nop=prefix-addr, call-nop=suffix-nop, call-nop=prefix-byte, call-nop=suffix-byte โ€” these set the 1-byte "NOP" padding when the linker transforms an indirect call to a locally defined function, foo, through its GOT slot ๐Ÿฆ‹ call-nop=prefix-addr gives you 0x67 call foo, call-nop=suffix-nop gives you call foo 0x90, call-nop=prefix-byte gives you byte call foo, and call-nop=suffix-byte gives you call foo byte. Supported for i386 and x86_64. โœจ
    • cet-report=none, cet-report=warning, cet-report=error โ€” this decides how missing GNU_PROPERTY_X86_FEATURE_1_IBT and GNU_PROPERTY_X86_FEATURE_1_SHSTK properties get reported in the input .note.gnu.property section ๐Ÿ” cet-report=none is the default, so the linker stays quiet about missing properties. cet-report=warning makes it issue a warning, and cet-report=error makes it issue an error. Heads up: ibt turns off the missing GNU_PROPERTY_X86_FEATURE_1_IBT report, and shstk turns off the missing GNU_PROPERTY_X86_FEATURE_1_SHSTK report ๐Ÿ’… Supported for Linux/i386 and Linux/x86_64.
    • combreloc, nocombreloc โ€” combreloc merges multiple dynamic relocation sections and sorts them so dynamic symbol lookup caching gets a glow-up ๐ŸŒˆ nocombreloc means don't do that.
    • common, nocommon โ€” during a relocatable link, common generates common symbols with the STT_COMMON type ๐Ÿ’œ With nocommon, you get the STT_OBJECT type instead.
    • common-page-size=value โ€” sets the most commonly used page size to value. If the system uses pages of that size, the memory image layout gets optimized to minimize memory pages ๐Ÿงš
    • defs โ€” reports unresolved symbol references from regular object files ๐Ÿ™ˆ This happens even when the linker is making a non-symbolic shared library. It's the opposite of -z undefs.
    • dynamic-undefined-weak, nodynamic-undefined-weak โ€” dynamic-undefined-weak makes undefined weak symbols dynamic when building a dynamic object, as long as they're referenced from a regular object file and aren't forced local by symbol visibility or versioning ๐ŸŒธ nodynamic-undefined-weak means don't make them dynamic. If you pass neither, a target might default to either one being in force, or pick its own way of making some undefined weak symbols dynamic. Not every target supports these, fyi.
    • execstack โ€” marks the object as requiring an executable stack ๐Ÿ˜ˆ
    • global โ€” only meaningful when building a shared object ๐ŸŒ It makes the symbols defined by this shared object available for symbol resolution of libraries loaded afterwards.
    • globalaudit โ€” only meaningful when building a dynamic executable ๐Ÿ•ต๏ธโ€โ™€๏ธ This marks the executable as requiring global auditing by setting the "DF_1_GLOBAUDIT" bit in the "DT_FLAGS_1" dynamic tag. Global auditing means any auditing library defined through the --depaudit or -P command-line options has to run for every dynamic object the application loads.
    • ibtplt โ€” generates Intel Indirect Branch Tracking (IBT) enabled PLT entries ๐Ÿฆ‹ Supported for Linux/i386 and Linux/x86_64.
    • ibt โ€” generates GNU_PROPERTY_X86_FEATURE_1_IBT in the .note.gnu.property section to say "yep, I'm IBT compatible" ๐Ÿ’– It also implies ibtplt. Supported for Linux/i386 and Linux/x86_64.
    • indirect-extern-access, noindirect-extern-access โ€” indirect-extern-access generates GNU_PROPERTY_1_NEEDED_INDIRECT_EXTERN_ACCESS in the .note.gnu.property section, which tells everyone the object file needs canonical function pointers and can't be used with copy relocation ๐Ÿซถ It also implies noextern-protected-data and nocopyreloc. Supported for i386 and x86-64. noindirect-extern-access removes GNU_PROPERTY_1_NEEDED_INDIRECT_EXTERN_ACCESS from the .note.gnu.property section ๐Ÿงน
    • initfirst โ€” only meaningful when building a shared object โญ It marks the object so its runtime initialization happens before the runtime initialization of any other objects brought into the process at the same time. Likewise, its runtime finalization happens after the finalization of any other objects.
    • interpose โ€” tells the dynamic loader to change its symbol search order so symbols in this shared library interpose all the other shared libraries that aren't marked this way ๐Ÿ‘‘
    • unique, nounique โ€” when generating a shared library or another dynamically loadable ELF object, unique marks it as one that should (by default) only ever be loaded once, and only in the main namespace when using "dlmopen" ๐Ÿ”’ This is mainly for fundamental libraries like libc and libpthread, which don't usually work right unless they're the only instances of themselves. The "dlmopen" caller can override this behaviour, and it doesn't apply to certain loading mechanisms, like audit libraries. nounique is the opposite.
    • lam-u48 โ€” generates GNU_PROPERTY_X86_FEATURE_1_LAM_U48 in the .note.gnu.property section to show compatibility with Intel LAM_U48 ๐Ÿ’… Supported for Linux/x86_64.
    • lam-u57 โ€” generates GNU_PROPERTY_X86_FEATURE_1_LAM_U57 in the .note.gnu.property section to show compatibility with Intel LAM_U57 ๐Ÿ’… Supported for Linux/x86_64.
    • lam-u48-report=none, lam-u48-report=warning, lam-u48-report=error โ€” controls how the missing GNU_PROPERTY_X86_FEATURE_1_LAM_U48 property gets reported in the input .note.gnu.property section ๐Ÿ“ฃ lam-u48-report=none is the default and keeps the linker quiet about it. lam-u48-report=warning makes the linker issue a warning, and lam-u48-report=error makes it issue an error. Supported for Linux/x86_64.
    • lam-u57-report=none, lam-u57-report=warning, lam-u57-report=error โ€” same vibe, but for the missing GNU_PROPERTY_X86_FEATURE_1_LAM_U57 property ๐Ÿ“ฃ lam-u57-report=none is the default, lam-u57-report=warning gives a warning, and lam-u57-report=error gives an error. Supported for Linux/x86_64.
    • lam-report=none, lam-report=warning, lam-report=error โ€” this one handles the missing GNU_PROPERTY_X86_FEATURE_1_LAM_U48 and GNU_PROPERTY_X86_FEATURE_1_LAM_U57 properties in the input .note.gnu.property section ๐Ÿซง lam-report=none is the default and stays silent, lam-report=warning issues a warning, and lam-report=error issues an error. Supported for Linux/x86_64.
    • lazy โ€” when generating an executable or shared library, this tells the dynamic linker to defer function call resolution until the function is actually called (lazy binding), instead of doing it at load time ๐Ÿ˜ด Lazy binding is the default, so you're already getting it.
    • loadfltr โ€” specifies that the object's filters get processed immediately at runtime โšก
    • max-page-size=value โ€” sets the maximum supported memory page size to value ๐Ÿ“
    • mark-plt, nomark-plt โ€” mark-plt marks PLT entries with the dynamic tags DT_X86_64_PLT, DT_X86_64_PLTSZ and DT_X86_64_PLTENT ๐Ÿท๏ธ Heads up, this one is a bit spicy: since it stores a non-zero value in the r_addend field of R_X86_64_JUMP_SLOT relocations, the resulting executables and shared libraries are incompatible with dynamic linkers like the ones in older versions of glibc that didn't get the change to ignore r_addend in R_X86_64_GLOB_DAT and R_X86_64_JUMP_SLOT relocations. Those older linkers don't ignore the r_addend field of R_X86_64_JUMP_SLOT relocations. โš ๏ธ Supported for x86_64. nomark-plt is the "no thanks" option.
    • muldefs โ€” allows multiple definitions ๐Ÿคทโ€โ™€๏ธ
    • nocopyreloc โ€” disables the linker-generated .dynbss variables that get used in place of variables defined in shared libraries ๐Ÿ™…โ€โ™€๏ธ This may result in dynamic text relocations.
    • nodefaultlib โ€” specifies that the dynamic loader, when searching for this object's dependencies, should ignore any default library search paths ๐Ÿšซ
    • nodelete โ€” specifies that the object shouldn't be unloaded at runtime ๐Ÿงท
    • nodlopen โ€” specifies that the object isn't available to "dlopen" ๐Ÿ™ˆ
    • nodump โ€” specifies that the object can't be dumped by "dldump" ๐Ÿ’€
    • noexecstack โ€” marks the object as not requiring an executable stack ๐Ÿ”
    • noextern-protected-data โ€” stops the linker from treating protected data symbols as external when building a shared library ๐ŸŒท This overrides the linker backend default. It can be used to work around incorrect relocations against protected data symbols that the compiler generates. Heads up: updates on protected data symbols made by another module won't be visible to the resulting shared library. Supported for i386 and x86-64.
    • noreloc-overflow โ€” disables the relocation overflow check ๐Ÿงจ You can use this if there won't be any dynamic relocation overflow at run-time. Supported for x86_64.
    • memory-seal, nomemory-seal โ€” memory-seal tells the executable or shared library that all PT_LOAD segments should be sealed so nobody can mess with them later, like changing the protection flags, the segment size, or removing the mapping ๐Ÿ”’ It's a security hardening feature that needs system support, and it generates GNU_PROPERTY_MEMORY_SEAL in the .note.gnu.property section. nomemory-seal is the opt-out ๐Ÿฆ‹
  • -z now โ€” Okay so this one tells the dynamic linker to resolve every symbol right away when the program starts up, or when a shared library gets loaded by dlopen, instead of waiting until a function is called for the first time. It only applies when you're generating an executable or shared library. ๐Ÿ”ฅ
  • -z origin โ€” Marks the object as needing $ORIGIN handling in paths. ๐ŸŒธ
  • -z pack-relative-relocs, -z nopack-relative-relocs โ€” Generates compact relative relocations in position-independent executables and shared libraries. It adds DT_RELR, DT_RELRSZ and DT_RELRENT entries to the dynamic section. It gets ignored when you're building position-dependent executables and relocatable output. nopack-relative-relocs is the default, which turns compact relative relocation off. When you link against the GNU C Library, a GLIBC_ABI_DT_RELR symbol version dependency on the shared C Library gets added to the output. Supported for i386 and x86-64. โœจ
  • -z relro, -z norelro โ€” Creates an ELF PT_GNU_RELRO segment header in the object. This marks a memory segment that should become read-only after relocation, if the system supports it. Heads up: setting common-page-size smaller than the system page size makes this protection totally ineffective. ๐Ÿ’€ norelro means no PT_GNU_RELRO segment gets created.
  • -z report-relative-reloc โ€” Reports the dynamic relative relocations the linker generated. Supported for Linux/i386 and Linux/x86_64. ๐Ÿฆ‹
  • -z sectionheader, -z nosectionheader โ€” Generates the section header. nosectionheader means no section header gets generated. sectionheader is the default, so you're getting it unless you ask otherwise. ๐ŸŽ€
  • -z separate-code, -z noseparate-code โ€” Creates a separate code PT_LOAD segment header in the object. This is a memory segment that should contain only instructions and has to sit in wholly disjoint pages from any other data. noseparate-code means no separate code PT_LOAD segment gets created. ๐Ÿ’œ
  • -z shstk โ€” Generates GNU_PROPERTY_X86_FEATURE_1_SHSTK in the .note.gnu.property section, which signals compatibility with Intel Shadow Stack. Supported for Linux/i386 and Linux/x86_64. ๐Ÿซถ
  • -z stack-size=value โ€” Sets a stack size for an ELF PT_GNU_STACK segment. Be careful here: specifying zero overrides any default non-zero sized PT_GNU_STACK segment creation. ๐Ÿ™ˆ
  • -z start-stop-gc, -z nostart-stop-gc โ€” Okay this one is kind of a lot, bestie ๐Ÿ˜ญ When --gc-sections is in effect, a reference from a retained section to __start_SECNAME or __stop_SECNAME causes all input sections named SECNAME to also get retained. This only happens if SECNAME is representable as a C identifier and the linker synthesizes either __start_SECNAME or __stop_SECNAME. -z start-stop-gc turns this effect off, so sections can get garbage collected as if the special synthesized symbols were never defined. It has no effect on a definition of __start_SECNAME or __stop_SECNAME in an object file or linker script. Such a definition stops the linker from providing a synthesized __start_SECNAME or __stop_SECNAME respectively, which means the special garbage collection treatment for those references doesn't happen either. ๐Ÿงš
  • -z start-stop-visibility=value โ€” Sets the ELF symbol visibility for the synthesized __start_SECNAME and __stop_SECNAME symbols. value must be exactly default, internal, hidden, or protected. If you don't give a -z start-stop-visibility option, protected gets used for compatibility with historical practice. However, it's highly recommended to use -z start-stop-visibility=hidden in new programs and shared libraries, so these symbols don't get exported between shared objects. That's usually not what's intended. ๐Ÿ‘‘
  • -z text, -z notext, -z textoff โ€” Reports an error if DT_TEXTREL is set. That means the position-independent or shared object has dynamic relocations in read-only sections. notext and textoff mean no error gets reported. ๐ŸŒˆ
  • -z undefs โ€” Stops the linker from reporting unresolved symbol references from regular object files, whether you're creating an executable or a shared library. This is the inverse of -z defs. โญ
  • -z unique-symbol, -z nounique-symbol โ€” Avoids duplicated local symbol names in the symbol string table. If you use unique-symbol, a "."number"" gets appended to duplicated local symbol names. nounique-symbol is the default. ๐Ÿ’…
  • -z x86-64-baseline, -z x86-64-v2, -z x86-64-v3, -z x86-64-v4 โ€” Specifies the x86-64 ISA level needed in the .note.gnu.property section. x86-64-baseline generates GNU_PROPERTY_X86_ISA_1_BASELINE. x86-64-v2 generates GNU_PROPERTY_X86_ISA_1_V2. x86-64-v3 generates GNU_PROPERTY_X86_ISA_1_V3. x86-64-v4 generates GNU_PROPERTY_X86_ISA_1_V4. Supported for Linux/i386 and Linux/x86_64. ๐Ÿ“
  • -z isa-level-report=none, -z isa-level-report=all, -z isa-level-report=needed, -z isa-level-report=used โ€” Decides how x86-64 ISA levels get reported in input relocatable files. isa-level-report=none is the default, and it stops the linker from reporting x86-64 ISA levels in input files. isa-level-report=all makes the linker report both needed and used x86-64 ISA levels in input files. isa-level-report=needed reports only the needed levels, and isa-level-report=used reports only the used levels. Supported for Linux/i386 and Linux/x86_64. ๐ŸŒธ

Other keywords are ignored for Solaris compatibility.


โœจ OPTIONS โœจ

  • -( archives -), --start-group archives --end-group โ€” The archives should be a list of archive files, either explicit file names or -l options. The linker searches the specified archives repeatedly until no new undefined references get created. Normally an archive only gets searched once, in the order it's specified on the command line. So if a symbol in that archive is needed to resolve an undefined symbol referenced by an object in a later archive on the command line, the linker won't be able to resolve it. Grouping the archives means they all get searched repeatedly until every possible reference is resolved. ๐Ÿ”

Using this option has a significant performance cost, so it's best to use it only when there are unavoidable circular references between two or more archives. ๐Ÿข

  • --accept-unknown-input-arch, --no-accept-unknown-input-arch โ€” Tells the linker to accept input files whose architecture can't be recognised. The assumption is that you know what you're doing and deliberately want to link in these unknown input files. This was the linker's default behaviour before release 2.14. From release 2.14 onwards the default is to reject such input files, so --accept-unknown-input-arch got added to bring back the old behaviour. ๐Ÿ•ฐ๏ธ
  • --as-needed, --no-as-needed โ€” This option affects ELF DT_NEEDED tags for dynamic libraries mentioned on the command line after --as-needed. Normally the linker adds a DT_NEEDED tag for every dynamic library on the command line, whether or not it's actually needed. --as-needed means a DT_NEEDED tag only gets emitted for a library that, at that point in the link, satisfies a non-weak undefined symbol reference from a regular object file. Or, if the library isn't found in the DT_NEEDED lists of other needed libraries, it can satisfy a non-weak undefined symbol reference from another needed dynamic library. Object files or libraries appearing after the library in question on the command line don't affect whether it's seen as needed. This works similarly to the rules for extracting object files from archives. --no-as-needed restores the default behaviour. ๐Ÿง 
Note: On Linux based systems the --as-needed option also affects the behaviour of the --rpath and --rpath-link options. Check the description of --rpath-link for more details.
  • --add-needed, --no-add-needed โ€” These two are deprecated, because their names look way too similar to --as-needed and --no-as-needed. ๐Ÿ˜ฌ They've been replaced by --copy-dt-needed-entries and --no-copy-dt-needed-entries.
  • -assert keyword โ€” This option is ignored for SunOS compatibility. ๐Ÿ™ƒ
  • -Bdynamic, -dy, -call_shared โ€” Links against dynamic libraries. This only means something on platforms that support shared libraries, and it's normally the default on those platforms. The different variants are there for compatibility with various systems. You can use this option multiple times on the command line, and it affects library searching for any -l options that come after it. ๐ŸŽถ
  • -Bgroup โ€” Sets the DF_1_GROUP flag in the DT_FLAGS_1 entry in the dynamic section. This makes the runtime linker perform lookups in this object and its dependencies only inside the group. --unresolved-symbols=report-all is implied. This option is only meaningful on ELF platforms that support shared libraries. ๐Ÿซง
  • -Bstatic, -dn, -non_shared, -static โ€” Doesn't link against shared libraries. This only matters on platforms that support shared libraries, and the different variants are for compatibility with various systems. You can use this option multiple times on the command line, and it affects library searching for any -l options that come after it. It also implies --unresolved-symbols=report-all. This option can be used with -shared, which means a shared library is being created, but all of the library's external references must be resolved by pulling in entries from static libraries. ๐Ÿชจ
  • -Bsymbolic โ€” When creating a shared library, binds references to global symbols to the definition inside the shared library, if there is one. Normally a program linked against a shared library can override the definition inside that library. This option is only meaningful on ELF platforms that support shared libraries. ๐Ÿ”’
  • -Bsymbolic-functions โ€” okay so when you're making a shared library ๐Ÿฆ‹, this tells the linker to bind references to global function symbols to the definition inside that same shared library, if there is one. It only matters on ELF platforms that support shared libraries ๐Ÿ’œ
  • -Bno-symbolic โ€” the cancel button ๐Ÿ™…โ€โ™€๏ธ It undoes any earlier -Bsymbolic and -Bsymbolic-functions you set.
  • --dynamic-list=dynamic-list-file โ€” lets you hand the linker the name of a dynamic list file ๐Ÿ“œโœจ It's usually for two things: when you're making shared libraries and want a list of global symbols that should not get bound to the definition inside the library, or when you're making dynamically linked executables and want a list of symbols that should be added to the executable's symbol table. Same as before, this only matters on ELF platforms that support shared libraries. The format of the list is the same as a version node without the scope and node name, so check out VERSION for the deets ๐ŸŽ€
  • --dynamic-list-data โ€” pulls every global data symbol into the dynamic list ๐Ÿ’…
  • --dynamic-list-cpp-new โ€” provides the builtin dynamic list for C++ operator new and delete ๐Ÿงš It's mainly useful when you're building a shared libstdc++.
  • --dynamic-list-cpp-typeinfo โ€” provides the builtin dynamic list for C++ runtime type identification ๐ŸŒธ
  • --check-sections, --no-check-sections โ€” by default the linker double-checks section addresses after they've been assigned, looking for overlaps ๐Ÿ‘€ If it finds any, it spits out helpful error messages. --no-check-sections tells it to skip that check. The linker does know about overlays and makes allowances for them, so no drama there ๐Ÿซถ Heads up: overlap isn't usually checked for relocatable links, but you can force the check with --check-sections. And the default behaviour can be restored by using the command-line switch --check-sections ๐Ÿ’–
  • --copy-dt-needed-entries, --no-copy-dt-needed-entries โ€” this one is all about dynamic libraries that are referenced by DT_NEEDED tags inside ELF dynamic libraries you mention on the command line ๐Ÿ“ Normally the linker won't add a DT_NEEDED tag to the output binary for every library listed in a DT_NEEDED tag of an input dynamic library. But if you pass --copy-dt-needed-entries, then any dynamic libraries that come after it get their DT_NEEDED entries added. Go back to the default with --no-copy-dt-needed-entries โœจ

This also changes how symbols get resolved in dynamic libraries ๐Ÿ” With --copy-dt-needed-entries, the dynamic libraries on the command line get searched recursively, following their DT_NEEDED tags to other libraries, so the linker can resolve symbols the output binary needs. With the default setting, the search stops at the dynamic library itself, and no DT_NEEDED links get traversed to resolve symbols.

  • --cref โ€” outputs a cross reference table ๐Ÿ“‹ If you're generating a linker map file, the table gets printed into the map file. Otherwise it shows up on standard output. It's intentionally simple so a script can chew through it if needed. The symbols are printed sorted by name, and for each one you get a list of file names. If the symbol is defined, the first file listed is where the definition lives. If it's defined as a common value, any files where that happens come next. And finally, you get every file that references the symbol ๐Ÿฆ‹
  • --ctf-variables, --no-ctf-variables โ€” the CTF debuginfo format has a section that stores the names and types of variables in your program that don't show up in any symbol table ๐Ÿ™ˆ Those variables can't be looked up by address with regular debuggers, so the space for their types and names is usually wasted. The types are usually small, but the names often aren't. --ctf-variables makes the linker generate that section anyway, and --no-ctf-variables gets you back to the default ๐Ÿ’œ
  • --ctf-share-types=method โ€” adjusts how types get shared between translation units in CTF ๐Ÿงฉ There are two methods:
    • share-unconflicted โ€” puts every type that doesn't have an ambiguous definition into the shared dictionary, where debuggers can easily reach them, even if they only show up in one translation unit. This is the default ๐Ÿ‘‘
    • share-duplicated โ€” only puts types that appear in multiple translation units into the shared dictionary. Types with only one definition go into per-translation-unit dictionaries instead. Types with ambiguous definitions across multiple translation units always go into per-translation-unit dictionaries. This tends to make the CTF bigger, but it may shrink what's in the shared dictionary. For very large projects it can speed up opening the CTF and save memory in the CTF consumer at runtime ๐Ÿš€
  • --no-define-common โ€” stops the linker from assigning addresses to common symbols ๐Ÿ›‘ The script command INHIBIT_COMMON_ALLOCATION does the exact same thing. This option lets you decouple the choice to assign addresses to Common symbols from the choice of output file type. Normally a non-Relocatable output type forces the linker to assign addresses to Common symbols anyway. With --no-define-common, Common symbols referenced from a shared library only get addresses in the main program ๐ŸŽ€ That kills the unused duplicate space in the shared library, and it also prevents any confusion about resolving to the wrong duplicate when there are lots of dynamic modules with specialized search paths for runtime symbol resolution.
  • --force-group-allocation โ€” makes the linker place section group members like normal input sections, and deletes the section groups themselves ๐Ÿงš That's already the default for a final link, but you can use this option to change the behaviour of a relocatable link (-r). The script command FORCE_GROUP_ALLOCATION has the same effect.
  • --defsym=symbol=expression โ€” creates a global symbol in the output file, containing the absolute address given by expression ๐ŸŒˆ You can use this as many times as you want to define multiple symbols on the command line. The expression gets a limited form of arithmetic: you can give a hexadecimal constant or the name of an existing symbol, and use + and - to add or subtract hexadecimal constants or symbols. If you need fancier expressions, use the linker command language from a script instead ๐Ÿ’… Also, there should be no white space between symbol, the equals sign (=), and expression. ๐Ÿ™…โ€โ™€๏ธ

Order matters here ๐ŸŽฏ The linker processes --defsym and -T arguments in order. Put --defsym before -T and the symbol gets defined before the linker script from -T is processed. Put it after -T and the symbol gets defined after the script has been processed. This affects expressions inside the linker script that use the --defsym symbols, so which order is correct depends on what you're trying to achieve.

  • --demangle[=style], --no-demangle โ€” these control whether symbol names get demangled in error messages and other output โœจ When demangling is on, the linker tries to make symbol names readable: it strips leading underscores if the object file format uses them, and converts C++ mangled symbol names into user-readable names. Different compilers have different mangling styles, so the optional style argument lets you pick one that matches your compiler. By default the linker demangles unless the environment variable COLLECT_NO_DEMANGLE is set. You can use these options to override that default ๐Ÿฆ‹
  • -Ifile, --dynamic-linker=file โ€” sets the name of the dynamic linker ๐Ÿ”— This only matters when you're generating dynamically linked ELF executables. The default dynamic linker is normally correct, so don't touch this unless you really know what you're doing ๐Ÿ™ˆ
  • --no-dynamic-linker โ€” when you're producing an executable file, this leaves out the request for a dynamic linker to be used at load-time ๐ŸŒธ It only matters for ELF executables that contain dynamic relocations, and it usually needs entry point code that can actually process those relocations.
  • --embedded-relocs โ€” works like --emit-relocs, except the relocs get stored in a target-specific section ๐Ÿ’œ This option is only supported by the BFIN, CR16 and M68K targets.
  • --disable-multiple-abs-defs โ€” doesn't allow multiple definitions of symbols that are included in the file invoked by -R or --just-symbols ๐Ÿ™…โ€โ™€๏ธ
  • --fatal-warnings, --no-fatal-warnings โ€” treats every warning as an error ๐Ÿ˜ค If you want to go back to the default, use --no-fatal-warnings ๐Ÿ“
  • -w, --no-warnings โ€” hides every warning and error message ๐Ÿ™ˆ This overrides --fatal-warnings if it's been turned on. It's handy when you already know the output binary won't work but you still need to create it anyway ๐Ÿ’€
  • --force-exe-suffix โ€” makes sure the output file ends with a .exe suffix ๐ŸชŸ If a successfully built, fully linked output file doesn't have a .exe or .dll suffix, this forces the linker to copy the output to a file with the same name plus a .exe suffix. It's super useful when you're using unmodified Unix makefiles on a Microsoft Windows host, since some versions of Windows won't run an image unless it ends in .exe ๐Ÿ‘‘
  • --gc-sections, --no-gc-sections โ€” enables garbage collection of unused input sections ๐Ÿ—‘๏ธโœจ It gets ignored on targets that don't support this option. The default behaviour (which is not doing this garbage collection) comes back if you specify --no-gc-sections on the command line. Heads up: garbage collection for COFF and PE format targets is supported, but the implementation is currently considered to be experimental โš ๏ธ๐Ÿฅบ
  • --gc-sections decides which input sections get to stick around by checking symbols and relocations. ๐Ÿงน The section holding the entry symbol is kept, and so is every section with symbols that are undefined on the command line. Sections with symbols referenced by dynamic objects stay too. Heads up: when you're building shared libraries, the linker has to assume that any visible symbol is referenced. ๐Ÿซถ Once that first set is locked in, the linker recursively marks as used any section that gets referenced by their relocations. See --entry, --undefined, and --gc-keep-exported.

This option can be set during a partial link (turned on with -r). ๐Ÿฆ‹ In that case, the roots of the symbols you want to keep must be spelled out explicitly, either with one of --entry, --undefined, or --gc-keep-exported, or with an "ENTRY" command in the linker script. No guessing allowed bestie ๐Ÿ’…

As a GNU extension, ELF input sections marked with the "SHF_GNU_RETAIN" flag will not be garbage collected. ๐Ÿ‘‘

  • --print-gc-sections, --no-print-gc-sections โ€” this one is giving receipts ๐Ÿงพ It lists every section that got removed by garbage collection, and the listing gets printed on stderr. It only works if garbage collection is turned on with --gc-sections. If you want to go back to the default (no list of removed sections), just pass --no-print-gc-sections on the command line.
  • --gc-keep-exported โ€” when --gc-sections is on, this one stops unused input sections from being garbage collected if they contain global symbols with default or protected visibility. ๐Ÿ›ก๏ธ It's meant for executables, where unreferenced sections would otherwise get collected no matter how visible their symbols are. Lowkey useful. It has no effect when linking shared objects, because that's already the default behaviour. And it's only supported for ELF format targets. ๐ŸŒธ
  • --print-output-format โ€” prints the name of the default output format, which might be influenced by your other command-line options. ๐Ÿ“ It's the same string that would show up in an "OUTPUT_FORMAT" linker script command.
  • --print-memory-usage โ€” prints the used size, total size and used size of the memory regions created with the MEMORY command. ๐Ÿง  This is a lifesaver on embedded targets when you want a quick look at how much free memory you've got left. The output has one headline and one line per region, and it's both human readable and easy for tools to parse. Here's what an example looks like:
        Memory region         Used Size  Region Size  %age Used
                     ROM:        256 KB         1 MB     25.00%
                     RAM:          32 B         2 GB      0.00%

Note: if you want to find out about the memory usage of the linker itself, the --stats option will do that for you. ๐Ÿค“

  • --help โ€” prints a summary of the command-line options to standard output, then exits. ๐Ÿ’œ
  • --target-help โ€” prints a summary of all the target-specific options to standard output, then exits. ๐ŸŽ€
  • -Map=mapfile โ€” prints a link map to the file mapfile. Check out the description of the -M option up above for the full story. โœจ If mapfile is just -, the map gets written to stdout.

Okay, so, if you point this at a directory, the linker map gets written as a file inside that directory. ๐Ÿ“ Normally the file name inside the directory is the basename of the output file with ".map" tacked on the end. But if you use the special character %, it gets replaced by the full path of the output file instead. And if there are any characters after the % symbol, then ".map" won't get appended anymore. Iykyk ๐Ÿ™ˆ

-o foo.exe -Map=bar                  [Creates ./bar]
-o ../dir/foo.exe -Map=bar           [Creates ./bar]
-o foo.exe -Map=../dir               [Creates ../dir/foo.exe.map]
-o ../dir2/foo.exe -Map=../dir       [Creates ../dir/foo.exe.map]
-o foo.exe -Map=%                    [Creates ./foo.exe.map]
-o ../dir/foo.exe -Map=%             [Creates ../dir/foo.exe.map]
-o foo.exe -Map=%.bar                [Creates ./foo.exe.bar]
-o ../dir/foo.exe -Map=%.bar         [Creates ../dir/foo.exe.bar]
-o ../dir2/foo.exe -Map=../dir/%     [Creates ../dir/../dir2/foo.exe.map]
-o ../dir2/foo.exe -Map=../dir/%.bar [Creates ../dir/../dir2/foo.exe.bar]
It is an error to specify more than one % character. ๐Ÿ’€ Don't do it bestie.

If the map file already exists, it will get overwritten by this operation. ๐Ÿซ 

  • --no-keep-memory โ€” ld normally goes for speed over memory usage, caching the symbol tables of input files in memory. ๐ŸŽ๏ธ This option tells ld to optimize for memory instead, by rereading the symbol tables as necessary. You might need this if ld runs out of memory space while linking a large executable. ๐Ÿฅน
  • --no-undefined, -z defs โ€” reports unresolved symbol references from regular object files. ๐Ÿšจ It does this even if the linker is creating a non-symbolic shared library. The switch --[no-]allow-shlib-undefined controls how unresolved references found in shared libraries being linked in get reported. And if you change your mind, the effects of this option can be reverted by using -z undefs.
  • --allow-multiple-definition, -z muldefs โ€” normally, if a symbol gets defined more than once, the linker reports a fatal error. ๐Ÿ˜ค These options allow multiple definitions, and the first definition is the one that gets used.
  • --allow-shlib-undefined, --no-allow-shlib-undefined โ€” allows or disallows undefined symbols in shared libraries. ๐ŸŒˆ This is kind of like --no-undefined, except it decides what happens when the undefined symbols live in a shared library instead of a regular object file. It doesn't change how undefined symbols in regular object files are handled, so don't mix those up ๐Ÿ™…โ€โ™€๏ธ

By default, the linker reports errors for any undefined symbols referenced in shared libraries when it's making an executable. But when it's making a shared library, it just allows them. ๐Ÿฆ‹

So why would anyone want undefined symbol references in shared libraries specified at link time? Two big reasons:

  • A shared library specified at link time might not be the same one that's available at load time, so the symbol could actually be resolvable at load time. ๐Ÿงš
  • Some operating systems, eg BeOS and HPPA, have undefined symbols in shared libraries as the norm. ๐Ÿคฏ

The BeOS kernel, for example, patches shared libraries at load time to select whichever function is most appropriate for the current architecture. โœจ This is used, for example, to dynamically select an appropriate memset function. Main character energy from the kernel honestly ๐Ÿ‘‘

  • --error-handling-script=scriptname โ€” if you give this option, the linker will invoke scriptname whenever it hits an error. ๐Ÿ› ๏ธ Right now only two kinds of error are supported: missing symbols and missing libraries. The script gets two arguments: either the keyword "undefined-symbol" or missing-lib", plus the name of the undefined symbol or missing library. The whole point is that the script can give the user suggestions about where the symbol or library might be found. ๐Ÿ” Once the script finishes, the normal linker error message gets displayed. Also, this option's availability is controlled by a configure time switch, so it might not be present in every implementation. Not every build has it, babe ๐Ÿฅฒ
  • --no-undefined-version โ€” normally, when a symbol has an undefined version, the linker just ignores it. ๐Ÿ™„ This option disallows symbols with an undefined version, and a fatal error is issued instead.
  • --default-symver โ€” creates and uses a default symbol version (the soname) for unversioned exported symbols. ๐ŸŽ€
  • --default-imported-symver โ€” creates and uses a default symbol version (the soname) for unversioned imported symbols. ๐Ÿง
  • --no-warn-mismatch โ€” normally ld will throw an error if you try to link together input files that don't match for some reason, like being compiled for different processors or different endiannesses. ๐Ÿง This option tells ld to silently permit those possible errors. โš ๏ธ Use it with care, and only when you've taken some special action that makes the linker errors inappropriate. Don't be reckless ๐Ÿซถ
  • --no-warn-search-mismatch โ€” normally ld gives a warning if it finds an incompatible library during a library search. This option silences that warning. ๐Ÿคซ
  • --no-whole-archive โ€” turns off the effect of --whole-archive for subsequent archive files. ๐Ÿ”•
  • --noinhibit-exec โ€” retains the executable output file whenever it's still usable. ๐Ÿ’พ Normally the linker won't produce an output file if it runs into errors during the link process. It exits without writing an output file whenever it issues any error whatsoever. So this one is for when you want the file anyway ๐Ÿฅบ
  • -nostdlib โ€” only searches library directories that were explicitly specified on the command line. ๐Ÿ“š Library directories specified in linker scripts (including linker scripts specified on the command line) get ignored. Totally ignored, no cap ๐Ÿ™ƒ
  • --oformat=output-format โ€” ld may be configured to support more than one kind of object file. ๐Ÿงฉ If yours is set up that way, you can use --oformat to specify the binary format for the output object file. Even when ld supports alternative object formats, you usually don't need to specify this, because ld should be configured to produce the most usual format on each machine as the default. output-format is a text string, the name of a particular format supported by the BFD libraries. (You can list the available binary formats with objdump -i. ๐Ÿ”Ž) The script command "OUTPUT_FORMAT" can also specify the output format, but this option overrides it. Override energy ๐Ÿ’…
  • --out-implib file โ€” creates an import library in file that matches the executable the linker is generating (eg. a DLL or ELF program). ๐ŸŽ This import library, which should be called "*.dll.a" or "*.a" for DLLs, can be used to link clients against the generated executable. This means you can skip a separate import library creation step (eg. "dlltool" for DLLs). โœจ This option is only available for the i386 PE and ELF targetted ports of the linker. Very niche, very specific, very iconic ๐ŸŒˆ

okay so this part is where the options get spicy ๐ŸŒถ๏ธ bestie, pay attention ๐Ÿ’…

  • -pie, --pic-executable โ€” okay this one makes a position independent executable ๐Ÿฆ‹ It's only supported on ELF platforms rn. The dynamic linker relocates it to whatever virtual address the OS picks, and that address can change between runs โœจ It's marked ET_DYN in the ELF file header, but it's not a shared library, it's giving different energy in a bunch of ways. The big one: defined symbols in a PIE by default can't be overridden by another object, unlike in a shared library ๐Ÿ™ˆ
  • -no-pie โ€” makes a position dependent executable instead ๐ŸŽ€ This is the default, so you literally don't need to ask for it ๐Ÿ’€
  • -qmagic โ€” ignored, purely for Linux compatibility ๐Ÿง It does nothing, babe.
  • -Qy โ€” also ignored, but this one's for SVR4 compatibility ๐Ÿงš
  • --relax, --no-relax โ€” this one has machine dependent effects, so it's a bit of a vibe check ๐Ÿ”ฅ It's only supported on a few targets. On some platforms, --relax does target specific, global optimizations once the linker has resolved addressing in the program. We're talking relaxing address modes, synthesizing new instructions, picking shorter versions of current instructions, and combining constant values. Lowkey genius ๐Ÿง 

Heads up though: on some platforms these link time global optimizations can make symbolic debugging of the resulting executable impossible ๐Ÿ˜ญ This is known to be the case for the Matsushita MN10200 and MN10300 family of processors.

Where the feature is supported, --no-relax turns it off. Where it isn't supported, both --relax and --no-relax are accepted and just ignored, no drama โœจ

  • --retain-symbols-file=filename โ€” keeps only the symbols listed in the file filename and tosses everything else ๐Ÿ—‘๏ธ filename is just a flat file with one symbol name per line. This is super useful in environments like VxWorks, where a huge global symbol table builds up gradually, so it helps conserve run-time memory ๐Ÿ’œ

--retain-symbols-file does not discard undefined symbols, or symbols needed for relocations, so those stay safe ๐Ÿซถ

You can only specify --retain-symbols-file once on the command line. It overrides -s and -S, so it's the boss ๐Ÿ‘‘

  • -rpath=dir โ€” adds a directory to the runtime library search path ๐ŸŒธ This is used when linking an ELF executable with shared objects. All -rpath arguments get concatenated and passed to the runtime linker, which uses them to find shared objects at runtime.

The -rpath option is also used when locating shared objects that are needed by shared objects explicitly included in the link. See the description of the -rpath-link option for that one ๐Ÿฆ‹ This way of searching -rpath is only supported by native linkers and cross linkers that were configured with the --with-sysroot option.

If -rpath isn't used when linking an ELF executable, the contents of the environment variable "LD_RUN_PATH" get used, as long as it's defined ๐Ÿ“

-rpath also works on SunOS. By default there, the linker forms a runtime search path out of all the -L options it's given. But if you use a -rpath option, the runtime search path gets formed exclusively from the -rpath options, and the -L options get ignored. This is handy when using gcc, since gcc adds a ton of -L options that might be on NFS mounted file systems ๐Ÿ™ˆ

For compatibility with other ELF linkers: if the -R option is followed by a directory name instead of a file name, it gets treated as -rpath ๐Ÿ’…

  • -rpath-link=dir โ€” okay this is the one that gets complicated ๐Ÿ˜ญ With ELF or SunOS, one shared library may need another. That happens when an "ld -shared" link includes a shared library as one of the input files.

When the linker hits this kind of dependency during a non-shared, non-relocatable link, it'll automatically try to find the required shared library and include it, if it isn't already included explicitly. Several directories get searched for that, as described below. The -rpath-link option specifies the first set of directories to search. You can give a sequence of directory names either as a list separated by colons, or by writing the option multiple times โœจ

The tokens $ORIGIN and $LIB can show up in these search directories. $ORIGIN gets replaced by the full path to the directory containing the program or shared object. $LIB gets replaced by either lib - for 32-bit binaries or lib64 - for 64-bit binaries ๐ŸŒˆ

The alternative form of these tokens, ${ORIGIN} and ${LIB}, works too. The token $PLATFORM is not supported, sorry bestie ๐Ÿฅฒ

Use --rpath-link with caution!! It overrides the search path that may have been hard compiled into a shared library. So you could accidentally end up using a different search path than the runtime linker would have used ๐Ÿ’€

When more shared libraries are needed, the linker searches directories in the order listed below to find them. Important bit: this only applies to additional libraries needed to satisfy shared libraries that are already included. It does not apply to libraries pulled in with the -l command line option. Searches for -l libraries only happen in directories specified by the -L option ๐Ÿซถ

  1. Any directories specified by -rpath-link options.
  2. Any directories specified by -rpath options. The difference between -rpath and -rpath-link is that directories given with -rpath get included in the executable and used at runtime, whereas -rpath-link only matters at link time. Searching -rpath this way is only supported by native linkers and cross linkers configured with the --with-sysroot option.
  3. On an ELF system, for native linkers, if neither -rpath nor -rpath-link was used, search the contents of the environment variable "LD_RUN_PATH".
  4. On SunOS, if -rpath wasn't used, search any directories specified with -L options.
  5. For a native linker, search the contents of the environment variable "LD_LIBRARY_PATH".
  6. For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library get searched for shared libraries that it needs. The "DT_RPATH" entries get ignored if "DT_RUNPATH" entries exist โœจ
  7. For a linker on a Linux system: if the file /etc/ld.so.conf exists, search the list of directories found in that file. Note: the path to this file gets prefixed with the "sysroot" value, if that's defined, and then with any "prefix" string if the linker was configured with the --prefix=<path> option ๐Ÿงš
  8. For a native linker on a FreeBSD system, search any directories specified by the "_PATH_ELF_HINTS" macro defined in the elf-hints.h header file.
  9. Any directories specified by a "SEARCH_DIR" command in a linker script given on the command line, including scripts specified by -T (but not -dT).
  10. The default directories, normally /lib and /usr/lib.
  11. Any directories specified by a plugin's LDPT_SET_EXTRA_LIBRARY_PATH.
  12. Any directories specified by a "SEARCH_DIR" command in a default linker script.

Okay, but there's an extra caveat on Linux based systems ๐Ÿง If the --as-needed option is active, and a shared library gets located that would normally satisfy the search, and that library does not have a DT_NEEDED tag for libc.so, AND there's a shared library later in the set of search directories that also satisfies the search and does have a DT_NEEDED tag for libc.so, then the second library gets picked instead of the first. Chaotic, I know ๐Ÿ˜ญ

If the required shared library isn't found, the linker issues a warning and keeps going with the link โš ๏ธ

  • --section-ordering-file=script โ€” this one augments the current linker script with extra mapping of input sections to output sections ๐ŸŒธ The file has to use the same syntax for "SECTIONS" as normal linker scripts do, but it should only place input sections into output sections and nothing else. @pxref{SECTIONS}

Second constraint: the section ordering script can only reference output sections that are already defined by whichever linker script is currently in use (the default linker script, or a script given on the command line). The perk is that input sections get mapped to the start of the output sections, so they can control the ordering inside them โœจ Say the default linker script looks like this:

        SECTIONS {
          .text : { *(.text.hot) ; *(.text .text.*) }
          .data : { *(.data.big) ; *(.data .data.*) }
          }

And then you use a section ordering file like this:

          .text : { *(.text.first) ; *(.text.z*) }
          .data : { foo.o(.data.first) ; *(.data.small) }

That's giving the same result as a linker script like this:

        SECTIONS {
          .text : { *(.text.first) ; *(.text.z*) ; *(.text.hot) ; *(.text .text.*) }
          .data : { foo.o(.data.first) ; *(.data.small) ; *(.data.big) ; *(.data .data.*) }
          }

The advantage of the section ordering file is that you can order just the sections you care about, without having to worry about every other section, memory region, or anything else ๐Ÿ’–

  • -shared, -Bshareable โ€” creates a shared library ๐ŸŽ€ This is currently only supported on ELF, XCOFF and SunOS platforms. On SunOS, the linker automatically creates a shared library if the -e option isn't used and there are undefined symbols in the link ๐Ÿฆ‹
  • --sort-common, --sort-common=ascending, --sort-common=descending โ€” tells ld to sort the common symbols by alignment, in ascending or descending order, when it places them in the appropriate output sections โœจ The alignments considered are sixteen-byte or larger, eight-byte, four-byte, two-byte, and one-byte. This prevents gaps between symbols caused by alignment constraints ๐Ÿงฉ If you don't specify a sorting order, descending order is assumed ๐Ÿ’œ
  • --sort-section=name โ€” applies "SORT_BY_NAME" to all wildcard section patterns in the linker script ๐Ÿ“

โœจ OPTIONS โœจ (cont.)

okay so bestie, this next bit is all about how the linker handles sections, stats, and symbols ๐Ÿ’…

  • --sort-section=alignment โ€” this one tells the linker to apply SORT_BY_ALIGNMENT to every wildcard section pattern in the linker script ๐Ÿฆ‹
  • --spare-dynamic-tags=count โ€” sets how many empty slots get left in the .dynamic section of ELF shared objects ๐ŸŽ€ those slots are handy for post processing tools, like the prelinker. the default is 5 โœจ
  • --split-by-file[=size] โ€” kinda like --split-by-reloc, but it makes a brand new output section for each input file once size is reached ๐ŸŒธ if you don't give size, it defaults to 1 ๐Ÿ’–
  • --split-by-reloc[=count] โ€” this one tries to make extra sections in the output file so no single section has more than count relocations ๐Ÿ”ฅ it's super useful when you're making huge relocatable files for downloading into certain real time kernels that use the COFF object file format, since COFF can't hold more than 65535 relocations in one section ๐Ÿ˜ญ heads up: this will fail with object file formats that don't support arbitrary sections ๐Ÿ’€ and the linker won't split up individual input sections, so if one input section has more than count relocations, its output section will have that many too ๐Ÿ™ˆ count defaults to 32768
  • --stats[=filename] โ€” computes and shows stats about how the linker is doing, like execution time and memory usage ๐Ÿ“Š
    • no filename? you just get basic info, sent to the standard error output stream ๐ŸŒˆ
    • give it a filename and you get the extended info written to that file โœจ
    • if filename is just -, the extended info goes to the standard output stream ๐Ÿซถ
    • if filename starts with +, the file gets opened in append mode instead of overwrite mode ๐Ÿงš
    • > Note: if both --stats and -Map have filename arguments and they match, the info only gets written once, not twice ๐Ÿ’œ
    • if the LD_STATS environment variable is defined, this behaves like --stats ๐Ÿ“ if its value is a string, that string becomes the name of the file the info gets recorded in. otherwise it goes to the standard output stream. using the env var means you can record stats without touching the linker's command line ๐Ÿ”ฅ
    • > Note: if both the env var and --stats are used, --stats wins ๐Ÿ‘‘
    • -Map: if -Map is enabled, the info gets recorded in the map file too ๐Ÿฆ‹
    • the extended info includes cpu time, and if the getrusage() system library call is available, memory use gets recorded too ๐Ÿ’… this info is reported for individual parts of the linking process, called phases ๐ŸŒธ there's also a special phase called ALL that covers the entire linking process โœจ
    • careful: phases can contain or overlap with each other, so you can't assume the overall resources used by the linker is the sum of the phases ๐Ÿ™ˆ
    • extended info also includes the linker version, command line arguments, and linker start time ๐ŸŽ€ this is clutch when a build system is running multiple links, so you can figure out exactly which arguments made the stats you're looking at ๐Ÿซถ
    • here's what the extended output looks kinda like:
        Stats: linker version: (GNU Binutils) 2.44.50.20250401
        Stats: linker started: Wed Apr  2 09:36:41 2025
        Stats: args: ld -z norelro -z nomemory-seal -z no-separate-code -o a.out [...]

        Stats: phase               cpu time    memory      user time    system time
        Stats: name                (microsec)   (KiB)      (seconds)      (seconds)
        Stats: ALL                   390082    217740              0              0
        Stats: ctf processing            12         0              0              0
        Stats: string merge            1324         0              0              0
        Stats: parsing                  349       288              0              0
        Stats: plugins                    1         0              0              0
        Stats: processing files      259616    214524              0              0
        Stats: write                 116493         0              0              0
  • --no-stats โ€” turns off the usage stats reporting, if it was turned on by the --stats option or the LD_STATS env var ๐Ÿ™…โ€โ™€๏ธ
  • --sysroot=directory โ€” uses directory as the sysroot location, overriding the default set at configure time ๐Ÿ—บ๏ธ only linkers configured with --with-sysroot support this one ๐Ÿ’–
  • --task-link โ€” used by COFF/PE based targets to make a task-linked object file, where all the global symbols get turned into statics ๐ŸŒˆ
  • --traditional-format โ€” for some targets, ld's output is different in some ways from an existing linker's output ๐Ÿ˜ญ this switch tells ld to use the traditional format instead ๐ŸŽ€
    • example: on SunOS, ld combines duplicate entries in the symbol string table, which can shrink an output file with full debugging info by over 30 percent โœจ but the SunOS "dbx" program can't read that result (gdb has no trouble tho ๐Ÿ’…) so --traditional-format tells ld not to combine duplicate entries ๐Ÿซถ
  • --section-start=sectionname=org โ€” places a section in the output file at the absolute address org ๐Ÿ“ use this as many times as you need to place multiple sections ๐Ÿฆ‹ org has to be a single hexadecimal integer, but for compatibility with other linkers you can skip the leading 0x ๐Ÿงš
    • > Note: there should be no white space between sectionname, the equals sign (=), and org ๐Ÿ™ˆ
  • --image-base=org โ€” for ELF, this is the same as -Ttext-segment, and both set the base address of the ELF executable ๐Ÿ’œ
    • for PE, value is the base address of your program or dll ๐Ÿ”ฅ it's the lowest memory location used when your program or dll is loaded ๐ŸŒธ to cut down on relocating and boost dll performance, each dll should have a unique base address that doesn't overlap any other dll โœจ the default is 0x400000 for executables and 0x10000000 for dlls ๐Ÿ‘‘
  • -Tbss=org, -Tdata=org, -Ttext=org โ€” same as --section-start, just with .bss, .data, or .text as the sectionname ๐Ÿ’…
  • -Ttext-segment=org โ€” when making an ELF executable, this sets the address of the first byte of the first segment ๐ŸŒˆ heads up: if you use -pie with -Ttext-segment=org, the output executable gets marked ET_EXEC, so the first byte of the text segment is guaranteed to be org at run time ๐Ÿ˜ญ
  • -Trodata-segment=org โ€” when making an ELF executable or shared object for a target where the read-only data lives in its own segment separate from the executable text, this sets the address of the first byte of the read-only data segment ๐Ÿฆ‹
  • -Tldata-segment=org โ€” when making an ELF executable or shared object for the x86-64 medium memory model, this sets the address of the first byte of the ldata segment ๐Ÿงš
  • --unresolved-symbols=method โ€” decides how unresolved symbols get handled ๐Ÿ” there are four possible values for method:
    • ignore-all โ€” don't report any unresolved symbols ๐Ÿ™ˆ
    • report-all โ€” report all unresolved symbols. this is the default ๐Ÿ’–
    • ignore-in-object-files โ€” report unresolved symbols contained in shared libraries, but ignore them if they come from regular object files ๐ŸŒธ
    • ignore-in-shared-libs โ€” report unresolved symbols from regular object files, but ignore them if they come from shared libraries ๐ŸŽ€ this can be super handy when making a dynamic binary and you know all the shared libraries it should reference are on the linker's command line โœจ
    • the behaviour for shared libraries on their own can also be controlled by the --[no-]allow-shlib-undefined option ๐Ÿซถ
    • normally the linker throws an error message for each reported unresolved symbol, but --warn-unresolved-symbols can turn those into warnings instead โš ๏ธ
  • --dll-verbose, --verbose[=NUMBER] โ€” shows the version number for ld and lists the linker emulations it supports ๐Ÿ“ฃ it also shows which input files can and can't be opened, and the linker script being used ๐Ÿ“œ if the optional NUMBER is greater than 1, the plugin symbol status gets displayed too ๐Ÿ”ฅ
  • --version-script=version-scriptfile โ€” tells the linker the name of a version script ๐Ÿงพ this is usually used when making shared libraries, to add extra info about the version hierarchy for the library ๐Ÿ’œ it's only fully supported on ELF platforms that support shared libraries (see VERSION) ๐ŸŒˆ it's partially supported on PE platforms, which can use version scripts to filter symbol visibility in auto-export mode: any symbols marked local in the version script won't get exported ๐Ÿ™ˆ
  • --warn-common โ€” warns when a common symbol gets combined with another common symbol or with a symbol definition ๐Ÿšจ unix linkers allow this slightly sloppy practice, but linkers on some other operating systems don't ๐Ÿ˜ญ this option helps you find potential problems from combining global symbols ๐Ÿ” unfortunately some C libraries do this, so you might get warnings about symbols in the libraries as well as in your programs ๐Ÿ’€

there are three kinds of global symbols, shown here with C examples:

  • int i = 1; โ€” a definition, which goes in the initialized data section of the output file ๐ŸŽ€
  • extern int i; โ€” an undefined reference, which doesn't allocate space. there must be either a definition or a common symbol for the variable somewhere ๐Ÿงš

int i;

okay so this is a common symbol ๐Ÿ’… If there's only one or more common symbols for a variable, it gets tucked into the uninitialized data area of the output file ๐Ÿงš The linker merges multiple common symbols for the same variable into one single symbol ๐Ÿซถ If they're different sizes, it picks the largest one, no cap ๐Ÿ”ฅ And if there's an actual definition for that same variable, the linker turns the common symbol into a declaration ๐ŸŒธ

The --warn-common option can produce five kinds of warnings ๐Ÿ˜ญ Each warning comes as a pair of lines. The first line describes the symbol you just ran into, and the second describes the previous symbol that had the same name ๐Ÿ’œ At least one of those two symbols will be a common symbol, periodt.

  1. Turning a common symbol into a reference, because there's already a definition for the symbol ๐Ÿ‘‘
<file>(<section>): warning: common of `<symbol>'
   overridden by definition
<file>(<section>): warning: defined here
  1. Turning a common symbol into a reference, because a later definition for the symbol shows up ๐Ÿฆ‹ It's the same as the one above, just with the symbols showing up in a different order โœจ
<file>(<section>): warning: definition of `<symbol>'
   overriding common
<file>(<section>): warning: common is here
  1. Merging a common symbol with a previous common symbol of the same size ๐ŸŽ€
<file>(<section>): warning: multiple common
   of `<symbol>'
<file>(<section>): warning: previous common is here
  1. Merging a common symbol with a previous, larger common symbol ๐Ÿ“
<file>(<section>): warning: common of `<symbol>'
   overridden by larger common
<file>(<section>): warning: larger common is here
  1. Merging a common symbol with a previous, smaller common symbol ๐ŸŒˆ This is the same as the one above, except the symbols show up in a different order ๐Ÿ™ˆ
<file>(<section>): warning: common of `<symbol>'
   overriding smaller common
<file>(<section>): warning: smaller common is here

๐Ÿ’– --warn-constructors ๐Ÿ’–

Warns you if any global constructors get used ๐Ÿงš This is only useful for a few object file formats, though. For formats like COFF or ELF, the linker literally can't detect when global constructors are being used ๐Ÿ’€


โœจ --warn-execstack, --warn-execstack-objects, --no-warn-execstack โœจ

okay so on ELF platforms, the linker may generate warning messages if you ask it to make an output file with an executable stack ๐Ÿ˜ญ There are three possible states:

  1. Don't generate any warnings at all ๐Ÿซถ
  2. Always generate warnings, even if the executable stack is requested with the -z execstack command line option ๐Ÿ”ฅ
  3. Only generate a warning if an object file requests an executable stack, but not if the -z execstack option is used ๐Ÿฆ‹

The default state depends on how the linker was configured when it was built ๐ŸŽ€ The --no-warn-execstack option always puts the linker into the no-warnings state ๐Ÿ™ˆ The --warn-execstack option puts it into the warn-always state ๐Ÿ’œ And the --warn-execstack-objects option puts it into the warn-for-object-files-only state ๐Ÿ‘‘

Note: ELF input files can say they need an executable stack by having a .note.GNU-stack section with the executable bit set in its section flags ๐Ÿ˜ญ They can say they don't need one by having that same section without the executable flag bit set. If an input file doesn't have a .note.GNU-stack section, the default behaviour is target specific ๐Ÿ’€ For some targets, a missing section means an executable stack is required, which is often a problem for hand crafted assembler files.

๐Ÿšจ --error-execstack, --no-error-execstack ๐Ÿšจ

If the linker is about to generate a warning about an executable stack, --error-execstack turns that warning into an error instead ๐Ÿ”ฅ Heads up, though: this option does not change the linker's execstack warning generation state ๐Ÿ™ˆ If you want to set a specific warning state, use --warn-execstack or --warn-execstack-objects instead.

--no-error-execstack brings back the default behaviour of generating warning messages ๐ŸŒธ


๐ŸŒท --warn-multiple-gp ๐ŸŒท

Warns you if multiple global pointer values are required in the output file ๐Ÿงš This only matters for certain processors, like the Alpha. Some processors put large-valued constants in a special section, and a special register (the global pointer) points into the middle of that section so constants can be loaded efficiently through a base-register relative addressing mode ๐Ÿซถ The offset in that mode is fixed and pretty small (e.g., 16 bits), which limits how big the constant pool can get. So in large programs you often need multiple global pointer values to reach all the possible constants ๐Ÿ˜ญ This option makes the linker issue a warning whenever that happens โœจ


๐ŸŒ™ --warn-once ๐ŸŒ™

Only warns once for each undefined symbol, instead of once per module that refers to it ๐Ÿ’…


๐Ÿ”ฅ --warn-rwx-segments, --no-warn-rwx-segments ๐Ÿ”ฅ

Warns if the linker creates a loadable, non-zero sized segment that has all three of the read, write and execute permission flags set ๐Ÿ’€ That's a potential security vulnerability, so yeah, be careful ๐Ÿ™ˆ Warnings also get generated if a thread local storage segment is created with the execute permission flag set, whether or not it has the read and/or write flags ๐Ÿฆ‹

These warnings are enabled by default ๐ŸŽ€ You can disable them with --no-warn-rwx-segments and turn them back on with --warn-rwx-segments ๐Ÿซถ


๐Ÿšซ --error-rwx-segments, --no-error-rwx-segments ๐Ÿšซ

If the linker is about to generate a warning about an executable, writeable segment, or an executable TLS segment, --error-rwx-segments will turn that warning into an error instead ๐Ÿ’– --no-error-rwx-segments restores the default behaviour, which is just generating a warning message ๐ŸŒˆ

Note - --error-rwx-segments doesn't turn on these warnings by itself ๐Ÿ˜ญ They're either enabled by default (if the linker was configured that way) or switched on with the --warn-rwx-segments command line option.

๐Ÿ“ --warn-section-align ๐Ÿ“

Warns if the address of an output section gets changed because of alignment ๐Ÿงš Usually the alignment is set by an input section ๐Ÿ’œ The address only changes if it wasn't explicitly specified, meaning the SECTIONS command doesn't give the section a start address โœจ


โš ๏ธ --warn-textrel โš ๏ธ

Warns if the linker adds DT_TEXTREL to a position-independent executable or a shared object ๐Ÿ™ˆ


๐Ÿงฌ --warn-alternate-em ๐Ÿงฌ

Warns if an object has alternate ELF machine code ๐Ÿ‘‘


๐Ÿ” --warn-unresolved-symbols ๐Ÿ”

If the linker is about to report an unresolved symbol (see the option --unresolved-symbols), it'll normally throw an error ๐Ÿ˜ญ This option makes it give a warning instead ๐Ÿซถ


๐Ÿ›ก๏ธ --error-unresolved-symbols ๐Ÿ›ก๏ธ

Restores the linker's default behaviour of throwing errors when it reports unresolved symbols ๐Ÿ’…


๐Ÿ“ฆ --whole-archive ๐Ÿ“ฆ

For each archive mentioned on the command line after --whole-archive, the linker includes every object file in that archive in the link, instead of searching the archive for the object files it actually needs ๐Ÿ”ฅ This is normally used to turn an archive file into a shared library, forcing every object into the resulting shared library ๐ŸŒธ You can use this option more than once ๐Ÿฆ‹

Two notes if you're using this option from gcc ๐Ÿ™ˆ First, gcc doesn't know about this option, so you have to write -Wl,-whole-archive ๐Ÿ˜ญ Second, don't forget to add -Wl,-no-whole-archive after your list of archives, because gcc adds its own list of archives to your link and you probably don't want this flag affecting those too ๐Ÿ’€


๐ŸŽญ --wrap=symbol ๐ŸŽญ

Uses a wrapper function for symbol ๐Ÿงš Any undefined reference to symbol gets resolved to "__wrap_symbol", and any undefined reference to "__real_symbol" gets resolved to symbol ๐Ÿ’–

This is handy for wrapping a system function ๐Ÿ’… The wrapper function should be called "__wrap_symbol". If it wants to call the system function, it should call "__real_symbol" ๐Ÿซถ

Here's a trivial example:

void *
__wrap_malloc (size_t c)
{
  printf ("malloc called with %zu\n", c);
  return __real_malloc (c);
}

If you link other code with this file using --wrap malloc, then all calls to "malloc" will call the function "__wrap_malloc" instead ๐ŸŒˆ The call to "__real_malloc" inside "__wrap_malloc" will call the real "malloc" function ๐Ÿ‘‘

You might want to provide a "__real_malloc" function too, so that links without the --wrap option will still succeed ๐ŸŽ€ If you do this, don't put the definition of "__real_malloc" in the same file as "__wrap_malloc". If you do, the assembler may resolve the call before the linker gets a chance to wrap it to "malloc" ๐Ÿ˜ญ

Only undefined references get replaced by the linker ๐Ÿ™ˆ So translation unit internal references to symbol are not resolved to "__wrap_symbol". In the next example, the call to "f" in "g" is not resolved to "__wrap_f" ๐Ÿ’œ

int
f (void)
{
  return 123;
}

int
g (void)
{
  return f();
}

๐Ÿงญ --eh-frame-hdr, --no-eh-frame-hdr ๐Ÿงญ

--eh-frame-hdr requests the creation of the ".eh_frame_hdr" section and the ELF "PT_GNU_EH_FRAME" segment header ๐Ÿฆ‹ --no-eh-frame-hdr suppresses them ๐ŸŒธ


๐Ÿช„ --no-ld-generated-unwind-info ๐Ÿช„

Asks for no creation of ".eh_frame" unwind info for linker generated code sections, like PLT โœจ This option is on by default if linker generated unwind info is supported ๐Ÿ’… It also controls the generation of ".sframe" stack trace info for linker generated code sections, like PLT ๐Ÿซถ

This linker can create the new dynamic tags in ELF, but older ELF systems may not understand them ๐Ÿฅบ.

  • --enable-new-dtags, --disable-new-dtags โ€” --enable-new-dtags makes the new dynamic tags get created as needed, and older dynamic tags get dropped โœจ. --disable-new-dtags means no new dynamic tags get created at all ๐Ÿ™…โ€โ™€๏ธ. By default, the new dynamic tags are not created. These options only work on ELF systems ๐Ÿ’œ.
  • --hash-size=number โ€” Sets the default size of the linker's hash tables to a prime number close to number ๐Ÿ”ข. Making it bigger can make the linker finish its tasks faster, but it eats more memory. Making it smaller saves memory, but the linker gets slower ๐Ÿข.
  • --hash-style=style โ€” Picks the type of hash table(s) the linker uses ๐Ÿง. style can be "sysv" for the classic ELF ".hash" section, "gnu" for the newer GNU ".gnu.hash" section, or "both" for both of them together ๐Ÿ‘‘. The default depends on how the linker was configured, but on most Linux systems it's "both" ๐Ÿซถ.
  • --compress-debug-sections=none, --compress-debug-sections=zlib, --compress-debug-sections=zlib-gnu, --compress-debug-sections=zlib-gabi, --compress-debug-sections=zstd โ€” On ELF platforms, these decide how DWARF debug sections get compressed ๐Ÿ—œ๏ธโœจ.

--compress-debug-sections=none means the DWARF debug sections don't get compressed at all ๐Ÿ™…โ€โ™€๏ธ. --compress-debug-sections=zlib-gnu compresses them and renames them to start with .zdebug instead of .debug ๐Ÿท๏ธ. --compress-debug-sections=zlib-gabi also compresses them, but instead of renaming, it sets the SHF_COMPRESSED flag in the sections' headers ๐Ÿฉท.

--compress-debug-sections=zlib is just an alias for --compress-debug-sections=zlib-gabi ๐Ÿ‘ฏโ€โ™€๏ธ. And --compress-debug-sections=zstd compresses DWARF debug sections using zstd โšก.

Heads up bestie: this option overrides any compression already in the input debug sections ๐Ÿšจ. So if you link with --compress-debug-sections=none, for example, any compressed debug sections in the input files get uncompressed before they're copied into the output binary ๐Ÿ˜ญ.

The default compression behaviour changes depending on the target and the configure options used to build the toolchain ๐Ÿคทโ€โ™€๏ธ. To find the default, check the output from the linker's --help option ๐Ÿ”.

  • --reduce-memory-overheads โ€” Lowers the memory the linker needs while it runs, but linking gets slower as a tradeoff ๐ŸŒ. This was added to pick the old O(n^2) algorithm for link map file generation, instead of the new O(n) algorithm, which uses about 40% more memory for symbol storage ๐Ÿง .

The switch also sets the default hash table size to 1021, which saves more memory but makes the linker's run time longer ๐Ÿ’…. That doesn't happen if the --hash-size switch has been used, though ๐Ÿ™ˆ. The --reduce-memory-overheads switch may also be used to enable other tradeoffs in future versions of the linker ๐Ÿ”ฎ.

  • --max-cache-size=size โ€” By default, ld caches the relocation information and symbol tables of input files in memory with no size limit ๐Ÿซ . This option sets the maximum cache size to size ๐Ÿ“.
  • --build-id, --build-id=style โ€” Asks the linker to create a ".note.gnu.build-id" ELF note section, or a ".buildid" COFF section ๐Ÿ†”. The note holds unique bits that identify the linked file โœจ. style can be "uuid" for 128 random bits, "sha1" for a 160-bit SHA1 hash, "md5" for a 128-bit MD5 hash, or "xx" for a 128-bit XXHASH on the normative parts of the output contents ๐Ÿงฉ. You can also use "0xhexstring" to pass your own bit string as an even number of hexadecimal digits, where "-" and ":" characters between digit pairs get ignored ๐ŸŽ€. If style is left out, "sha1" is used ๐Ÿ’œ.

The "md5", "sha1", and "xx" styles always produce the same identifier for an identical output file, and they're almost certainly unique among all nonidentical output files ๐Ÿฅน. They're not meant to be compared as a checksum for the file's contents, though ๐Ÿ™…โ€โ™€๏ธ. A linked file can be changed later by other tools, but the build ID bit string identifying the original linked file won't change ๐Ÿ”’.

Passing "none" for style disables the setting from any earlier --build-id options on the command line ๐Ÿšซ.

  • --package-metadata=JSON โ€” Asks the linker to create a ".note.package" ELF note section ๐Ÿ“ฆ. The note's contents are in JSON format, following the package metadata specification. For more info, check out https://systemd.io/ELF_PACKAGE_METADATA/ ๐ŸŒ.

The JSON argument supports percent-encoding and following %[string] encoding, where string refers to the name in HTML's Named Character References ๐Ÿค“. Here's the list: %[comma] for ,, %[lbrace] for {, %[quot] for ", %[rbrace] for }, and %[space] for a space character ๐ŸŽ€.

If the JSON argument is missing or empty, this disables creating the metadata note, but only if an earlier --package-metadata option turned it on ๐Ÿ™…โ€โ™€๏ธ. If the linker was built with libjansson, the JSON string gets validated too โœ….


The i386 PE linker supports the -shared option, which makes the output a dynamically linked library (DLL) instead of a normal executable ๐Ÿฆ‹. When you use this option, name the output "*.dll" ๐Ÿ“. The linker also fully supports standard "*.def" files ๐Ÿ’…. You can give them on the linker command line just like an object file โœจ. Fun fact: they should come before any archives they export symbols from, so that they get linked in, just like a normal object file ๐Ÿงš.

On top of the options common to all targets, the i386 PE linker supports extra command-line options that are specific to the i386 PE target ๐ŸŒธ. Options that take values can be separated from their values by either a space or an equals sign ๐Ÿ“.

  • --add-stdcall-alias โ€” If you give this, symbols with a stdcall suffix (@nn) get exported as-is, and also exported with the suffix stripped โœ‚๏ธ. [This option is specific to the i386 PE targeted port of the linker] ๐Ÿ’–
  • --base-file file โ€” Uses file as the name of a file where the base addresses of all the relocations needed for generating DLLs with dlltool get saved ๐Ÿ’พ. [This is an i386 PE specific option] ๐Ÿซถ
  • --dll โ€” Creates a DLL instead of a regular executable ๐ŸŒˆ. You can also use -shared, or specify a "LIBRARY" in a given ".def" file ๐Ÿ“œ. [This option is specific to the i386 PE targeted port of the linker] ๐Ÿ”ฅ
  • --enable-long-section-names, --disable-long-section-names โ€” The PE variants of the COFF object format add an extension that lets section names be longer than eight characters, which is the normal COFF limit ๐Ÿง. By default, these long names are only allowed in object files, because fully-linked executable images don't carry the COFF string table needed to support them ๐Ÿ™ƒ.

Using these two options, you can allow long names in executable images too, as a GNU extension, or disallow them in object files (probably pointlessly!) ๐Ÿคทโ€โ™€๏ธ. Executable images with long section names are slightly non-standard, since they carry a string table, and they may print confusing output when checked with non-GNU PE-aware tools, like file viewers and dumpers ๐Ÿ‘€.

Here's the tea though: GDB relies on PE long section names to find Dwarf-2 debug information sections in an executable image at runtime ๐Ÿ•ต๏ธโ€โ™€๏ธ. So if neither option is given on the command line, ld turns long section names on, overriding the default and technically correct behaviour, whenever it finds debug information while linking an executable image without stripping symbols ๐Ÿซฃ. [This option is valid for all PE targeted ports of the linker] ๐Ÿ’œ

  • --enable-stdcall-fixup, --disable-stdcall-fixup โ€” If the link finds a symbol it can't resolve, it tries "fuzzy linking" ๐Ÿ”. That means it looks for another defined symbol that only differs in the symbol name format (cdecl vs stdcall), and links to that match instead ๐Ÿ’ซ. For example, the undefined symbol "_foo" might get linked to the function "_foo@12", or the undefined symbol "_bar@16" might get linked to the function "_bar" ๐ŸŽญ.

When the linker does this, it prints a warning, since it normally should've failed to link ๐Ÿšจ. Sometimes though, import libraries generated from third-party DLLs need this feature to be usable ๐Ÿฅบ. If you specify --enable-stdcall-fixup, this feature is fully enabled and no warnings get printed ๐Ÿ™Œ. If you specify --disable-stdcall-fixup, the feature is disabled and those mismatches count as errors ๐Ÿ’€. [This option is specific to the i386 PE targeted port of the linker] ๐ŸŒท

  • --leading-underscore, --no-leading-underscore โ€” For most targets, the default symbol prefix is an underscore, and it's defined in the target's description ๐Ÿงšโ€โ™€๏ธ. With this option, you can disable or enable that default underscore symbol prefix ๐Ÿ”.
  • --export-all-symbols โ€” If you give this, all global symbols in the objects used to build a DLL get exported by the DLL ๐Ÿฆข. This is the default if there'd otherwise be no exported symbols at all ๐Ÿซถ. When symbols are explicitly exported via DEF files, or implicitly exported via function attributes, the default is to export nothing else unless this option is given ๐Ÿ™…โ€โ™€๏ธ.

Some symbols are never auto-exported, even with this option: "DllMain@12", "DllEntryPoint@0", "DllMainCRTStartup@12", and "impure_ptr" ๐Ÿšซ. Symbols imported from other DLLs won't be re-exported either, and neither will symbols that specify the DLL's internal layout, like ones beginning with "_head_" or ending with "_iname" ๐Ÿงฑ. Also, no symbols from "libgcc", "libstd++", "libmingw32", or "crtX.o" get exported ๐Ÿ“š. Symbols whose names begin with "__rtti_" or "__builtin_" won't be exported either, to help with C++ DLLs ๐Ÿ’….

Finally, there's a whole list of cygwin-private symbols that don't get exported ๐Ÿ™ˆ. This obviously applies when you're building DLLs for cygwin targets. Those cygwin-excludes are: "_cygwin_dll_entry@12", "_cygwin_crt0_common@8", "_cygwin_noncygwin_dll_entry@12", "_fmode", "_impure_ptr", "cygwin_attach_dll", "cygwin_premain0", "cygwin_premain1", "cygwin_premain2", "cygwin_premain3", and "environ" ๐Ÿซง. [This option is specific to the i386 PE targeted port of the linker] ๐Ÿ’–

Okay so the PE port options keep going, bestie, and it's a LOT ๐Ÿ’…๐ŸŽ€

  • --exclude-symbols symbol,symbol,... โ€” ๐Ÿ™ˆ Gives a list of symbols that should NOT get auto-exported. You can separate the names with commas or colons. [i386 PE targeted port only]
  • --exclude-all-symbols โ€” ๐Ÿšซ Tells the linker to auto-export zero symbols. Nothing gets exported automatically. [i386 PE targeted port only]
  • --file-alignment โ€” ๐Ÿ“ Sets the file alignment. Sections in the file always start at file offsets that are multiples of this number. The default is 512. [i386 PE targeted port only]
  • --heap reserve, --heap reserve,commit โ€” ๐Ÿง  Sets how many bytes of memory get reserved (and optionally committed) to be used as the heap for this program. The default is 1MB reserved and 4K committed. [i386 PE targeted port only]
  • --kill-at โ€” ๐Ÿ’€ If you pass this, the stdcall suffixes (@nn) get stripped off symbols before they're exported. [i386 PE targeted port only]
  • --large-address-aware โ€” ๐Ÿฆ‹ If given, the right bit in the "Characteristics" field of the COFF header gets set, which says this executable can handle virtual addresses bigger than 2 gigabytes. You should pair this with the /3GB or /USERVA=value megabytes switch in the "[operating systems]" section of the BOOT.INI. Without that, this bit does absolutely nothing. [PE targeted ports of the linker only]
  • --disable-large-address-aware โ€” โ†ฉ๏ธ Undoes a previous --large-address-aware. This comes in handy when your compiler driver (like Cygwin gcc) always turns it on, but your executable can't actually handle addresses over 2 gigabytes. [PE targeted ports of the linker only]
  • --major-image-version value โ€” ๐ŸŒธ Sets the major number of the "image version". Defaults to 1. [i386 PE targeted port only]
  • --major-os-version value โ€” ๐ŸŒธ Sets the major number of the "os version". Defaults to 4. [i386 PE targeted port only]
  • --major-subsystem-version value โ€” ๐ŸŒธ Sets the major number of the "subsystem version". Defaults to 4. [i386 PE targeted port only]
  • --minor-image-version value โ€” ๐Ÿ“ Sets the minor number of the "image version". Defaults to 0. [i386 PE targeted port only]
  • --minor-os-version value โ€” ๐Ÿ“ Sets the minor number of the "os version". Defaults to 0. [i386 PE targeted port only]
  • --minor-subsystem-version value โ€” ๐Ÿ“ Sets the minor number of the "subsystem version". Defaults to 0. [i386 PE targeted port only]
  • --output-def file โ€” ๐Ÿ“œ The linker makes a file called file that holds a DEF file for the DLL it's generating. That DEF file (which should be named *.def) can be used to build an import library with dlltool, or as a reference for automatically or implicitly exported symbols. [i386 PE targeted port only]
  • --enable-auto-image-base, --enable-auto-image-base=value โ€” ๐Ÿ—บ๏ธ Picks the image base for DLLs automatically, optionally starting from the base value. This doesn't happen if you already set one with the --image-base argument. The linker hashes the dllname to make a unique image base for each DLL, which means in-memory collisions and relocations (the stuff that can slow your program down on startup) get avoided. [i386 PE targeted port only]
  • --disable-auto-image-base โ€” ๐Ÿ™… Stops the linker from generating a unique image base. If you didn't give a user-specified image base (--image-base), you get the platform default. [i386 PE targeted port only]
  • --dll-search-prefix string โ€” ๐Ÿ” When you link dynamically to a DLL with no import library, the linker looks for "<string><basename>.dll" before it looks for "lib<basename>.dll". This makes it easy to tell apart DLLs built for the different subplatforms: native, cygwin, uwin, pw, and so on. For example, cygwin DLLs usually use --dll-search-prefix=cyg. [i386 PE targeted port only]
  • --enable-auto-import โ€” โœจ Does some fancy linking that maps _symbol to __imp__symbol for DATA imports from DLLs. This means you can skip the dllimport mechanism on the user side and reference unmangled symbol names. [i386 PE targeted port only]

The next bit is the tea on the original auto-import implementation. It's obsolete nowadays for Cygwin and MinGW targets, so you can relax a little ๐Ÿซถ

โš ๏ธ Using the auto-import extension makes the text section of the image file writable. That does NOT follow the PE-COFF format spec Microsoft published, so this one is a little sus.
โš ๏ธ Using auto-import also moves read-only data that would normally live in the .rdata section into the .data section instead. The reason is to dodge a problem with consts, described here: http://www.cygwin.com/ml/cygwin/2004-09/msg01101.html

Generally, auto-import just works ๐Ÿฅฐ but every now and then you might see this message:

"variable '<var>' can't be auto-imported. Please read the documentation for ld's "--enable-auto-import" for details."

This message pops up when some (sub)expression touches an address that's given by the sum of two constants. Win32 import tables only allow ONE constant, so this is the drama ๐Ÿ˜ญ Places where it can happen include touching member fields of struct variables imported from a DLL, and using a constant index into an array variable imported from a DLL. Any multiword variable (arrays, structs, long long, and so on) can trigger this. No matter what data type your exported variable is, ld will always catch it, issue the warning, and exit.

๐Ÿ› ๏ธ The fixes, bestie

There are a few ways to get past this, whatever the data type of your exported variable is.

One way is to use the --enable-runtime-pseudo-reloc switch. This hands the job of adjusting references in your client code over to the runtime environment, so it only works if your runtime environment supports the feature. ๐Ÿงš

A second solution is to make one of the 'constants' into a variable, meaning something the compiler can't know or optimize away at compile time. For arrays you've got two options: a) make the indexee (the array's address) a variable, or b) make the 'constant' index a variable. So it looks like this:

extern type extern_array[];
extern_array[1] -->
   { volatile type *t=extern_array; t[1] }

or

extern type extern_array[];
extern_array[1] -->
   { volatile int t=1; extern_array[t] }

For structs (and most other multiword data types), the only option is to make the struct itself (or the long long, or the ...) a variable:

extern struct s extern_struct;
extern_struct.field -->
   { volatile struct s *t=&extern_struct; t->field }

or

extern long long extern_ll;
extern_ll -->
  { volatile long long * local_ll=&extern_ll; *local_ll }

A third method is to ditch 'auto-import' for the offending symbol and mark it with __declspec(dllimport). In practice though, that means using compile-time #defines to tell whether you're building a DLL, building client code that links to the DLL, or just building or linking to a static library. ๐Ÿ˜ตโ€๐Ÿ’ซ When you're picking between these fixes for the 'direct address with constant offset' problem, think about typical real-world usage.

Here's the before-and-after, so you can see what I mean ๐Ÿ‘€

Original:

--foo.h
extern int arr[];
--foo.c
#include "foo.h"
void main(int argc, char **argv){
  printf("%d\n",arr[1]);
}

Solution 1:

--foo.h
extern int arr[];
--foo.c
#include "foo.h"
void main(int argc, char **argv){
  /* This workaround is for win32 and cygwin; do not "optimize" */
  volatile int *parr = arr;
  printf("%d\n",parr[1]);
}

Solution 2:

--foo.h
/* Note: auto-export is assumed (no __declspec(dllexport)) */
#if (defined(_WIN32) || defined(__CYGWIN__)) && \
  !(defined(FOO_BUILD_DLL) || defined(FOO_STATIC))
#define FOO_IMPORT __declspec(dllimport)
#else
#define FOO_IMPORT
#endif
extern FOO_IMPORT int arr[];
--foo.c
#include "foo.h"
void main(int argc, char **argv){
  printf("%d\n",arr[1]);
}

A fourth way to dodge this whole problem is to re-code your library so it uses a functional interface instead of a data interface for the offending variables (like set_foo() and get_foo() accessor functions). Iconic, honestly ๐Ÿ‘‘

  • --disable-auto-import โ€” ๐Ÿšซ Stops the linker from doing the fancy _symbol to __imp__symbol linking for DATA imports from DLLs. [i386 PE targeted port only]
  • --enable-runtime-pseudo-reloc โ€” ๐Ÿช„ If your code has the expressions from the --enable-auto-import section (that's DATA imports from a DLL with a non-zero offset), this switch builds a vector of 'runtime pseudo relocations'. The runtime environment can use that vector to adjust references to that data in your client code. [i386 PE targeted port only]
  • --disable-runtime-pseudo-reloc โ€” ๐Ÿ™…โ€โ™€๏ธ Doesn't create pseudo relocations for DATA imports with a non-zero offset from DLLs. [i386 PE targeted port only]
  • --enable-extra-pe-debug โ€” ๐Ÿ”Ž Shows extra debug info about auto-import symbol thunking. [i386 PE targeted port only]
  • --section-alignment โ€” Okay so this one sets the section alignment bestie ๐Ÿงš Sections in memory will always start at addresses that are a multiple of this number. The default is 0x1000. (Only in the i386 PE targeted port of the linker, fr.)
  • --stack reserve, --stack reserve,commit โ€” Here you get to decide how many bytes of memory to reserve (and optionally commit) to use as stack for your program ๐Ÿฅž The default is 2MB reserved and 4K committed. (i386 PE targeted port only.)
  • --subsystem which, --subsystem which:major, --subsystem which:major.minor โ€” This picks the subsystem your program runs under ๐Ÿ‘‘ The legal values for which are "native", "windows", "console", "posix", and "xbox". You can also set the subsystem version if you're feeling extra ๐Ÿ’… Numeric values work for which too. (i386 PE targeted port only.)

The following options set flags in the DllCharacteristics field of the PE file header. (These are specific to the PE targeted ports of the linker, so only those will care.) ๐Ÿฆ‹

  • --high-entropy-va, --disable-high-entropy-va โ€” Makes the image compatible with 64-bit address space layout randomization (ASLR) ๐ŸŒˆ This is enabled by default for 64-bit PE images in MinGW targets. Turning this on also implies --dynamicbase and --enable-reloc-section.
  • --dynamicbase, --disable-dynamicbase โ€” The image base address can get relocated using ASLR, which was introduced with MS Windows Vista for i386 PE targets ๐ŸŽ€ It's enabled by default for MinGW targets, but you can turn it off with --disable-dynamicbase. This option also implies --enable-reloc-section.
  • --forceinteg, --disable-forceinteg โ€” Code integrity checks get enforced. This one is disabled by default, so you have to ask for it ๐Ÿ™ˆ
  • --nxcompat, --disable-nxcompat โ€” The image is compatible with Data Execution Prevention ๐Ÿ›ก๏ธ This feature came in with MS Windows XP SP2 for i386 PE targets. It's enabled by default for MinGW targets.
  • --no-isolation, --disable-no-isolation โ€” Even though the image understands isolation, this tells the linker not to isolate the image. Disabled by default.
  • --no-seh, --disable-no-seh โ€” The image does not use SEH, so no SE handler may be called from this image ๐Ÿ™…โ€โ™€๏ธ Disabled by default.
  • --no-bind, --disable-no-bind โ€” Do not bind this image. Disabled by default.
  • --wdmdriver, --disable-wdmdriver โ€” The driver uses the MS Windows Driver Model. Disabled by default.
  • --tsaware, --disable-tsaware โ€” The image is Terminal Server aware ๐Ÿ’œ Disabled by default.
  • --insert-timestamp, --no-insert-timestamp โ€” Inserts a real timestamp into the image. This is the default because it matches legacy code, and it means the image keeps working with other proprietary tools ๐Ÿงพ The catch is that you'll get slightly different images every time you link the same sources, which is kinda annoying ngl. If you want binaries from identical sources to compare identically, use --no-insert-timestamp to put a zero value in the timestamp instead ๐ŸคŒ

If --insert-timestamp is active, the time inserted is either the time the linking takes place or, if the SOURCE_DATE_EPOCH environment variable is defined, the number of seconds since Unix epoch as specified by that variable โฐ

  • --enable-reloc-section, --disable-reloc-section โ€” Creates the base relocation table, which is necessary if the image gets loaded at a different image base than the one in the PE header ๐Ÿ—บ๏ธ Enabled by default.

The C6X uClinux target uses a binary format called DSBT to support shared libraries ๐Ÿ“š Each shared library in the system needs its own unique index, and all executables use an index of 0.

  • --dsbt-size size โ€” Sets the number of entries in the DSBT of the current executable or shared library to size. The default is a table with 64 entries.
  • --dsbt-index index โ€” Sets the DSBT index of the current executable or shared library to index. The default is 0, which is right for generating executables. If you generate a shared library with a DSBT index of 0, the R_C6000_DSBT_INDEX relocs get copied into the output file ๐Ÿฆ„

Also, the --no-merge-exidx-entries switch disables merging of adjacent exidx entries in frame unwind info.

  • --branch-stub โ€” Turns on linker branch relaxation by inserting branch stub sections when they're needed to extend the range of branches ๐ŸŒธ You usually don't need this, since C-SKY supports branch and call instructions that can reach the full memory range, and branch relaxation is normally handled by the compiler or assembler anyway.
  • --stub-group-size=N โ€” Gives you finer control over linker branch stub creation ๐ŸŽ›๏ธ It sets the maximum size of a group of input sections that one stub section can handle. A negative N puts stub sections after their branches, while a positive N lets stub sections go either before or after the branches. The values 1 or -1 tell the linker to pick suitable defaults.

The 68HC11 and 68HC12 linkers support specific options for controlling memory bank switching mapping and trampoline code generation ๐Ÿงธ

  • --no-trampoline โ€” Disables generation of trampolines. By default, a trampoline gets generated for each far function that's called using a jsr instruction (this happens when a pointer to a far function is taken).
  • --bank-window name โ€” Tells the linker the name of the memory region in the MEMORY specification that describes the memory bank window ๐ŸชŸ The linker then uses that region's definition to compute paging and addresses within the memory window.

The following options control GOT generation when linking for 68K targets ๐Ÿงฉ

  • --got=type โ€” Tells the linker which GOT generation scheme to use. type should be one of single, negative, multigot or target. For more info, check the Info entry for ld.

The following options control microMIPS instruction generation and branch relocation checks for ISA mode transitions when linking for MIPS targets ๐Ÿฌ

  • --insn32, --no-insn32 โ€” These pick which microMIPS instructions the linker uses in code it generates, like in the PLT or lazy binding stubs, or during relaxation ๐Ÿ’ซ With --insn32, the linker only uses 32-bit instruction encodings. By default, or if you use --no-insn32, all instruction encodings get used, including 16-bit ones where possible.
  • --ignore-branch-isa, --no-ignore-branch-isa โ€” These control branch relocation checks for invalid ISA mode transitions ๐Ÿšฆ With --ignore-branch-isa, the linker accepts any branch relocations, and any ISA mode transition that's required gets lost in the relocation calculation. The exception is some BAL instructions that meet relaxation conditions, which get converted to equivalent JALX instructions as their relocation is calculated. By default, or with --no-ignore-branch-isa, a check happens, and losing an ISA mode transition produces an error ๐Ÿšจ
  • --compact-branches, --no-compact-branches โ€” These control whether the linker generates compact instructions in the PLT entries for MIPS R6 ๐Ÿญ

For the pdp11-aout target, three variants of the output format can be produced, selected by the following options ๐Ÿงƒ The default variant for pdp11-aout is --omagic, whereas for other targets --nmagic is the default. The --imagic option is only defined for the pdp11-aout target, while the others get described here as they apply to pdp11-aout.

  • -N, --omagic โ€” Marks the output as "OMAGIC" (0407) in the a.out header, which says the text segment is not write-protected and shared ๐Ÿซ  Since the text and data sections are both readable and writable, the data section gets allocated right after the text segment. This is the oldest format for PDP11 executable programs, and it's the default for ld on PDP11 Unix systems from the very beginning through 2.11BSD. Ancient but iconic ๐Ÿ›๏ธ
  • -n, --nmagic โ€” Marks the output as "NMAGIC" (0410) in the a.out header, meaning that when the file runs, the text portion is read-only and shareable among all processes running the same file ๐Ÿ”’ This works by moving the data areas up to the first possible 8K byte page boundary after the end of the text. This option creates a pure executable format โœจ
  • -z, --imagic โ€” Marks the output as "IMAGIC" (0411) in the a.out header, meaning that when the file runs, the program text and data areas get loaded into separate address spaces ๐Ÿชž It uses the split instruction and data space feature of the memory management unit in the larger PDP11 models, which doubles the address space available to the program. The text segment is still pure, write-protected, and shareable. The only output-format difference from the others (besides the magic number) is that both the text and data sections start at location 0. The -z option picked this format in 2.11BSD. This option creates a separate executable format ๐Ÿ’–
  • --no-omagic โ€” Equivalent to --nmagic for pdp11-aout ๐Ÿซถ

โœจ ENVIRONMENT โœจ

okay bestie, ld has three environment variables you can use to change how it behaves: GNUTARGET, LDEMULATION, and COLLECT_NO_DEMANGLE ๐Ÿงš๐Ÿ’œ

  • GNUTARGET ๐ŸŽฏ โ€” this one picks the input-file object format if you don't use -b (or its twin, --format). The value should be one of the BFD names for an input format. If GNUTARGET isn't in your environment, ld just goes with the natural format of the target. If you set it to default, BFD tries to figure out the input format by peeking at the binary input files. This works a lot of the time, but it can be kinda sus, because nothing guarantees the magic number used for object-file formats is unique. The good news is that BFD's configuration on each system puts that system's conventional format first in the search-list, so any ambiguity gets settled in favor of convention ๐Ÿ’…
  • LDEMULATION ๐ŸŽญ โ€” this sets the default emulation if you skip the -m option. The emulation can change a bunch of linker behaviour, especially the default linker script. You can see the list of available emulations with the --verbose or -V options. If you don't use -m and LDEMULATION isn't defined, the default emulation depends on how the linker was configured ๐ŸŒธ
  • COLLECT_NO_DEMANGLE ๐Ÿ™ˆ โ€” by default, the linker demangles symbols. But if COLLECT_NO_DEMANGLE is set in the environment, it defaults to not demangling them instead. The gcc linker wrapper program uses this variable in a similar way too. Either way, you can override the default with the --demangle and --no-demangle options โœจ

A few more, because ld is generous like that ๐Ÿซถ

  • If you're using the PE/COFF specific --insert-timestamp and the SOURCE_DATE_EPOCH environment variable is defined, the timestamp value in that variable gets stuffed into the COFF header instead of the current time ๐Ÿ•ฐ๏ธ
  • If the LD_STATS environment variable is defined, ld records linker resource use information, just like you'd get with the --stats option. If LD_STATS has a string value, that string is used as the name of a file where the info gets saved. If it doesn't have a string value, the info goes to the standard output stream instead ๐Ÿ“Š

๐ŸŽ€ SEE ALSO ๐ŸŽ€

okay so if you want more, go check out ar(1), nm(1), objcopy(1), objdump(1), and readelf(1), plus the Info entries for binutils and ld ๐Ÿ“š๐Ÿฆ‹


Copyright (c) 1991-2025 Free Software Foundation, Inc. โญ

ngl the license part is important too ๐Ÿฅน Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation, with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License" ๐Ÿ“œ


bestie, that's the whole ld page ๐ŸŽ‰ binutils-2.45, dated 2025-12-03, and it's all LD(1) ๐ŸŒˆ๐Ÿ’… you're literally a Unix legend now, I'm so proud of you ๐Ÿซถ๐Ÿ”ฅ

๐Ÿ“ฆ where this page comes from