連結器、載入器與目的檔格式

符號表(the symbol table)

當你把一個程式拆成數個原始檔時,某個檔案會呼叫另一個檔案裡定義的函式或使用其變數。編譯器只看一個檔案,並不知道那個函式最後會落在記憶體的哪裡——它只知道名字。符號表是記錄名字的索引:一個檔案定義的每個函式與全域變數,以及它使用但期望別人提供的每個名字。它是連結器的通訊錄,是讓分別編譯的片段彼此找到對方的東西。

在 ELF 中,.symtab 節區放著一個符號項目的陣列;每個項目有名字(指向字串表 .strtab 的索引)、值(解析後通常是位址或偏移量)、大小與屬性。最重要的有兩個屬性。符號的繫結(binding)說明它對連結器的可見範圍:LOCAL 符號是單一目的檔私有的(想成 C 的 static),GLOBAL 符號是共享的、必須跨檔案解析,而 WEAK 是一種較柔性的 global,在它自己的條目裡討論。符號的型別說明它命名什麼(函式、物件/變數、節區等等)。已定義的符號有真正的值;未定義的符號(繫結為 GLOBAL 但節區為 UNDEF)是連結器必須靠在別處找到定義來兌現的承諾。還有 .dynsym,是保留在執行中二進位檔裡的較小符號表,讓動態連結器能在載入時解析名字。

它重要,是因為你會遇到的幾乎每個連結錯誤都來自這張表:'undefined reference to foo' 表示一個叫 foo 的 GLOBAL UNDEF 符號從未被配對到定義;'multiple definition of foo' 表示兩個物件各自定義了同一個強 GLOBAL 符號。一個有用的心智檢查:宣告 'extern int x;' 建立的是未定義參照(一次使用、一個之後要找到 x 的承諾),而 'int x = 5;' 建立的是定義(一個有值的真正項目)。nm 之類的工具會印出這張表,讓你精確看到某個物件定義了哪些符號(程式碼/text 常是大寫 T、資料是 D)對比它參照了哪些(未定義是 U)。

$ nm main.o 0000000000000000 T main # T = 定義於 .text(global) U printf # U = 未定義,必須在連結時解析

nm 讀取符號表:main 在此被定義(T),printf 被參照但未定義(U),直到與 C 函式庫連結為止。

LOCAL 符號(C 的 static 名字)不會跨檔案衝突,對其他物件不可見;只有 GLOBAL 與 WEAK 符號參與跨檔案解析。.symtab 可被剝除以縮小二進位檔;若二進位檔使用動態連結,.dynsym 不能。

又称
.symtab.dynsymsymbol binding符號繫結