ELF (Executable and Linkable Format),已具備可執行條件,載入時由動態連結器 ld-linux.so 完成重定位。.so 的 .text 區段至相同物理頁框 (Copy-on-Write),.data/.bss 各行程獨立副本。-fPIC,產生相對定址指令,使庫可在任意虛擬位址載入,實現真正共享。libfoo.so.1 (實體) → libfoo.so (連結器用符號連結) → libfoo.so.1.2.3 (完整版本),支援多版本共存與平滑升級。// my_strcpy.c
#include <string.h>
void my_strcpy(char *dest, const char *src)
{
while ((*dest++ = *src++));
}
// my_strcmp.c
int my_strcmp(const char *s1, const char *s2)
{
while (*s1 && *s2) {
int diff = *s1 - *s2;
if (diff) return diff;
s1++; s2++;
}
return *s1 - *s2;
}
gcc -fPIC -c my_strcpy.c my_strcmp.c
# 產生 my_strcpy.o, my_strcmp.o
-fPIC 關鍵:生成 GOT (Global Offset Table) 相對定址,否則共用庫載入固定位址會衝突。
gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 my_strcpy.o my_strcmp.o
-shared:產生共用庫而非執行檔。-Wl,-soname,libmylib.so.1:傳遞 -soname 給連結器 ld,嵌入 .dynamic 區段,動態連結器載入時讀取此字串尋找庫檔。-o libmylib.so.1.0.0:實體檔名含完整版本。ln -sf libmylib.so.1.0.0 libmylib.so.1 # SONAME 連結,執行期載入用
ln -sf libmylib.so.1 libmylib.so # 連結器名稱,編譯期 -lmylib 用
// main.c
#include <stdio.h>
void my_strcpy(char*, const char*);
int my_strcmp(const char*, const char*);
int main() {
char buf[32];
my_strcpy(buf, "hello linux");
puts(buf);
printf("cmp: %d\n", my_strcmp("hello", "world"));
return 0;
}
gcc -o main main.c -L. -lmylib
直接 ./main 會報 error while loading shared libraries: libmylib.so.1: cannot open shared object file。三種解法:
| 方法 | 指令 | 適用場景 |
|---|---|---|
環境變數 LD_LIBRARY_PATH | export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH | 開發/測試臨時用,不污染系統 |
/etc/ld.so.conf.d/ + ldconfig | echo "/opt/mylib" > /etc/ld.so.conf.d/mylib.conf && ldconfig | 系統級部署,需 root |
編譯時嵌入 RPATH/RUNPATH | gcc -Wl,-rpath,'$ORIGIN' -o main main.c -L. -lmylib | 隨程式部署,相對路徑 $ORIGIN 指向執行檔目錄 |
// 隱藏內部符號,僅匯出公用 API
#define API_EXPORT __attribute__((visibility("default")))
#define API_INTERNAL __attribute__((visibility("hidden")))
API_EXPORT void my_strcpy(char*, const char*);
API_INTERNAL void internal_helper();
編譯加 -fvisibility=hidden,預設隱藏,僅標記 API_EXPORT 者匯出,減少符號表大小、避免名稱衝突、提升載入速度。
// mylib.map
LIBMYLIB_1.0 {
global:
my_strcpy;
my_strcmp;
local: *;
};
gcc -shared -Wl,--version-script=mylib.map -o libmylib.so.1.0.0 ...
允許同一庫未來釋出 LIBMYLIB_2.0 新增/變更介面,舊執行檔繼續綁定 1.0 符號,實現 ABI 向後相容。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mylib LANGUAGES C)
add_library(mylib SHARED my_strcpy.c my_strcmp.c)
set_target_properties(mylib PROPERTIES
VERSION 1.0.0
SOVERSION 1
POSITION_INDEPENDENT_CODE ON
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS mylib LIBRARY DESTINATION lib)
| 現象 | 原因 | 解決 |
|---|---|---|
| 編譯通過,執行找不到 .so | 執行期載入路徑未包含庫目錄 | 設定 LD_LIBRARY_PATH、rpath 或 ldconfig |
| undefined symbol: xxx | 缺少目標檔、符號被隱藏、C++ name mangling | 檢查 nm -D libmylib.so,確認 extern "C" |
| 版本衝突:多個 libfoo.so.1 | 系統目錄與專案目錄同時存在不同版本 | 用 rpath 鎖定專案私有庫,或改 SONAME |
| PIC 相關錯誤 | 未加 -fPIC 直接建立 .so | 所有目標檔必須 -fPIC 編譯 |
Linux 共用庫製作看似步驟繁瑣,實則每一步都對應動態連結器運行機制的具體需求:-fPIC 解決位址獨立、SONAME 解決版本識別、RPATH 解決部署路徑、符號可見性解決封裝與效能。掌握這些原理,配合 CMake/Meson 現代建構系統,即可產出專業級、可維護、ABI 穩定的共用庫,支撐大型系統的模組化與熱更新架構。