鏈接參數控制
鏈接器中提供了-dn -dy 參數來控制使用的是動態庫還是靜態庫,-dn表示后面使用的是靜態庫,-dy表示使用的是動態庫
例:
g++ -Lpath -Wl,-dn -lx -Wl,-dy -lpthread 這樣如果在path路徑下有libx.so和libx.a這個時候只會用到 libx.a.
注意在最后的地方如果沒有-Wl,-dy 讓后面的庫都使用動態庫,可能會報出 “cannot find -lgcc_s” 的錯誤,這是由于glibc的.a庫和.so庫名字不同,–static會自動處理,但是 -Wl,-dy卻不會去識別這個問題.
小提示:
如果使用–static, 由于-dy的使用導致后面的庫都是共享庫(dy強制屏蔽了靜態庫),這個時候編譯出來的程序和只有動態庫的情況下強制使用–static編譯一樣都會報錯
運行報錯 ”undefined reference to `xxx()’ ” 對 于動態鏈接庫,實際的符號定位是在運行期進行的.在編譯.so的時候,如果沒有把它需要的庫和他一起進行聯編,比如libx.so 需要使用uldict, 但是忘記在編譯libx.so的時候加上-luldict的話,在編譯libx.so的時候不會報錯,因為這個時候libx.so被認為是一個庫,它里面 存在一些不知道具體實現的符號是合法的,是可以在運行期指定或者編譯另外的二進制程序的時候指定.
如果是采用 g++ -Lpath -lx 的方式進行編譯,鏈接器會發現所需要的uldict的符號表找不到從而報錯,但是如果是程序采用dlopen的方式載入,由于是運行期,這個程序在這個地 方就直接運行報錯了. 另外還有一種情況就是一個對外的接口在動態庫中已經聲明定義了,但是忘記實現了,這個時候也會產生類似的錯誤.
如果在運行期報出這樣的錯誤,就要注意是否是由于某些庫沒有鏈接進來或者某些接口沒有實現的原因產生
日志庫問題 其實不只是日志庫存在這樣的問題,其他需要同時被多個動態庫以及主程序同時使用的函數其實都存在這樣的問題.這里主要以日志庫的問題為例來說明這些問題.
有 一個程序,它通過dlopen的方式調用了一個.so文件. 在這個主程序中和.so中都使用了日志庫,主程序中使用ul_openlog打開了日志, 在.so中沒有用ul_openlog打開日志.這個時候發現,主程序中的日志正常輸出,但.so中的日志卻直接輸出到了標準出錯.
這個問 題的原因在前面的其實已經提到了,在默認情況下主程序中使用的接口對于.so是不可見的,.so所在的代碼空間與主程序的代碼空間是隔離的,這個時 候.so調用的ul_writelog其實是沒有經過ul_openlog的那塊代碼空間,由于ul_log庫使用了一些static變量(如果是帶 comlog的ul_openlog那還有全局符號),只有在ul_writelog, ul_openlog都是在同一塊空間上的時候才會起作用.
這個問題的一個最簡單的解決方案是
在主程序的鏈接的時候加入-rdynamic,仍然鏈接libullib.a庫 編譯動態鏈接庫時,不加鏈接libullib.a庫 其實動態庫這里是否鏈了libullib.a已經不重要了,在有-rdynamic的情況下,.so中如果有與主程序同名的函數那么會優先調用主程序中的函數, 動態庫不鏈接libullib.a倒是可以省點空間
但是這種方式在某些情況還是不能完全解決問題
假 設有A.so, B.so,主程序main, 在A.so中調用了ul_openlog, B.so中沒有調用ul_openlog, 但調用了ul_writelog. 在主程序中沒有調用ul_log中的任何接口和使用任何變量.這種情況下即時使用了-rdynamic還是會導致在 A.so中正常輸出日志,但在B.so中卻把日志輸出到標準出錯.
這個問題的主要原因在于,gcc在鏈接的時候是以.o為單位的,如果一 個.o中的符號沒有被外部所使用,那么在鏈接的時候就不會把這個.o中的符號給鏈接進行.so或者二進制程序中.在上面的問題中主程序里面沒有調用到日志 庫中的任何符號,所以在鏈接的時候就不會把ullib中的ul_openglog和ul_writelog給鏈接進行主程序中,這個時候即使有 -rdynamic也是做不到讓.so中的動態鏈接庫都使用.
這個問題一般有下面幾種方案:
載入A.so的時候使用RTLD_GLOBAL參數,把A.so中的所有的符號都變成對外可見,這樣A.so和B.so的ul_writelog都在一塊代碼空間中了 編譯主程序的時候鏈接ullib的地方由-lullib改為 -Wl,–whole-archive -lullib -Wl,–no-whole-archive, 同時加上-rdynamic. -Wl, –whole-archive是鏈接參數,它表示把ullib的中所有的符號都鏈接進主程序中,而不管主程序是否調用了ullib中的符 號.-Wl,–no-whole-archive表示后面的鏈接取消–whole-archive的行為,畢竟其他的庫沒有必要采用這種方式全部鏈接 進來. 在主程序中隨便調一下ul_log中的符號,比如可以先隨便ul_openlog一下,然后ul_closelog, 后面再進行動態庫調用. 把libullib.a用 ar x 命令還原成多個.o文件,采用直接鏈接的方式使用ul_log.o. 上 面的幾個問題的產生主要還在于靜態鏈接和動態鏈接混用,而兩種鏈接方式又存在不一樣的地方.事實上如果我們把ullib庫 采用動態鏈接庫的方式編譯成 libullib.so, 采用前面的方式在編譯期鏈接libullib.so,并且設置LD_LIBRARY_PATH, 上面的2個問題都不會存在. 編譯期使用的.so,是全局可見的,不需要-rdynamic也可以被dlopen的動態庫所用到,由于是動態鏈接庫,所以 包含了所有的符號,不會像靜態庫那樣只包含了所用到的.o中的符號. 事實上這也是多數第三方程序的解決方案
主程序中使用-rdynamic會對后續的升級造成一些麻煩:
加上-rdynamic后,像日志庫這樣的基礎如果需要升級, 那么就必須要升級主程序, 使用的.so無論如何升級所用到的ul_writelog都是主程序中的. 主程序中除了日志庫,還會有其他庫或者函數的存在, 這些函數如果不是static的就有可能與 dlopen打開的so中的函數混到一起,造成困惑 如果.so中需要打印它自己的日志, 那樣需要comlog本身功能的支持才可以實現,而不能簡單的使用ul_writelog來實現 但是如果主程序中沒有使用-rdynamic,那么又有下面的這些麻煩 dlopen打開的動態庫日志是打印自己的, 不能和主程序統一在一起. 如果.so的程序和主程序open的是同一個日志,這相當于多進程打日志, 那必須要comlog的支持,ullog本身不支持多進程打同一個日志. 如果主程序中用dlopen+RTLD_GLOBAL的打開了某個.so 日志的問題就可能影響到其他的.so中的調用. 同時對于一些老程序, 升級前后-rdynamic 可能也會產生影響,比如兩次ul_openlog 這里對于類似日志庫這種需要全局狀態變量支持的庫提出另外方案
編譯一個專門的.so, 這個.so中包括了其它.so中所需要的所有和全局量相關的接口 2. 主程序不使用-rdynamic編譯, 但打開上面的.so的時候,采用RTLD_GLOBAL方式,并且是第一個打開 3. 除了打開第一個 .so, 其它的.so都不使用RTLD_GLOBAL方式, 并且在編譯的時候都不把和第一.so相關的庫聯編 4. 第一個.so的升級需要保證沒有其它.so在運行才可以dlclose, 重新dlopen
這個問題首先需要明確需求, 到底是希望每個.so打自己獨立的日志,還是和主線程統一
這 里要注意另外一個問題就是目前的ullib 日志庫情況比老的ullog要復雜, 在comlog中引入一些extern 出來的全局變量 在采用dlopen的時候,對于一般符號,一般都是主程序和動態庫在兩塊空間中, 但是對于使用extern出來的變量主程序和動態庫都是在一塊空間中(注:由于32位下不用-fPIC也可以編譯so, 在沒有-fPIC的情況也是分開的, 但是由于64位一定要-fPIC所以一定會出現同一塊空間的問題), 對于這個問題的解決方案是在動態庫鏈接的時候加上 -Wl,-Bsymbolic 參數將動態庫的空間和主程序的空間強行分開。
上面有提到 編譯動態鏈接庫時,不加鏈接libullib.a庫,但主程序使用-rdynamic, 這里主要是為了避免使用到了不同的ullib導致調用了一些不同的內部符號,導致出現另外的麻煩
對于動態庫中的日志建議采用下面的幾個方案:
動態庫完全打自己的,日志,編譯二進制程序不要用-rdynamic, 動態庫鏈接的編譯加上 -Wl,-Bsymbolic 參數 , 鏈接ullib, 在動態庫中自己open,自己控制等級 1. 動態庫不鏈接ullib, 編譯二進制程序用-rdynamic , 這樣可以正確的使用主程序中的日志庫,也規避了版本不一致帶來的問題, 但是這樣失去了對于動態庫日志的控制, 而且存在升級的不便, 日志的升級是由主程序控制的。
小提示:
有關動態庫使用的例子還可以參考 SoTips 在運行期可以通過設置環境變量LD_DEBUG查看每個符號具體鏈接到了什么地方,每個符號具體的查找過程和綁定過程.可以這樣使用 export LD_DEBUG=help 隨便運行一個程序就可以看到對于LD_DEBUG的使用說明
export LD_DEBUG=files./main 可以看到整個裝載過程