1. 問題重現模型
為了重現問題並去掉無關干擾細節,我們將構建乙個最簡單的可執行模組和依賴模組的關係鏈,程式依賴模型如下:
1.1 解釋
(1)有乙個名為rtsp的第三方庫提供了公共介面rtsp_open,rtsp可以編譯為靜態庫librtsp_static.a也可以編譯為動態庫librtsp_shared.so。
(2)基於rtsp庫封裝了乙個名為stream的庫,該庫以動態庫libstream.so的形式提供使用。stream庫提供了1個名為stream_open的介面,該介面在內部使用rtsp庫提供的公共介面rtsp_open。
(3)使用者程式呼叫了stream庫的stream_open介面。
(4)整個依賴鏈關係為使用者程式依賴stream庫,stream庫依賴rtsp庫。
1.2 **
為了便於看到實驗效果,rtsp庫的靜態庫(.a)和動態庫(.so)分別使用不同的原始檔進行編譯,但是他們提供相同的介面。(現實情況是使用同一套**生成靜態庫和動態庫,此處只是為了方便看到實驗效果)。
(1)rtsp庫**如下:
1)rtsp.h
#ifndef __rtsp_h__
#define __rtsp_h__
int rtsp_open();
int rtsp_close();
int rtsp_parse();
#endif12
3456
2)rtsp_static.c
#include "stdio.h"
int rtsp_open()
int rtsp_close()
int rtsp_parse()12
3456
78910
1112
1314
1516
1718
193)rtsp_shared.c
#include "stdio.h"
int rtsp_open()
int rtsp_close()
int rtsp_parse()12
3456
78910
1112
1314
1516
1718
19(2)stream庫**如下:
1)stream.h
#ifndef __stream_h__
#define __stream_h__
int stream_open();
#endif12
342)stream.c
#include "rtsp.h"
int stream_open()12
3456
(3)使用者程式**如下:
1)test_stream.c:
#include "stream.h"
int main(int argc, char *ar**)12
3456
71.3 編譯
各模組編譯語句如下:
(1)librtsp_shared.so
gcc -g -fpic -shared rtsp_shared.c -o
librtsp_shared.so12
(2)librtsp_static.a
gcc -c -g -fpic rtsp_static.c -o rtsp_static.o
ar crv librtsp_static.a rtsp_static.o12
(3)libstream.so
stream庫使用rtsp靜態庫,請注意stream庫的編譯方法,非常重要,後面解決符號衝突問題會修改此編譯語句!
gcc -g -fpic -shared stream.c -o libstream.so -l./ -lrtsp_static
1(4)使用者程式的編譯放在下一節分析,因為使用者程式的編譯方法不同將導致使用者程式執行結果不同,使用者程式有可能呼叫到rtsp靜態庫中的rtsp_open介面,也有可能呼叫到rtsp動態庫中的rtsp_open介面。
2. 問題重現分析
現在我們再來看下上述**的程式依賴模型圖:
2.1 情景分析
stream庫在內部使用了第三方rtsp庫提供的公共介面,如果使用者程式也直接使用了rtsp庫會出現什麼情況?請看下面的情景分析。
(1)情況1
使用者程式使用如下語句編譯:
gcc -g test_stream.c -o test_stream -l./ -lstream
1程式執行結果如下:
可以看到使用者程式最終呼叫了rtsp靜態庫中的rtsp_open介面。
分析:通常情況下,使用者程式直接使用封裝的stream庫,stream庫隱含的使用了第三方rtsp庫的靜態庫,這個隱含關係對於使用者來說是不可見的。如果使用者程式不直接使用rtsp動態庫,一切都沒有問題。
情況1模組載入關係如下:
圖中藍色部分代表使用者程式執行期間被載入到記憶體中的模組,從圖中可以看到,使用者程式執行時只存在rtsp靜態庫,因此rtsp_open是唯一的。
(2)情況2
使用者程式使用如下語句編譯:
gcc -g test_stream.c -o test_stream -l./ -lrtsp_shared -lstream
1程式執行結果如下:
可以看到使用者程式最終呼叫了rtsp動態庫中的rtsp_open介面。
分析:在這種情況下,使用者程式使用了stream庫,同時又直接鏈結了第三方rtsp庫的動態庫(編譯語句中的紅色部分)。因為stream庫是使用rtsp庫的靜態庫(librtsp_static.a)編譯的,現在又鏈結了rtsp庫的動態庫(librtsp_shared.so),因此在使用者程式中會有兩個相同的rtsp_open符號,載入器在載入應用程式並繫結符號時就要做出決議,到底是要使用靜態庫中的rtsp_open符號還是動態庫中的rtsp_open符號。
從執行結果上來看,上述的編譯語句編譯出來的使用者程式使用了動態庫中的rtsp_open符號。
情況2模組載入關係如下:
圖中藍色部分代表使用者程式執行期間被載入到記憶體中的模組,從圖中可以看到,使用者程式執行時同時存在rtsp靜態庫和rtsp動態庫,因此rtsp_open具有二義性。
實際上,即使使用者使用了rtsp動態庫也不一定會導致使用者程式呼叫到動態庫中的rtsp_open符號。例如,我們把上面的編譯語句改為下面的:
原來的使用者程式編譯語句:
gcc -g test_stream.c -o test_stream -l./ -lrtsp_shared –lstream
1修改的使用者程式編譯語句:
gcc -g test_stream.c -o test_stream -l./ -lstream -lrtsp_shared
1重新編譯編譯後執行使用者程式,輸出如下:
可以看到即使鏈結了rtsp動態庫,使用者程式最終還是呼叫了靜態庫中的rtsp_open介面。
具體原因在此處暫時不展開,但是可以說明一點,如果使用者程式使用了rtsp動態庫可能會產生符號衝突問題,並且這個行為是stream庫提供者不能控制的!
3. 問題解決方案
3.1 解決方法
我們回過頭來看一下stream庫的編譯語句:
gcc -g -fpic -shared stream.c -o libstream.so -l./ -lrtsp_static
1再看一下libstream.so的重定位資訊:
可以發現rtsp_open是乙個動態繫結符號,所謂動態繫結符號就是編譯鏈結階段並不確定符號位址,符號位址的解析和繫結推遲到裝載階段。
因此解決問題的一種思路就是在編譯鏈結階段將使用的rtsp靜態庫中的符號位址確定下來。
解決的辦法就是在編譯libstream.so的時候加上-wl,-bsymbolic編譯選項,該編譯選項的含義是在鏈結過程中優先使用本模組內部的符號。
原來的libstream.so編譯語句:
gcc -g -fpic -shared stream.c -o libstream.so -l./ -lrtsp_static
1修改的libstream.so編譯語句:
gcc -g -fpic -shared stream.c -o libstream.so -l./ -lrtsp_static -wl,-bsymbolic
1重新編譯libstream.so後檢視重定位資訊:
可以看到libstream.so中的動態繫結符號中已經沒有rtsp_open這個符號了。
3.2 驗證
重新編譯使用者程式並使用新的libstream.so,驗證符號衝突問題是否解決:
(1)gcc -g test_stream.c -o test_stream -l./ -lrtsp_shared –lstream
1執行結果:
(2)gcc -g test_stream.c -o test_stream -l./ -lstream -lrtsp_shared
1執行結果:
從上面2個例子可以看出,使用者程式載入了動態庫librtsp_shared.so,但是使用的都是rtsp靜態庫(librtsp_static.a)中提供的rtsp_open介面,符號衝突問題已經解決。
Linux靜態庫與動態庫
靜態庫 a 靜態庫的 在編譯過程中已經被載入可執行程式,因此體積較大。編譯程式時候需要庫作依賴,執行時候不需要。方便,不再需要外部函式庫支援 缺點 1 因為靜態庫被鏈結後直接嵌入可執行程式中,相當於每乙個可執行程式裡都有乙個庫的副本,浪費空間 2 一旦庫中有bug,需要重新編譯。建立步驟 1 編寫函...
linux動態庫與靜態庫
現實中每個程式都要依賴很多基礎的底層庫,不可能每個人的 都從零開始。盡量不重複做別人已經做過的事,站在巨人的肩膀上 做事情。根據鏈結時期的不同,庫又有 靜態庫和共享庫 動態庫 二者的不同點在於 被載入的時刻不同,靜態庫的 在編譯過程中已經被載入可執行程式,因此體積較大。共享庫的 是在可執行程式執行時...
Linux 靜態庫與動態(共享)庫
不論是在linux還是windows下程式設計,我們都會用到庫,有自身帶的標準庫,也有我們自己寫的庫,庫就是預先編譯好的的方法的集合。linux中的庫可以分為兩種,靜態庫和動態庫,動態庫也稱為共享庫。在linux中,庫名稱都以lib開始,靜態庫名為 lib a,動態庫名為 lib so。靜態庫和動態...