20200707-01動態庫UNIX使用說明

Unit 系統編程手冊-(41-42) 共享庫基礎

一、靜態庫 Vs 共享庫 優缺點

靜態庫 共享庫
可靠,已經包含運行所需的全部庫,與系統無關 運行之前需要確保相關共享庫已經存在
加載速度更快 需要運行之前依次檢索、加載所需的共享庫,以及符號的重新定位
每次靜態庫改動,相關引用該庫程序都需要重新編譯 運行時動態加載,所以不需要重新編譯
浪費磁盤,每被引用一次就會生成一次副本 運行是加載,所以不需要
浪費內存,每次運行都會在內存生成一次副本 只會在內存中生成一次副本
庫更加簡單
編譯時必須使用位置獨立的代碼,帶來額外性能開銷

二、靜態庫簡單應用

2.1 創建、維護

使用 ar 命令進行

#創建
cc -g -c mod1.c mod2.c mod3.c
ar r libdemo.a mod1.o mod2.o mod3.o
#打印
ar tv libdemo.a
#刪除
ar d libdemo.a mod3.o
2.2 鏈接

標準庫:

/usr/lib 、/lib 、/usr/local/lib

#庫當前目錄
cc -g -o prog prog.o libdemo.a
#庫位於標準庫 使用 -l 去掉 .a
cc -g -o prog prog.o -ldemo
#指定路徑 -L 文件路徑 -l 庫名稱
cc -g -o prog prog.o -Lmydir -ldemo

三、共享庫使用

3.1 創建
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
gcc -g -shared -o libfoo.so mod1.o mod2.o mod3.o
#或則
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c -shared -o libfoo.so

-fPIC 指定編譯器生成位置獨立的代碼,因爲鏈接的時候無法直到共享庫位於內存的何處

#方式一: 是否使用 -fPIC 選項
nm mod1.o | grep _GLOBAL_OFFSET_TABLE_
readelf -s mod1.o | grep _GLOBAL_OFFSET_TABLE_
#方式二: 若輸出信息,則說明至少有一個目標模塊沒有使用 -fPIC 選項
objump --all-headers libfoo.so | grep TEXTREL
readelf -d libfoo.so | grep TEXTREL
3.2 共享庫 soname

目的: 提供一層間接,使得程序能夠運行時使用與鏈接時使用的庫不同但兼容的共享庫

# 1 創建
gcc -g -c -fPIC -Wall mod1.c mod2.c mod3.c
# 2 創建 soname: 將 libfoo.so 的 soname 設爲 libbar.so
gcc -g -shared -Wl,-soname,libbar.so -o libfoo.so mod1.o mod2.o mod3.o
# 3 檢查 soname: 確定一個既有共享庫 soname,以下命令二選一 
objump -p libfoo.so | grep SONAME
readelf -d libfoo.so | grep SONAME
# 4 soname 嵌入程序: 編譯器檢測到 soname 就將 soname 頂替 libfoo.so 嵌入程序中
gcc -g -Wall -o prog prog.c libfoo.so
# 5 創建 soname: 創建符號鏈接
ln -s libfoo.so libbar.so
3.3 共享庫小工具
  1. ldd命令:顯示一個程序所需共享庫

  2. objump / readelf 命令:獲取各類信息(包括反彙編二進制碼),從可執行文件、庫,顯示 ELF 節頭部信息

  3. nm 命令:列出目標庫或可執行程序中定義的一組符號 nm -A /usr/lib/lib*.so 2> /dev/null | grep 'crypt$'

3.4 命名規則

真實庫命名:libname.so.major_id.minor_id(次要版本號.補丁號) 如libdemo.1.0.1

soname 命名: libname.so.major_id 如 libdemo.so.1 -> libdemo.so.1.0.2

連接器命名:libname.so -> libdemo.so.1(指向 soname 常見,指向真實庫也可)

3.5 安裝共享庫
方式一:(不推薦)LD_LIBRARY_PATH 所有用戶都可以使用
方式二:標準庫中
方式三:使用 ldconfig

ldconfig :解決庫位置分散帶來的加載緩慢和 soname 鏈接符號因爲庫迭代而失效

1 ldconfig 會依次檢索 /etc/ld.so.conf中指定目錄、/lib、/usr/lib 生成 /etc/ld.so.cache (ldconfig -p 打印)

2 檢查每個庫主要版本的最新次要版本(最大)找出嵌入的 soname,在同一目錄創建相對符號鏈接(命名符合規範) -N 防止緩存重建 -X 阻止 soname 符號鏈接創建

方式四:目標文件中指定庫檢索 -rpath

1 強制指定

gcc -g -Wall -Wl,rpath,/home/mtk/pdir -o prog prog.c libdemo.so

DT_RPATH (優先級高於 LD_LIBRARY_PATH) / DT_RUNPATH (低於LD):

默認情況下,鏈接器會創建 DT_RPATH 標籤,--enable-new-dtags 強制使用 DT_RUNPATH 標籤

gcc -g -Wall -o prog prog.c -Wl,--enable-new-dtags -Wl,-rpath,/home/mtk/pdir/d1 -L/home/mtk/pdir/d1 -lx1
#這樣是可執行文件中將包含兩種標籤,爲了兼容老式動態鏈接器工作

2 不強制指定:使用 $ORIGIN(被解釋成包含應用程序的目錄),允許程序在任意目錄中運行應用程序

gcc -Wl,-rpath,'$ORIGIN'/lib ...

3.6 運行時符號鏈接順序

若主程序定義了與庫相同的全局符號,將會覆蓋庫

gcc -g -shared -Wl,-Bsymbolic -o libfoo.so foo.o

-Bsymbolic 能夠確保庫自身調用與主程序相同的全局符號時,正確調用自身的符號


四、共享庫高級特性

1 動態加載庫

  1. dlopen() 將共享庫加載進調用進程的虛擬空間並增加該庫的打開引用次數

  2. dlerror() 錯誤信息

  3. dlsym() handle 指向庫及所依賴樹的庫中檢索 symbol 的符號(函數或變量)

  4. dlclose() 關閉共享庫

  5. dladdr() 獲取與加載的符號相關的消息

2 支持回調(庫調用主程序符號)

gcc -Wl,--export-dynamic main.c

使用 gcc -rdynamic / gcc -Wl -E / -Wl,–export-dynamic 含義一樣

3 符號可見性

1 static 符號的可見性侷限於單個源代碼文件中

2 void __attribute__((visibility("hidden"))) fun(void) {}對所有共享庫不可見

4 鏈接器版本腳本

4.1 控制全局可見的符號
gcc -g -c -fPIC -Wall vis_com.c 
gcc -g -shared -o vis.so vis_com.o -Wl,--version-script,vis.map

#vis.map 內容
VER_1 {
	global:
		vis_f1;
		vis_f2;
	local:
		*;
};

#打印可見符號
readelf --syms --use-dynamic vis.so | grep vis_
4.2 符號版本化

也就是一個共享庫提供同一個函數的多個版本

//cat sv_lib_v2.c
#include <stdio.h>
__asm__(".symver xyz_old,xyz@VER_1");
__asm__(".symver xyz_new,xyz@@VER_2"); //在.symver 指令中只能有一個 @@ 標記

//sv_v2.map
VER_1 {
    global: xyz;
    local: *;
};
VER_2 {
    global: pqr;
}VER_1; //VER_2 依賴 VER_1
4.3 初始化和終止函數
  1. 老式 _init() 和 _fini(),需要指定 gcc -nostartfiles 也可以通過 -Wl,-init / -Wl,-fini 指定函數

  2. 使用 gcc ,函數名根據需要替換

void __attribute__((constructor)) some_name_load(void) 
{    
}
void __attribute__((destructor)) some_name_unload(void) 
{
}
4.4 預加載共享庫: LD_PRELOAD

LD_PRELOAD=libalt.so ./prog

這樣的好處就是,本來 prog 會加載指定庫的符號,使用預加載之後,預加載內存在的符號會覆蓋原先的符號被主程序調用

4.5 監控動態鏈接器: LD_DEBUG

LD_DEBUG=libs date 根據關鍵字從動態鏈接器獲得跟蹤信息,以便清楚所檢索的庫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章