Explain GNU style linker options

English version

2025-02更新

(首先慶祝一下LLVM 2000 commits達成!)

編譯器driver options

在描述鏈接器選項前先介紹一下driver options的概念。gccclang面向用戶的選項稱爲driver options。一些driver options會影響傳遞給鏈接器的選項。 有些driver options和鏈接器重名,它們往往在傳遞給鏈接器同名選項之外還有額外功效,比如:

  • -shared: 不設置-dynamic-linker,不鏈接crt1.o
  • -static: 不設置-dynamic-linker,使用crtbegint.o而非crtbegin.o,使用--start-group鏈接-lgcc -lgcc_eh -lc(它們有(不好的)循環依賴)

-Wl,--foo,value,--bar=value會傳遞--foovalue--bar=value三個選項給鏈接器。 如果有大量鏈接選項,可以每行一個放在文本文件response.txt裏,然後指定-Wl,@response.txt

注意,-O2不會傳遞-O2給鏈接器,-Wl,-O2則會。

  • -fno-pic,-fno-PIC是同義的,生成position-dependent code
  • -fpie,-fPIE分別叫做small PIE、large PIE,在PIC基礎上引入了一個優化:編譯的.o只能用於可執行檔。參見下文的-Bsymbolic
  • -fpic,-fPIC分別叫做small PIC、large PIC,position-independent code。在32-bit PowerPC和Sparc上(即將退出歷史舞臺的架構)兩種模式有代碼生成差異。大多數架構沒有差異。

輸入文件

鏈接器接受幾類輸入。對於符號,每個輸入文件的符號表都會影響符號解析;對於sections,只有regular object files裏的sections(稱爲input sections)會貢獻到輸出文件的sections(稱爲output sections)。

  • .o (regular object files)
  • .so (shared objects): 只影響符號解析
  • .a (archive files)

符號解析細節參見Symbol processing

模式

鏈接器處於以下四種模式之一。模式控制輸出類型(可執行檔/shared object/relocatable object):

  • -no-pie (預設): 生成position-dependent executable (ET_EXEC)。要求最寬鬆,源文件可用-fno-pic, -fpie/-fPIE, -fpic/-fPIC編譯
  • -pie: 生成position-independent executable (ET_DYN)。源文件須要用-fpie/-fPIE, -fpic/-fPIC編譯
  • -shared: 生成position-independent shared object (ET_DYN)。最嚴格,源文件須要用-fpic/-fPIC編譯
  • -r: 生成relocatable file。這稱爲relocatable link,比較特殊。它會抑制各種linker synthesized sections並保留relocations。參見Relocatable linking

容易產生混淆的是,編譯器driver提供了幾個同名選項:-no-pie,-pie,-shared,-r。 GCC 6引入了configure-time選項--enable-default-pie:啓用該選項的GCC預設-fPIE-pie。現在,很多Linux發行版都啓用了該選項作爲基礎的security hardening。

可執行檔鏈接(-no-pie-pie)

定義的符號是non-preemptible的。 對於使用PLT-generating relocation的分支指令,分支可以直接綁定到定義,避免PLT。 對於涉及GOT-generating relocation的代碼序列,代碼序列可能被優化爲直接訪問。詳見All about Global Offset Table

Non-local STV_DEFAULT/STV_PROTECTED定義符號預設不導出到dynamic symbol table。

-no-pie

-no-pie表示link-time地址等於run-time地址。 鏈接器利用這個特性:所有引用non-preemptible符號的relocations都可以解析,包括absolute GOT-generating(如R_AARCH64_LD64_GOT_LO12_NC)、PC-relative GOT-generating(如R_X86_64_REX_GOTPCRELX)等。 在沒有GOT優化的情況下,non-preemptible符號的GOT entry是常量,避免dynamic relocation。 Image base預設爲架構特定的非零值。

  • 某些架構有不同的PLT代碼序列(i386、ppc32 .glink)
  • R_X86_64_GOTPCRELXR_X86_64_REX_GOTPCRELX可以進一步優化
  • ppc64 .branch_lt(long branch addresses)可以優化

-pie

-pie-shared -Bsymbolic很相似,但它生成可執行檔。以下行爲和-no-pie貼近而與-shared不同:

  • 允許copy relocation和canonical PLT
  • 允許relax General Dynamic/Local Dynamic TLS models和TLS descriptors到Initial Exec/Local Exec
  • 會鏈接時解析undefined weak符號爲零。ld.lld不生成dynamic relocation。GNU ld是否生成dynamic relocation有非常複雜的規則,且和架構相關

Shared object鏈接(-shared)

Non-local STV_DEFAULT定義預設是preemptible(可interpose)的,即定義可能在運行時被可執行檔或另一個shared object中的定義替換。 編譯器和鏈接器通過使用GOT和PLT entries來引用這類符號。

Non-local STV_DEFAULT/STV_PROTECTED符號會導出到dynamic symbol table。 例如,在以下程序中,如果用-shared鏈接,foo會導出到dynamic symbol table,但如果用-no-pie-pie鏈接則(預設)不會。

1
void foo() {}

PIC鏈接(-pie-shared)

引用non-preemptible non-TLS符號的symbolic relocation(absolute relocation且寬度匹配word size)會轉換爲relative relocation。

詳見Relative relocations and RELR

符號相關

-Bsymbolic

-Bsymbolic系列選項使shared object中的non-local STV_DEFAULT定義變爲non-preemptible。對可執行檔輸出無效。關於"preemptible"的介紹參見上文"模式"。

  • -Bsymbolic使所有定義(除了被--dynamic-list/--export-dynamic-symbol-list/--export-dynamic-symbol匹配的)變爲non-preemptible
  • -Bsymbolic-functions類似-Bsymbolic,但只適用於STT_FUNC定義
  • -Bsymbolic-non-weak-functions類似-Bsymbolic,但只適用於non-STB_WEAK STT_FUNC定義

詳見ELF interposition and -Bsymbolic

--defsym

定義一個符號。類似ld64的-alias

--exclude-libs

如果匹配的archive(regular或被--whole-archive/--no-whole-archive包圍的)定義了non-local符號,不導出該符號。

例如,clang++ -static-libstdc++ -Wl,--export-dynamic,--exclude-libs=libstdc++.a a.cc不會導出libstdc++定義的符號。

--export-dynamic

此選項將non-local STV_DEFAULT/STV_PROTECTED定義符號放入可執行檔輸出的dynamic symbol table。 在以下情況下此選項無效:

  • 指定了-shared,因爲shared object預設就會這樣做
  • 指定了-no-pie且沒有輸入shared object,因爲不存在dynamic symbol table

以下是符號被導出到dynamic symbol table的規則(logical AND):

  • non-local STV_DEFAULT/STV_PROTECTED(這意味着可以被--exclude-libs隱藏)
  • 以下條件的logical OR:
    • undefined符號
    • (--export-dynamic || -shared) && ! (unnamed_addr linkonce_odr GlobalValue || local_unnamed_addr linkonce_odr (constant GlobalVariable || Function))(在LTO中,某些linkonce_odr符號可以被隱藏)
    • --dynamic-list/--export-dynamic-symbol-list/--export-dynamic-symbol匹配
    • 被shared object定義或引用爲STV_DEFAULT
    • 當指定--ignore-{data,function}-address-equality}時,shared object中的STV_PROTECTED定義被copy relocation/canonical PLT搶佔
    • -z ifunc-noplt && 有至少一個relocation

如果可執行檔定義了一個被鏈接時shared object引用的符號,鏈接器會導出該符號,使得運行時該shared object的undefined符號可以綁定到可執行檔中的定義。 如果可執行檔定義了一個也被鏈接時shared object定義的符號,鏈接器會導出該符號以啓用運行時的symbol interposition。

在LLVM中,某些unnamed_addr GlobalValue不受--export-dynamic對可執行檔鏈接的影響。 參見lld/test/ELF/lto/internalize-exportdyn.lllld/test/ELF/lto/unnamed-addr-comdat.ll

--export-dynamic-symbol=glob, --export-dynamic-symbol-list, and --dynamic-list

這些選項對可執行檔和shared object有不同的語義:

  • 可執行檔:將匹配的non-local定義符號放入dynamic symbol table(--export-dynamic適用於所有non-local定義符號)
  • shared object:對匹配的non-local STV_DEFAULT符號的引用不應綁定到shared object內的定義,即使由於-Bsymbolic-Bsymbolic-functions--dynamic-list它們本來會綁定

--dynamic-list額外隱含-Bsymbolic

對於shared object的情況,我通常稱這個操作爲"使符號preemptible"。

可以使用--export-dynamic-symbol=foo*來匹配所有non-local STV_DEFAULT符號foo*。ld.lld 11之前使用精確匹配而不是glob。

--export-dynamic-symbol-listGNU ld 2.35ld.lld 14起實現。

在以下示例中,當var是preemptible時,會看到GLOB_DAT dynamic relocation。

1
2
3
// a.c
int var;
int inc() { return ++var; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# shared object中預設是preemptible的。
% clang -O2 -fpic -shared a.cc && readelf -Wr a.out | grep var
0000000000003fc8 0000000500000006 R_X86_64_GLOB_DAT 000000000000400c var + 0

# -Bsymbolic使定義變爲non-preemptible。
% clang -O2 -fpic -shared -Bsymbolic a.cc && readelf -Wr a.out | grep var

# 儘管有-Bsymbolic,--export-dynamic-symbol使定義變爲preemptible
% clang -O2 -fpic -shared -Wl,-Bsymbolic,--export-dynamic-symbol=var a.cc && readelf -Wr a.out | grep var
0000000000003fc8 0000000500000006 R_X86_64_GLOB_DAT 000000000000400c var + 0

# 沒有symbolic意圖選項時,--export-dynamic-symbol對-shared無效。
% clang -O2 -fpic -shared -Wl,--export-dynamic-symbol=foo a.cc && readelf -Wr a.out | grep var
0000000000003fc8 0000000500000006 R_X86_64_GLOB_DAT 000000000000400c var + 0

# --dynamic-list隱含-Bsymbolic。
% clang -O2 -fpic -shared -Wl,--dynamic-list=<(printf '{a;};') a.cc && readelf -Wr a.out | grep var

# 匹配的符號仍然是preemptible的。
% clang -O2 -fpic -shared -Wl,--dynamic-list=<(printf '{var;};') a.cc && readelf -Wr a.out | grep var
0000000000003fc8 0000000500000006 R_X86_64_GLOB_DAT 000000000000400c var + 0

如果version script中local:匹配的符號被dynamic list指定,version script優先,符號會變爲local。

--discard-none, --discard-locals, and --discard-all

如果生成.symtab,live section中定義的local符號被保留的條件是:

1
2
3
4
5
6
7
8
if ((--emit-relocs or -r) && referenced) || --discard-none
return true
if --discard-all
return false
if --discard-locals
return is not .L
# No --discard-* is specified.
return not (.L in a SHF_MERGE section)

這些.L符號(MC中的temporary labels)可以在clang -Wa,-L構建中發出。然後如果未指定ld --discard-locals,鏈接器會將這些符號發出到可執行檔。

此外,RISC-V linker relaxation可能會發出.L0(帶尾隨空格)符號(llvm-project#89693)。 對於RISC-V,較新的GCC和Clang會傳遞-X--discard-locals)給鏈接器。

--no-undefined-version

如果version script指定了精確模式(非glob)但不匹配任何已定義的符號,則報錯。

假設有以下version script,如果foo不是已定義的符號,鏈接器會報錯。 對於不匹配任何符號的glob模式(例如bar*),不會報錯。這是一種折衷。

1
2
3
4
v1 {
foo;
bar*;
};

GNU ld自2002-08起支持--no-undefined-version,但--undefined-version是2022-10才添加的(milestone: binutils 2.40)。

--strip-all

不要創建.strtab.symtab

-u symbol

若某個archive file定義了-u指定的符號則pull(由archive file轉換爲object file,之後該文件就和一般的.o相同)。

比如:ld -u foo ... a.a。若a.a不定義被之前object files引用的符號,a.a不會被pull。 如果指定了-u foo,那麼a.a中定義了foo的archive member會被pull。

-u的另一個作用是指定一個GC root。

--version-script=script

Version script有三個用途:

  • 定義versions
  • 指定一些模式,使得匹配的、定義的、unversioned的符號具有指定的version
  • Local version:local:可以改變匹配的、定義的、unversioned的符號的binding爲STB_LOCAL,不會導出到dynamic symbol table

Symbol versioning描述了具體的symbol versioning機制。

-y symbol

常用於調試。輸出指定符號在哪裏被引用、哪裏被定義。

-z muldefs

允許重複定義的符號。鏈接器預設不允許兩個同名的non-local regular definitions(非weak、非common)。

-z unique-symbol

重命名local符號使其沒有重複。

一些Intel的開發者在開發function granular kernel address space layout randomization功能,需要這個特性(https://sourceware.org/bugzilla/show_bug.cgi?id=26391)。 GNU ld自2.36起支持此選項。 我關閉了ld.lld的feature request

我認爲這不是一個好的設計。

首先是穩定性問題。 假設舊kernel有foo.1 foo.2。 如果有一個新的local foo符號,新kernel會有foo.1 foo.2 foo.3。 然而,新符號不一定對應舊kernel中同名的local符號。 這種擾動在LTO或PGO時可能更容易發生。 對於Clang LTO,kernel Makefile目前指定-mllvm -import-instr-limit=5。 如果一個接近邊界的函數碰巧越過邊界,被inline到其他translation units,穩定性問題可能影響很多translation units。

實現需要對所有local符號進行迭代,這會影響鏈接速度。

此外,.[0-9]+方案已被C++ mangling使用。 Itanium C++ ABI說"A containing a period represents a vendor-specific version or portion of the entity named by the prior to the first period. There is no restriction on the characters that may be used in the suffix following the period." 在GNU中,這用於表示function cloning。

1
2
3
% c++filt <<< $'_ZL3foov\n_ZL3foov.1'
foo()
foo() [clone .1]

作爲替代方案,我建議FGASLR開發者使用STT_FILE符號:

1
2
3
4
STT_FILE    a.c
STT_NOTYPE foo
STT_FILE b.c
STT_NOTYPE foo

ELF規範說:

Conventionally, the symbol's name gives the name of the source file associated with the object file. A file symbol has STB_LOCAL binding, its section index is SHN_ABS, and it precedes the other STB_LOCAL symbols for the file, if it is present.

我在[PATCH v9 02/15] livepatch: use `-z unique-symbol` if available to nuke pos-based search的回覆中提到了我的擔憂。

Library相關

--as-needed and --no-as-needed

通常每個鏈接時shared object都有一個DT_NEEDED標籤。 這樣的shared object會被dynamic loader加載。

--as-needed可以避免不需要的DT_NEEDED標籤。 --as-needed--no-as-needed是position-dependent選項(非正式叫法,但沒找到更貼切的形容詞)。 在ld.lld中,一個shared object is needed,如果下面條件之一成立:

  • 在命令行中至少一次出現在--no-as-needed模式下(即--as-needed a.so --no-as-needed a.so => needed)
  • 或者它有一個定義解析了來自live section(未被--gc-sections丟棄)的non-weak引用

在gold中,規則大概是:

  • 在命令行中至少一次出現在--no-as-needed模式下
  • 或者它有一個定義解析了non-weak引用

在GNU ld中,規則相當複雜。基本如下:

  • 在命令行中至少一次出現在--no-as-needed模式下
  • 或者它有一個定義解析了前一個輸入文件的non-weak引用(工作方式類似archive selection)

ld.bfd ... a.so --as-needed b.so --no-as-needed中,如果a.so引用了b.so定義的符號但a.so不需要b.so,最終輸出會需要b.so。 這可能被用作underlinking問題的workaround。 當看到shared object缺少的依賴(b.so)時,輸出會獲得DT_NEEDED條目來滿足b.so的需求,即使它本身不需要該依賴。

-Bdynamic and -Bstatic

這兩個選項是position-dependent選項,影響後面命令行出現的-lname

  • -Bdynamic (default):在-L指定的目錄列表中查找libfoo.solibfoo.a
  • -Bstatic:在-L指定的目錄列表中查找libfoo.a

注意,歷史上GNU ld裏-Bstatic-static同義。編譯器driver的-static是個不同的選項,除了傳遞-static給ld外,還會去除預設的--dynamic-linker,影響libgcc libc等的鏈接。

--no-dependent-libraries

ld.lld特有。忽略object files中類型爲SHT_LLVM_DEPENDENT_LIBRARIES(通常命名爲.deplibs)的sections。

該section包含一個文件名列表。這些文件名會被ld.lld作爲額外的輸入文件添加。

-soname=name

設置生成的shared object的dynamic table中的DT_SONAME

鏈接器會記錄鏈接時shared objects,在生成的可執行檔/shared object的dynamic table中用一條DT_NEEDED記錄描述每一個鏈接時shared object。

  • 若該shared object含有DT_SONAME,該字段提供`DT_NEEDED的值
  • 否則,若通過-l鏈接,值爲去除目錄後的文件名
  • 否則值爲路徑名(絕對/相對路徑有差異)

比如:ld -shared -soname=a.so.1 a.o -o a.so; ld b.o ./a.soa.outDT_NEEDEDa.so.1。如果第一個命令不含-soname,則a.outDT_NEEDED./a.so

--start-group and --end-group

如果A.aB.a有相互引用,且不能確定哪一個會被先pull into the link,得使用這對選項。下面給出一個例子:

對於一個archive鏈接順序:main.o A.a B.a,假設main.o引用了B.a,而A.a沒有滿足之前的某個undefined符號,那麼該鏈接順序會導致錯誤。 鏈接順序換成main.o B.a A.a行不行呢?如果main.o變更後引用了A.a,而B.a沒有滿足之前的某個undefined符號,那麼該鏈接順序也會導致錯誤。

一種解決方案是main.o A.a B.a A.a。很多情況下重複一次就夠了,但是假如鏈接第一個A.a時僅加載了A.a(a.o),鏈接B.b時僅加載了B.a(b.o),鏈接第二個A.a時僅加載了A.a(c.o)A.a(c.o)需要B.a中的另一個member,該鏈接順序仍會導致undefined symbol錯誤。

我們可以再重複一次B.a,即main.o A.a B.a A.a B.a,但更好的解決方案是main.o --start-group A.a B.a --end-group,或main.o -( A.a B.a -)

--start-lib and --end-lib

參見Archives and --start-lib。 如果a.a包含b.o c.old ... --start-lib b.o c.o --end-lib作用類似ld ... a.a

--sysroot

這與driver選項--sysroot不同。 在GCC/Clang中,driver選項--sysroot做兩件事:

  • 決定include/library搜索路徑(例如$sysroot/usr/include$sysroot/lib64
  • 傳遞--sysroot給ld。

在ld中,

  • -l =foo-l=foo在sysroot目錄下查找libfoo.solibfoo.a
  • INPUTGROUP中的foo在sysroot目錄下查找foo
  • 如果一個linker script在sysroot目錄下,當它打開絕對路徑文件(INPUTGROUP)時,在絕對路徑前加上sysroot。

-t --trace

輸出relocatable object files、shared objects和提取的archive members。

--whole-archive and --no-whole-archive

--whole-archive選項後的.a會當作.o一樣處理,沒有惰性語義。 如果a.a包含b.o c.o,那麼ld --whole-archive a.a --no-whole-archiveld b.o c.o作用相同。

--push-state and --pop-state

GNU ld在binutils 2.25實現了這些選項。

-Bstatic, --whole-archive, --as-needed等都是表示boolean狀態的position-dependent選項。--push-state可以保存這些選項的boolean狀態,--pop-state則會還原。

在鏈接命令行插入新選項裏變更狀態時,通常希望能還原,這個時候就可以用--push-state--pop-state。 比如確保鏈接libc++.alibc++abi.a可以用-Wl,--push-state,-Bstatic -lc++ -lc++abi -Wl,--pop-state

依賴關係相關

詳見Dependency related linker options

-z defs and -z undefs

遇到來自regular objects的不能解析的undefined符號(不能在鏈接時綁定到可執行檔或一個鏈接時shared object中的定義),是否報錯。可執行檔預設爲-z defs/--no-undefined(不允許),而shared objects預設爲-z undefs(允許)。

很多構建系統會啓用-z defs,要求shared objects在鏈接時指定所有依賴(link what you use)。

--allow-shlib-undefined and --no-allow-shlib-undefined

遇到來自shared objects的不能解析的STB_GLOBAL undefined符號,是否報錯。可執行檔預設爲--no-allow-shlib-undefined(報錯),而-shared鏈接預設爲--allow-shlib-undefined(不報錯)。

對於如下代碼,鏈接可執行檔時會報錯:

1
2
3
4
5
6
7
// a.so
void f();
void g() { f(); }

// exe
void g()
int main() { g(); }

如果啓用--allow-shlib-undefined,鏈接會成功,但ld.so會在運行時報錯,在glibc中爲:symbol lookup error: ... undefined symbol:

GNU ld有個複雜的算法查找transitive closure,只有transitive closure的shared objects都無法解析一個undefined符號時纔會報錯。 gold和ld.lld使用一個簡化的規則:如果一個shared object的所有DT_NEEDED依賴都被直接鏈接了,則啓用報錯;如果部分依賴沒有被鏈接,那麼gold/ld.lld無法準確判斷是否一個未被直接鏈接的shared object能提供定義,就保守地不報錯。

值得一提的是,-z defs/-z undefs/--no-undefined--[no-]allow-shlib-undefined可以被一個選項--unresolved-symbols控制。

--warn-backrefs

參見Dependency related linker options#--warn-backrefs

Layout相關

--no-rosegment

預設情況下,ld.lld將只讀數據sections(如.rodata)和代碼sections(如.text)放入兩個PT_LOAD segments。

  • R PT_LOAD
  • RX PT_LOAD
  • RW PT_LOAD(和PT_GNU_RELRO重疊)
  • RW PT_LOAD

指定該選項可以合併R PT_LOAD和RX PT_LOAD。 RX PT_LOAD segment傳統上稱爲text segment,是第一個segment。

ld.lld將rodata和data放在text兩側。這種佈局的優點是text和data之間的距離較短,降低了relocation overflow的壓力。

gold是第一個實現--rosegment的鏈接器。

--xosegment

此選項啟用對execute-only memory的支持。

  • AArch32使用SHF_ARM_PURECODE section flag來指定純程序指令且無數據的sections。
  • AArch64使用SHF_AARCH64_PURECODE section flag。

預設情況下,LLD將具有SHF_ALLOC|SHF_EXECINSTR|SHF_AARCH64_PURECODE標誌的sections視為與具有SHF_ALLOC|SHF_EXECINSTR標誌的sections兼容,將它們合併到單個PT_LOAD segment中。 當指定--xosegment時,LLD將這些sections分離到不同的PT_LOAD segments中。

-z separate-loadable-segments

ld.lld傳統佈局:所有PT_LOAD segments都沒有重疊(一個字節不會被同時加載到兩個memory mappings)。

實現方式是每個新PT_LOAD的地址對齊到max-page-size。ld.lld預設有4個PT_LOAD(R,RX,RW(RELRO),RW(non-RELRO)),在輸出文件裏三次對齊都可能浪費一些字節。 在AArch64和PowerPC上因爲ABI指定的max-page-size較大(65536),最多可浪費65536*3字節。

-z separate-code

binutils 2.31引入該選項,在Linux/x86上爲預設。GNU ld採用這種佈局:

  • R PT_LOAD
  • RX PT_LOAD
  • R PT_LOAD
  • RW PT_LOAD
    • PT_GNU_RELRO部分
    • PT_GNU_RELRO部分

在這種佈局中,兩個相鄰的PT_LOAD program headers不能在文件偏移上重疊。也就是說,文件中被映射到可執行section的字節(RX PT_LOAD)不會同時被映射到R PT_LOAD。 這個想法是,既然只讀內存不能被執行,那裏的ROP gadgets就不能被使用。 然而,這基本上是安全劇場,因爲可執行內存本身就有大量的ROP gadgets。

由於實現複雜性,採用的佈局不太理想,因爲RX PT_LOAD後面還有另一個只讀PT_LOAD。 更好的佈局是將這個R與第一個R合併(PR23704)。 另一個問題是,當沒有RW PT_LOAD時,前幾個非SHF_ALLOC sections的內容可能被映射到RX內存。

我在ld.lld 10引入該選項。語義和GNU ld類似但佈局不同:兩個RW PT_LOAD允許重疊,這意味着第二個PT_LOAD的地址不需要對齊,最多可浪費max-page-size*2字節。

GNU ld的-z separate-code在lld中本質上被分成兩個選項:-z separate-code--rosegment

-z noseparate-code

這是GNU ld的經典佈局,允許某些文件內容被映射爲多個PT_LOAD segments,其中一個是可執行的,另一個是不可執行的。 在這種佈局中,兩個相鄰的PT_LOAD program headers在文件偏移上可能重疊。這個技巧避免了下一個program header開頭的padding。

在沒有linker script fragments的情況下,通常只有兩個PT_LOAD segments:

  • RX PT_LOAD:包含只讀sections(SHF_ALLOC)和可執行sections(SHF_ALLOC|SHF_EXECINSTR)。
  • RW PT_LOAD
    • 前綴部分爲PT_GNU_RELRO。這部分在rtld處理完dynamic relocations後mprotect成readonly。
    • PT_GNU_RELRO的部分。這部分在運行時始終可寫。

第一個PT_LOAD常被稱爲text segment。這個術語有些不準確,因爲該segment也包含只讀數據。

ld.lld 10中預設使用這種佈局,因爲其大小優勢。

注意:當一個SHT_NOBITS section後面跟著另一個section時,SHT_NOBITS section的行爲就像它佔用了文件偏移範圍。 這是因爲ld.lld沒有實現文件大小優化。 幾乎所有鏈接的image都不使用這個優化,因爲在SHT_NOBITS SHF_ALLOC section後添加SHF_ALLOC sections是很少見的。

-z relro

將RELRO sections放在PT_GNU_RELRO program header中。

GNU ld使用一個RW PT_LOAD program header,在開頭添加padding。PT_LOAD的前半部分與PT_GNU_RELRO重疊。 添加padding是爲了使PT_GNU_RELRO的結尾對齊到max-page-size。(參見ld.bfd --verbose輸出。) GNU ld 2.39之前,結尾對齊到common-page-size。 GNU ld的單個RW PT_LOAD佈局使對齊增加了文件大小。max-page-size可能很大,如65536,導致空間浪費

ld.lld使用兩個RW PT_LOAD program headers:一個用於RELRO sections,另一個用於non-RELRO sections。 雖然這最初看起來可能不尋常,但它消除了GNU ld佈局中的對齊padding需求。 關鍵變更:

  • https://reviews.llvm.org/D58892PT_LOAD(PT_GNU_RELRO(.data.rel.ro .bss.rel.ro) .data .bss)切換到PT_LOAD(PT_GNU_RELRO(.data.rel.ro .bss.rel.ro)) PT_LOAD(.data. .bss)
  • PT_GNU_RELRO segment和關聯的RW PT_LOAD segment的結尾被padding到common-page-size邊界。padding section .relro_padding類似mold。 LLD 18之前有個問題:runtime_page_size < common-page-size不工作。

mold使用的佈局與ld.lld類似。 在mold的情況下,PT_GNU_RELRO的結尾通過附加一個SHT_NOBITS .relro_padding section被padding到max-page-size。 這種方法確保無論系統page size如何,PT_GNU_RELRO的最後一頁都被保護。 然而,當系統page size小於max-page-size時,第一個RW PT_LOAD的映射大於所需。

在我看來,當運行時page size大於common-page-size時失去最後一頁的保護並不是真正的問題。 爲了保護而double mapping一頁達到max-common-page可能導致不必要的VM浪費。 保護.got.plt-z now的主要目的。保護.data.rel.ro的一小部分並不能真正使程序更安全,因爲.data.bss非常大且充滿了攻擊目標。 如果用戶真的很擔心,可以將common-page-size設置爲匹配系統page size。

GNU ld的內部linker scripts將RELRO sections放在DATA_SEGMENT_ALIGNDATA_SEGMENT_RELRO_END(內置函數)之間。 DATA_SEGMENT_ALIGN是添加padding的地方,使DATA_SEGMENT_RELRO_END對齊到max-page-size邊界。

1
2
3
. = DATA_SEGMENT_ALIGN(CONSTANT(MAXPAGESIZE), CONSTANT(COMMONPAGESIZE));
. = DATA_SEGMENT_RELRO_END(0, .);
. = DATA_SEGMENT_END(.);

ld.lld模擬這些內置函數:

  • DATA_SEGMENT_ALIGN:將當前位置設置爲alignTo(script->getDot(), + align)
  • DATA_RELRO_END:將當前位置設置爲alignTo(script->getDot(), MAXPAGESIZE).relro_padding被放置在DATA_RELRO_END之前。

-z lrodata-after-bss

參見Relocation overflow and code models#x86-64 linker requirement

--execute-only

這是ld.lld特有的AArch64選項。該選項需要--rosegment,使RX PT_LOAD segment為execute-only (PF_X)。

Relocation相關

--apply-dynamic-relocs

一些psABI使用RELA格式(AArch64、PowerPC、RISC-V、x86-64等):relocations包含addend字段。 在這些目標上,--apply-dynamic-relocs要求鏈接器將relocated位置的初始值設置爲addend而不是0。 如果可執行檔/shared objects使用壓縮,--no-apply-dynamic-relocs可以改善壓縮。

ld.lld的所有ports都支持--apply-dynamic-relocs。 截至2023年8月,GNU ld僅aarch64 port支持--apply-dynamic-relocs

--emit-relocs

此選項使-no-pie/-pie/-shared鏈接保留輸入的relocations,類似於-r。 可用於鏈接後的二進制分析。我知道的唯二用途是Linux kernel x86的CONFIG_RELOCATABLE和BOLT。

使用--emit-relocs時,output section順序可能不同。 .rela.eh_frame sections被保留。參見https://reviews.llvm.org/D44679,第一個.rela.eh_frame input section可能導致.eh_frame被放置在其他只讀output sections之前。

GNU ld的powerpc使用transformed relocation types

--pack-dyn-relocs=value

relr可以啓用DT_RELR,一種更加緊湊的relative relocation (R_*_RELATIVE)編碼方式。Relative relocations常見於-pie鏈接的可執行檔。

-z rel and -z rela

每個架構有一個主要的relocation格式。 ld.lld實現了-z rel,即使在使用RELA作爲主要格式的架構上也使用REL作爲dynamic relocations。 此選項可以節省一些空間。

  • COPY、GLOB_DAT和J[U]MP_SLOT的addend始終爲0。rtld實現不需要讀取隱式addend。REL嚴格更好。
  • RELATIVE有非零addend。它也可以使用隱式addend。或者,這些relocations可以用RELR relocation entry格式緊湊打包。
  • 對於其他dynamic relocation類型(例如symbolic relocation R_X86_64_64),ld.so實現需要讀取隱式addend。REL可能有輕微的性能影響,因爲隱式addends強制隨機訪問讀取,而不是能夠在追蹤relocation數組時批量寫入。

-z report-relative-reloc

輸出關於R_*_RELATIVER_*_IRELATIVE relocations的信息。

-z text and -z notext

-z text不允許text relocations。 -z notext允許text relocations。

binutils 2.35起,Linux/x86上的GNU ld預設啓用configure-time選項--enable-textrel-check={warning,error},若有text relocations會給出warning/error。

Text relocations這個概念的用詞不準確,實際含義是作用在readonly sections上的dynamic relocations的總稱。 .o中的relocations如果不能在鏈接時確定值,就需要轉換成dynamic relocations在運行時由ld.so計算(type和.o中相同)。 如果作用的section沒有SHF_WRITE標誌,ld.so就得臨時執行mprotect變更memory maps的權限、修改、再還原之前的只讀權限,這樣就妨礙了page sharing。

Shared objects形成text relocations的情況比可執行檔多。 可執行檔有canonical PLT和copy relocations可以避免某些text relocations。

不同鏈接器在不同架構上允許的text relocations的relocation types不同。GNU ld會允許一些glibc ld.so支持的types。 在x86-64上,鏈接器都會允許R_X86_64_64R_X86_64_PC64

下面的彙編程序裏defined_in_so是定義在某個shared object的符號。註釋裏給出每種text relocation的場景。

1
2
3
4
5
6
7
.globl global
global:
local:
.quad local # (-pie or -shared) R_X86_64_RELATIVE
.quad global # (-pie) R_X86_64_RELATIVE or (-shared) R_X86_64_64
.quad defined_in_so # (-shared) R_X86_64_64
.quad defined_in_so - . # (-shared) R_X86_64_PC64

-no-pie-pie模式下,根據defined_in_so的符號類型,鏈接器會作出不同選擇:

  • STT_FUNC: 產生canonical PLT
  • STT_OBJECT: 產生copy relocation
  • STT_NOTYPE:GNU ld會產生copy relocation。ld.lld會產生text relocation

Section相關

--gc-sections

在編譯時指定-ffunction-sections-fdata-sections才能生效。 鏈接器會進行liveness分析,從輸出中移除未使用的sections。

詳見Linker garbage collection

-z start-stop-gc and -z nostart-stop-gc

-z start-stop-gc意味着來自live section的__start_foo__stop_foo引用不會保留所有foo input sections。

-z nostart-stop-gc意味着來自live section的__start_foo__stop_foo引用會保留所有foo input sections。

詳見Metadata sections, COMDAT and SHF_LINK_ORDER

--icf=all and --icf=safe

啟用identical code folding。 名稱源於MSVC linker /OPT:ICF,其中"ICF"代表"identical COMDAT folding"。gold將其命名爲"identical code folding"。

這個名稱有些誤導性:

  • 該功能操作的是sections而不是functions。
  • 該功能也適用於只讀數據。

我們定義identical sections爲具有相同內容且其outgoing relocation集不可區分的sections: 它們需要有相同數量的relocations,在相同的相對位置,且被引用的符號不可區分。 這是一個遞迴定義:如果.text.a.text.b在相同位置引用不同符號,如果被引用的符號滿足identical code/rodata要求,它們仍然可以不可區分。

在一組identical sections中,鏈接器可能保守地抑制某些sections的folding。 --keep-unique=<symbol>使定義<symbol>的section唯一。 在ld.lld中,只讀section預設可folding(gold不fold只讀數據)。然而,定義.dynsym符號的只讀section不可以。

對於其餘sections,在一組identical sections中,鏈接器選擇一個代表並丟棄其餘,然後將引用重定向到代表。

gold基於relocation實現--icf=safe

ld.lld --icf=safe使用由Clang -faddrsig生成的特殊section .llvm_addrsig(LLVM address significance table,類型SHT_LLVM_ADDRSIG)。 截至2023-01,-faddrsig在大多數Linux目標上是預設的,但在Android、Gentoo和-fintegrated-as上被禁用。 如果該section不存在,ld.lld會保守地假設表中定義符號的每個section都是地址顯著的。

SHT_LLVM_ADDRSIG將符號索引編碼爲ULEB128。objcopyld -r和其他二進制操作工具可能會更改符號表。 一個有趣的特性是objcopyld -r會將已知section類型SHT_LLVM_ADDRSIGsh_link設置爲0。 ld.lld使用sh_link!=0來檢查有效性,並在sh_link==0時報告警告。

我有點遺憾設計權衡傾向於代碼大小而不是通用性,以及目前一些Linux發行版在Clang Driver中預設-faddrsig而其他則預設-fno-addrsig的狀態。 如果我們使用R_*_NONE relocations(並使用REL而不是RELA來減少大小膨脹)來編碼符號索引可能會更好。 那或許意味着我們應該預設-fno-addrsig並讓用戶選擇啓用該功能。

lld的Mach-O port選擇__DATA,__llvm_addrsig使用基於relocation的表示。

ld.lld --icf=all忽略.llvm_addrsig

對於以下代碼的-shared鏈接

1
2
int foo() { return 1; }
int bar() { return 1; }

foobar.dynsym中。 ld.lld --icf=safe假設.dynsym中的符號是地址顯著的,兩個符號不能共享相同地址,所以ld.lld保守地抑制合併.text.foo.text.bar

gold --icf=safe會合併.text.foo.text.bar。如果程序使用map並期望map[dlsym(h, "foo")]map[dlsym(h, "bar")]解析爲不同對象,這種選擇將是不安全的。

在LLVMCodeGen中,具有{,local_}unnamed_addr屬性的global value不會進入.llvm_addrsig

--icf=all放棄了C++語言關於指針相等的保證。 有些人認爲這是公平的,因爲保證的某些部分無論如何都被破壞了(-fvisibility-inlines-hidden)。 參見ELF interposition and -Bsymbolic

https://reviews.llvm.org/D141310中,提出了一個opt-in Clang診斷-Wcompare-function-pointers來捕獲會導致--icf=all失敗的一些問題。

ICF會使調試更加困難,因爲調試器可能無法區分合併的實例。

  • 與合併函數相關的調試信息本質上被重定向。
  • 在一個函數上設置斷點也會影響合併的函數。

ICF可能會改變堆棧跟蹤中的函數名稱並使分析不準確。

如果啓用DW_AT_LLVM_stmt_sequence並使用調用者來消除合併函數中地址的歧義,可以緩解調試信息的退化。

https://github.com/llvm/llvm-project/pull/139493#issuecomment-2896493771

當有以下情況時:

  • Section A1,在符號S1上有relocation,其中S1在section B1的偏移K處。
  • Section A2,在符號S2上有relocation,其中S2在section B2的偏移K處。

當relocation類型是R_AARCH64_ADR_GOT_PAGE,且sections B1和B2被合併而保持符號S1和S2分開時,sections A1和A2目前可以被合併,這會導致正確性問題。

GC roots:

  • --entry/--init/--fini/-u指定的所有定義符號所在的sections
  • Linker script表達式被引用的定義符號所在的sections
  • .dynsym中的所有定義符號所在的sections
  • 類型爲SHT_PREINIT_ARRAY/SHT_INIT_ARRAY/SHT_FINI_ARRAY
  • 名稱爲.ctors/.dtors/.init/.fini/.jcr
  • 不在section group中的SHT_NOTE(這個section group規則是爲了Fedora watermark)
  • .eh_frame引用的personality routines和language-specific data area

詳見Linker garbage collection

-z start-stop-gc and -z nostart-stop-gc

-z start-stop-gc表示來自live section的__start_foo__stop_foo引用不會保留所有foo input sections。

-z nostart-stop-gc表示來自live section的__start_foo__stop_foo引用會保留所有foo input sections。

詳見Metadata sections, COMDAT and SHF_LINK_ORDER

--symbol-ordering-file=<file>

指定一個文本文件,每行一個定義的符號。 在input section description(例如*(.text .text.*))內,對匹配的input sections排序:如果符號A在ordering file中位於符號B之前,則將定義A的section放在定義B的section之前。

如果一個符號未定義,或者所在的section被丟棄,鏈接器會輸出warning,除非指定了--no-warn-symbol-ordering。 然而,預設的--no-warn-symbol-ordering似乎經常會妨礙使用。

--symbol-ordering-file=主要用於兩個目標:性能或壓縮。

如果一個函數頻繁調用另一個,在linked image中如果讓兩個函數所在的input sections接近,可以增大它們落在同一個page的概率。 通過考慮函數之間的引用並將相關函數放在一起,可以減小page working set並減少TLB thrashing。參見Karl Pettis and Robert C. Hansen的 Profile Guided Code Positioning

移動應用通常優先考慮壓縮後的代碼大小。 對於冷函數,它們的壓縮大小比性能重要得多。 爲了改善壓縮大小,可以將相似的函數分組在一起,以增強壓縮算法(如Lempel-Ziv系列)。

這個選項是ld.lld特有的。gold有一個--section-ordering-file,根據section name排序。 實踐中text和data sections大多有不同的名字。然而,clang -fno-unique-section-namesGCC feature request)可以創建同名sections,從而使--section-ordering-file失效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat > a.s <<e
.section .rodata.0,"a",@progbits; .byte 0x0
.section .rodata.1,"a",@progbits; .byte 0x1
.section .rodata.2,"a",@progbits; .byte 0x2
.section .rodata.3,"a",@progbits; .byte 0x3
.section .rodata.4,"a",@progbits; .byte 0x4
e
cat > a.txt <<e
.rodata.3
.rodata.[2]
.rodata.1
e
as a.s -o a.o
gold --section-ordering-file=a.txt a.o -o a
1
2
3
4
% readelf -x .rodata a

Hex dump of section '.rodata':
0x004000b0 00040302 01 .....

GNU ld 2.43引入了具有不同語義的--section-ordering-file。 section ordering script必須指定已在linker script中定義的output sections。 指定的額外映射將被前置到output section。

1
2
3
4
cat > b.txt <<e
.rodata : { *(.rodata.3) *(.rodata.[2]) *(.rodata.1) }
e
ld.bfd --section-ordering-file=b.txt a.o -o a

--call-graph-profile-sort

--call-graph-profile-sort(預設)生效時,ld.lld會檢查輸入relocatable object files中的SHT_LLVM_CALL_GRAPH_PROFILE sections(call graph profile)。 SHT_LLVM_CALL_GRAPH_PROFILE section由(from_symbol, to_symbol, weight)元組組成。 LLD利用這些信息計算一個以input sections爲節點、(from_section, to_section, weight)爲邊的call graph,然後在input section description內排序sections。 排序算法基於_Optimizing Function Placement for Large-Scale Data-Center Applications_。

LLD按密度遞減排序input sections,其中密度計算爲weight除以size。 最初,每個input section單獨放在一個cluster中。 處理每個input section時,其cluster被附加到包含其在call graph中最可能前驅的cluster。 如果滿足以下任何條件,合併可能被阻止:

  • 邊不太可能(考慮到輸入邊權重的總和,邊權重太小)。
  • 兩個clusters的總大小大於閾值。
  • 合併後的密度會使前驅cluster的密度大大降低。

最後,clusters按密度遞減排序。

如果指定了--symbol-ordering-file=--symbol-ordering-file=指定的sections會先放置。 call graph profile仍用於其他sections(lld >= 20)。

當同時指定--call-graph-profile-sort--print-symbol-order=時,ld.lld會將符號順序輸出到指定文件。 該文件可以與--symbol-ordering-file=一起使用。

--bp-compression-sort= and --bp-startup-sort=

這兩個選項指示鏈接器優化section佈局,目標如下:

  • --bp-compression-sort=[data|function|both]:通過將相似sections分組在一起來改善Lempel-Ziv壓縮,從而減小壓縮後的應用大小。
  • --bp-startup-sort=function --irpgo-profile=<file>:利用temporal profile文件來減少程序啓動期間的page faults。

鏈接器通過考慮三組來確定section順序:

  • 根據temporal profile(--irpgo-profile=)排序的function sections,優先考慮早期訪問和頻繁訪問的函數。
  • Function sections。包含相似函數的sections被放在一起,最大化壓縮機會。
  • Data sections。相似的data sections被放在一起。

在每組內,sections使用Balanced Partitioning算法排序。

鏈接器構建一個二分圖,有兩組頂點:sections和utility頂點。

  • 對於profile引導的function sections:
    • utility頂點的數量由profile文件中的符號順序決定。
    • 如果指定了--bp-compression-sort-startup-functions,會分配額外的utility頂點以優先考慮附近函數的相似性。
  • 對於爲壓縮排序的sections:utility頂點通過分析section內容和relocations的k-mers來確定。

在此優化期間,call graph profile被禁用。

當指定--symbol-ordering-file=時,該文件中描述的sections會更早放置。

-z nosectionheader

GNU ld 2.41引入此選項,用於省略section header table。

--unique

預設情況下,鏈接器將所有具有相同名稱的input sections合併到單個output section中。 例如,來自不同object files的所有.text sections會合併成一個.text output section。

GNU ld的--unique選項爲每個orphan section創建單獨的output sections。 注意,使用-r(relocatable output)時,內部linker script仍會導致某些sections如.text.debug_info被合併。

爲了更精細的控制,使用--unique=glob來匹配特定模式——例如,--unique=*匹配所有sections。

ld.lld只實現了--unique形式,適用於所有sections。在沒有linker script的情況下,它將所有input sections視爲orphans。

分析相關

--cref

輸出cross reference table。對於每一個non-local符號,輸出定義的文件和被引用的文件列表。

-M and -Map=<file>

輸出link map,可以查看output sections的地址、文件偏移、包含的input sections。

Warning相關

--fatal-warnings

把warnings轉成errors。Warning和error的差別除了是否包含warningerror字串外更重要的一點是,error會阻止輸出鏈接結果。

--noinhibit-exec

把部分errors轉成warnings。注意不要指定--fatal-warnings把降級的warnings再升級爲errors:)

--no-warnings

抑制warnings。因--fatal-warnings而轉為errors的warnings不受影響。

其他

--build-id=value

生成.note.gnu.build-id,給輸出一個標識符。標識符通過對整個輸出進行散列生成。

SHA-1是最常見的選擇。鏈接器會給.note.gnu.build-id的內容填零,散列每個字節後把結果填回.note.gnu.build-id。 一些鏈接器使用tree-style散列以實現並行化。

--compress-debug-sections=[zlib|zstd]

用zlib或zstd壓縮輸出文件的.debug_* sections,並標記SHF_COMPRESSED。 參見Compressed debug sections

--format=binary, -b binary

每個輸入文件被視爲一個ELF文件,其.data section包含文件的原始二進制內容。 定義符號_binary_<filename>_{start,end,size}以提供對嵌入數據的訪問。

1
2
3
echo hello > a.txt
ld.bfd -r -b binary -m elf_x86_64 a.txt
objdump -s a.out

輸出:

1
2
3
4
a.out:     file format elf64-x86-64

Contents of section .data:
0000 68656c6c 6f0a hello.

輸入文件的轉換方式類似於以下objcopy操作:

1
2
3
4
5
# 兼容llvm-objcopy
objcopy -I binary -O elf64-x86-64 a.txt a.o

# 僅GNU objcopy
objcopy -I binary -O default a.txt a.o

--hash-style=style

ELF規範要求一個用於dynamic symbol lookup的hash table DT_HASH--hash-style=sysv生成該表。

DT_GNU_HASH在空間佔用和性能上都優於DT_HASH。 mips使用替代方案DT_MIPS_XHASH(mips ABI設計聰明反被聰明誤的好例子)。 我個人認爲DT_MIPS_XHASH在解決一個錯誤的問題。實際上有辦法用DT_GNU_HASH,但可能mips社區的人不想再折騰了。

參見glibc and DT_GNU_HASH瞭解關於"Easy Anti-Cheat"的故事。

--no-ld-generated-unwind-info

參見PR12570 .plt has no associated .eh_frame/.debug_frame

PC在PLT entry中時,如果鏈接器不合成.eh_frame信息,從當前PC unwind將無法獲得frames。 在i386和x86-64上,lazy binding狀態下,一個PLT entry的首次調用會執行push指令。在ESP/RSP改變後,如果PLT entry沒有.eh_frame提供的unwind信息,unwinder可能會無法正確unwind,影響profiler精度。

1
2
3
jmp *got(%rip)
pushq $0x0
jmpq .plt

然而,由於-Wl,-z,relro,-z,now(BIND_NOW)的普遍使用,這個功能如今已經基本過時。 PLT entries的行爲像沒有prologue的函數。profiler可以使用預設規則輕鬆檢索返回地址:如果一個代碼區域沒有被metadata覆蓋,假設返回地址在*rsp處可用(x86-64)。

要識別PLT名稱,profiler需要:

  • 解析.plt section以識別PLT entries的區域
  • 解析.rel[a].plt以獲取R_*_JUMP_SLOT dynamic relocations及其引用的符號名
  • 如果當前PC在PLT區域內,解析附近的指令並找到GOT load。關聯的R_*_JUMP_SLOT標識符號名
  • 連接符號名和@plt形成foo@plt

注意:foo@plt是objdump等工具使用的慣例,但object file中不包含這樣的符號。

gdb有heuristics可以識別這種情況。

這個問題不會影響C++ exception。PLT entry是tail call,__cxa_throw調用的_Unwind_RaiseException會穿透ld.so resolver和PLT entry的tail calls。 PC會還原爲PLT entry的caller的下一條指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// b.cc - b.so
void ext() { throw 3; }

// a.cc - exe
#include <stdio.h>

void ext();
void foo() {
try {
ext(); // PLT entry
} catch (int x) {
printf("%d\n", x);
}
}

int main() {
foo();
}

-O

啓用大小優化。 優化等級和編譯器driver選項-O不同。 -O不影響--lto-O:對LTO代碼生成沒有效果。

在ld.lld中,-O1是預設值。

-O0禁用SHF_MERGE的常量合併。

-O2啓用一些計算量大的大小優化:

  • 啓用SHF_MERGE|SHF_STRINGS的string suffix merge。這非常慢且不並行。
  • --compress-debug-sections=zlib使用較高壓縮比的zlib壓縮。
  • 自14.0.0起,在.strtab中去重local符號名。一旦ld.lld支持並行.symtab寫入,我可能會完全移除這個功能。

在GNU ld中,非零-O可以使.hash.gnu.hash更小。

對於引用SHF_MERGE section的符號賦值,它被認爲引用常量數據元素。 在消除重複後,符號值被調整爲引用output section中的數據元素。

-plugin file

GNU ld和gold支持這個選項加載GCC LTO插件(liblto_plugin.so)或LLVM LTO插件(LLVMgold.so) clang -flto={full,thin}會傳遞-plugin path/to/LLVMgold.so,除非使用-fuse-ld=lld

插件的API接口由binutils-gdb/include/plugin-api.h定義。

注意,LLVMgold.so的名稱含gold,但也能用於GNU binutils (ld, gold, nm, ar)和mold。

--verbose

GNU ld會輸出linker script(內部或外部)。gold、ld.lld和mold不是linker script驅動的,沒有linker script輸出。

隨機性相關

--shuffle-sections=<seed>

隨機打亂input sections,用於發現依賴特定section順序的bug。

--randomize-section-padding=<seed>

使用給定的seed在input sections之間和每個segment的開頭隨機插入padding。

假設某個改動無意中降低了一個頻繁執行的函數的內存對齊。雖然原程序可能沒有保證這個函數的對齊,但這個改動可能會加劇問題。使用--randomize-section-padding可以通過引入內存佈局的變化來發現這類微妙的性能退化。

地址相關

-Ttext-segment

text segment傳統上是第一個segment。指定-Ttext-segment的用戶可能實際上想指定image base。 當與-z separate-code一起使用時,該選項有奇怪的語義(可能是bug):https://sourceware.org/bugzilla/show_bug.cgi?id=25207

ld.lld提供--image-base來設置image base。

GNU ld的PE/COFF端口很早就支持--image-base,並在binutils 2.44版本中為ELF實現了該選項。

這個選項似乎主要用於mmap MAP_FIXED的使用,以避免與ASLR衝突。 更好的替代方案是避免設置固定地址。qemu的linux-user/elfload.c:probe_guest_base可能會提供一些見解。

目標特定

--cmse-implib, --out-implib=out.lib

參見Linker notes on AArch32