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

符號版本控制(symbol versioning)

一個共享函式庫經年演進,有時某函式的行為或引數型別必須以會破壞舊程式的方式改變。但那些已編譯、已出貨的舊程式,仍照名字呼叫舊函式。單一個 libc.so 要如何同時滿足想要舊行為的 2005 年程式與想要新行為的 2025 年程式——兩者都呼叫同名的函式?符號版本控制就是答案:它讓一個函式庫匯出同一符號名字的多個版本,並把每個呼叫者繫結到它建構時所對應的版本。

具體說,每個匯出符號可帶一個版本標籤,例如 foo@GLIBC_2.2.5 對比 foo@@GLIBC_2.34,雙 at 標示目前的預設版本。當你編譯一個呼叫 foo 的程式時,連結器記下的不只是名字 foo,還有它在建構時找到的版本。載入時,動態連結器把你記下的版本與函式庫實際提供的版本相配,於是舊二進位檔持續拿到舊的 foo、新的拿到新的 foo,都來自同一個 .so 檔。函式庫作者用版本腳本控制這一切:一個傳給連結器的小文字檔(透過 --version-script),列出哪些符號在哪個版本節點下是 global(匯出)、哪些是 local(隱藏),讓他們在同一處既定義版本又修剪匯出表面。

它重要,是因為這就是 glibc 維持其驚人向後相容性的方法:數十年前建構的二進位檔仍能對著今天的 libc 執行,因為舊的符號版本被保留下來。它也是函式庫實踐乾淨封裝的方式——版本腳本能精確宣告哪些符號是公開的、隱藏其餘一切,這比依賴散布在程式碼各處的個別可見性屬性更好。你真正會遇到的常見陷阱是錯誤 'version GLIBC_2.34 not found':它表示一個二進位檔是對著比執行期所存在更新的 libc 建構的,而它需要一個較舊函式庫無法提供的符號版本。

/* 版本腳本:ver.map */ LIBFOO_1.0 { global: foo; bar; local: *; }; /* 匯出 foo,bar;隱藏其餘 */ LIBFOO_2.0 { global: baz; } LIBFOO_1.0; /* 2.0 加入 baz,依賴 1.0 */ $ gcc -shared -Wl,--version-script,ver.map -o libfoo.so ...

版本腳本在版本節點下命名匯出的符號,並以 'local: *' 隱藏其餘,於是一個 .so 能服務新舊呼叫者。

'version GLIBC_2.xx not found' 表示二進位檔是對著比執行期所存在更新的函式庫建構的——這是向前相容性失敗,不是檔案遺失。版本控制提供向後相容(舊二進位檔在新函式庫上),而非向前(新二進位檔在舊函式庫上)。

又称
versioned symbolsversion scripts符號版本版本腳本