1. 程式人生 > >關於動態鏈接庫的分析

關於動態鏈接庫的分析

組織 RR 其他 猜想 發現 blog AI OS 加載

關於動態鏈接庫的分析

linux系統中動態鏈接庫文件用.so後綴標記,一般命名規則為libxxx.so

1 鏈接產生動態庫.so與編譯源碼產生的二進制文件.o的關系

現在有工程,源文件包括:

main1.cpp

myAPI.cpp

myAPI.h

其中myAPI.cpp,myAPI.h定義了兩個函數ADD(), MINUS()

main1.cpp中則調用ADD(), MINUS()

<實驗1>

1)編譯產生myHandler.o

g++ -fPIC -c -o myAPI.o myAPI.cpp

2)myAPI.o文件編譯鏈接產生main1

g++ main1.cpp myAPI.o -o main1

3)編譯、鏈接、運行成功,然後用ldd main1查看可執行文件的依賴:

linux-vdso.so.1 => (0x00007fffed77c000)

libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f21fc26b000)

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f21fbea1000)

libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f21fbb98000)

/lib64/ld-linux-x86-64.so.2 (0x00007f21fc5ed000)

libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f21fb982000)

4)刪除myAPI.o,再運行./main1

結果程序正常運行。

分析:這說明,main文件中包含有myAPI.o myHandler.o中的所有可執行代碼,除了因為main.cpp中包含了<iostream>庫所以需要在運行時動態加載一些.so文件外,沒有依賴任何其他庫文件。

<實驗2>

1)編譯產生myHandler.o

g++ -fPIC -c -o myAPI.o myAPI.cpp

2)myAPI.o文件打包產生libmyAPI.so

g++ -shared myAPI.o -o libmyAPI.so

3)用libmyAPI.somain1.cpp編譯鏈接產生main2

g++ -o main2 main1.cpp -L. -lmyAPI

3)編譯、鏈接、運行成功,然後用ldd main2查看可執行文件的依賴:

linux-vdso.so.1 => (0x00007fffed77c000)

libmyAPI.so (0x00007fe029706000)

libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f21fc26b000)

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f21fbea1000)

libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f21fbb98000)

/lib64/ld-linux-x86-64.so.2 (0x00007f21fc5ed000)

libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f21fb982000)

4)刪除myAPI.o,再運行./main2,程序正常運行

5)刪除myAPI.so,再運行./main2,程序運行失敗

./main2: error while loading shared libraries: libmyAPI.so: cannot open shared object file: No such file or directory

分析:在4)刪除myAPI.o後程序運行成功,說明./main2的運行與myAPI.o無關。在4)刪除myAPI.so後執行程序提示加載動態庫失敗,恢復myAPI.so後隨機成功運行,直接說明main2依賴myAPI.so文件。原因是main2中缺少某部分執行代碼,這部分代碼包含在libmyAPI.so中,而main2記錄這部分代碼的來源,所以當使用ldd main2查看其依賴列表時有libmyAPI.so

根據<實驗1><實驗2>,可以得出結論,.so文件中完全包含了.o中多有可執行代碼。另外.o文件不是可執行文件,而.so是可執行文件。.so文件允許程序一開始不加載他所包含的代碼,當需要運行它包含的代碼時,才根據文件中的路徑動態家去加載它的代碼。

註:

在編譯鏈接main2時,遇到一個有意思的小麻煩:

能夠成功編譯鏈接的指令為:g++ -o main2 main1.cpp -L. -lmyAPI

但如果交換源文件和動態鏈接庫的順序:g++ -o main2 -L. -lmyAPI main1.cpp

g++會報錯:

/tmp/ccvwLNKz.o:在函數‘main’中:

main1.cpp:(.text+0x14):對‘ADD(int, int)’未定義的引用

main1.cpp:(.text+0x4b):對‘MINUS(int, int)’未定義的引用

collect2: error: ld returned 1 exit status

原因不明,這可能與g++組織組織文件依賴關,總之記住以後讓源文件在前,依賴在後。

2 .so文件的路徑問題

現在有工程,源文件包括:

main1.cpp

myAPI.cpp

myAPI.h

其中myAPI.cpp,myAPI.h定義了兩個函數ADD(), MINUS()

main1.cpp中則調用ADD(), MINUS()

<實驗3>

1)首先產生libmyAPI.so,這一次將動態庫放入./libs目錄下

2)編譯鏈接產生main3

g++ -o main3 main1.cpp -L. -L./libs -lmyAPI

3)成功產生目標,然而運行時,系統報錯:

./main3: error while loading shared libraries: libmyAPI.so: cannot open shared object file: No such file or directory

4)ldd main3查看依賴列表

...

libmyAPI.so => not found

...#其他都正常,省略

發現main3的依賴列表中缺省libmyAPI.so的路徑,

恩???步驟2)中明明給定了libmyAPI.so的找尋路徑-L./libs,為什麽依賴列表中會沒有?更奇怪的是當我們將libmyAPI.so拷貝到main3所在的目錄下時,又運行成功,這又是怎麽回事?

分析:

首先排除g++在鏈接時不會去驗證libmyAPI.so是否真實存在這種猜想,因為如果libmyAPI.so不存在,2)中的編譯指令不會通過,直接提示缺少庫文件。

實際上,原因在於連接器ld的工作模式,註意到2)中指定動態庫的參數-lmyAPI而不是直接給其名稱。在執行這條編譯指令時,鏈接器ld此處有疑問:在執行g++指令時,到時會不會是用ld指令?)會自動將-lmyAPI擴展為libmyAPI.so然後根據參數中的庫文件查找路徑-L.-L./libs去尋找libmyAPI.so,可能編譯器會驗證下libmyAPI.so是否包含目標的依賴(包括直接依賴和間接依賴),如果包含則將參數指定的路徑寫入依賴表,不包含的直接忽略(此處有疑問,也可能連基本的驗證一下libmyAPI.so是否包含源文件中依賴的代碼都不會做,但從<實驗5>結果看更有可能會驗證),當遇到依賴文件不能完全滿足編譯目標的需求時,編譯器不會報錯,只有在需要鏈接時才會報錯。又因為編譯時依賴的對象是動態庫,不會將libmyAPI.so中的代碼全部打包到main3中,簡單記錄libmyAPI.so文件的路徑,以便以後運行時動態去加載。然而,執行編譯指令時,用戶指定的查找路徑-L.-L./libs應該只是用來驗證,不會將這個查詢路徑連同文件名一起填寫到main3的依賴列表中,此時只會填寫為libmyAPI.so,要想加上路徑,用戶必須在編譯參數中寫上動態庫的全稱(當然也可以是絕對路徑,但是這樣做不利於程序移植)如:

g++ -o main3 main1.cpp ./libs/libmyAPI.so

此時再去查看main3的依賴列表:

...

./libs/libmyAPI.so (0x00007f07463ce000)

...

在真正運行main3時,ld會首先查找其配置目錄/etc/ld.so.conf.d/下的若*.conf文件(可以是到某個.conf文件的鏈接),文件中配置了ld的默認查詢路徑,若默認路徑下目錄下有無libmyAPI.so文件,再直接查詢依賴列表中的路徑是否有效。

3 .so文件間依賴的問題

現在有工程,源文件包括:

main2.cpp

myAPI.cpp

myAPI.h

myHandler.cpp

myHandler.h

其中myAPI.cpp,myAPI.h定義了兩個函數ADD(), MINUS()

myHandler.cpp,myHandler.h中也定義了兩個函數mutil_ADD(), mutil_MINUS(),而兩個函數分別調用了ADD(), MINUS()函數;

main.cpp中則調用mutil_ADD(), mutil_MINUS()

<實驗4>

1)鏈接產生.so文件

g++ myAPI.o -shared -o libs/libmyAPI.so

g++ myHandler.o -shared -o libs/libmyHandler.so

2).so文件編譯鏈接產生main4

g++ main2.cpp libs/libmyAPI.so libs/libmyHandler.so -o main4

結果鏈接失敗:

./libs/libmyHandler.so:對‘ADD(int, int)’未定義的引用

./libs/libmyHandler.so:對‘MINUS(int, int)’未定義的引用

collect2: error: ld returned 1 exit status

3)產看libmyHandler.so的依賴列表ldd ./libs/libmyHandler.so

statically linked

表明,libmyHandler.so中沒有依賴任何動態庫。

然而,myHandler.cpp中不是調用了myAPI.cpp定義的函數麽,為什麽編譯指令

g++ myHandler.o -shared -o libs/libmyHandler.so可以通過編譯呢?

原因是這種情況下,編譯器最多驗證下指定的依賴文件(包括.so.a)是否存在,不會進一步驗證依賴文件是否覆蓋源文件的需求,只有當需要連接時才會驗證依賴是否有效。

分析:由於myHandler.cpp文件使用了myAPI.cpp中定義的函數,然而在編譯鏈接libmyHandler.so時既沒有聲明需要鏈接myAPI.o文件也沒有鏈接libmyAPI.so,所以現在的libmyHandler.so中的ADD()MINUS()屬於未定義的引用。

<實驗5>

1)鏈接產生.so文件

g++ -fPIC -c -o libs/myAPI.o myAPI.cpp

g++ -fPIC -c -o libs/myHandler.o myHandler.cpp

g++ libs/myAPI.o -shared -o libs/libmyAPI.so

g++ libs/myHandler.o libs/libmyAPI.so -shared -o libs/libmyHandler.so

2).so文件編譯鏈接產生main5

g++ main2.cpp libs/libmyAPI.so libs/libmyHandler.so -o main5

# g++ main2.cpp libs/libmyHandler.so -o main5

3)編譯、運行成功,產看main5依賴表

...

libs/libmyHandler.so (0x00007f1c5030e000)

libs/libmyAPI.so (0x00007f1aea515000)

...

???為啥2)中明明聲明了libs/libmyAPI.solibs/libmyHandler.so兩個依賴,為啥依賴列表中只有libs/libmyHandler.so

可能編譯器會驗證下libmyAPI.so是否包含目標的依賴(包括直接依賴和間接依賴),如果包含則將參數指定的路徑寫入依賴表,不包含的直接忽略。

4)產看ldd libs/libmyHandler.so 依賴表

...

libs/libmyAPI.so (0x00007f69ac934000)

...

此時的依賴層次圖:

4同庫新舊版同引用問題

考慮一種特殊情況,要在同一個程序中鏈接同一個庫的新舊兩個版本。

<實驗6>

目錄結構如下:

libs2/

myAPI.cpp

myAPI.h

libs1/

myAPI.cpp

myAPI.h

myHandler1.cpp

myHandler1.h

myHandler2.cpp

myHandler2.h

main4.cpp

其中libs2/myAPI.cpp與libs1/myAPI.cpp中都定義有命名為的ADD()MINUS()函數(參數列表,返回值,函數名均相同,區別是函數打印的信息不同),而myHandler2.cppmyHandler1.cpp則分別調用llibs2/myAPI.cpp與libs1/myAPI.cpp中的同名函數。main4.cpp中又調用myHandler.cppmyHandler1.cpp中定義的函數。

Makefile如下:

step4:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI.o libs1/myAPI.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1.o myHandler1.cpp

g++ libs2/myAPI.o -shared -o libs2/libmyAPI.so

g++ libs1/myAPI.o -shared -o libs1/libmyAPI.so

g++ libs2/myHandler2.o libs2/libmyAPI.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1.o libs1/libmyAPI.so -shared -o libs1/libmyHandler1.so

g++ main4.cpp libs1/libmyHandler1.so libs/libmyHandler.so -o main6

理論上依賴關系應該如下圖:

ldd main6查看main6關系列表:

...

libs1/libmyHandler1.so (0x00007f61c7837000)

libs2/libmyHandler2.so (0x00007f61c7635000)

libs1/libmyAPI.so (0x00007f61c6ce7000)

libs2/libmyAPI.so (0x00007f61c6ae5000)

...

哇塞,貌似很科學的樣子。然後運行發現,完全不正確:

libs1: ADD #正確的話應該打印libs2: ADD

libs1: ADD #正確的話應該打印libs2: ADD

mutil_ADD2(1,2,3) = 6

libs1: MINUS #正確的話應該打印libs2: MINUS

libs1: MINUS #正確的話應該打印libs2: MINUS

mutil_MINUS2(1,2,3) = -4

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

當註釋掉main4.cpp調用myHandler1.cpp的代碼後,編譯運行:

libs2: ADD

libs2: ADD

mutil_ADD2(1,2,3) = 6

libs2: MINUS

libs2: MINUS

mutil_MINUS2(1,2,3) = -4

結果完全正確!!!

接著,註釋掉調用myHandler2.cpp的代碼後,編譯運行,

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

結果還是完全正確!!!

為啥,單獨編譯就能得到正確結果,一起調用就不行呢???

猜測可能和libs1/libmyAPI.solibs2/libmyAPI.so文件名重復有關,接下來再進行第2種情況的實驗。

<實驗7>

不修改任何代碼,僅修改Makefile

step4:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI.o libs1/myAPI.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1.o myHandler1.cpp

g++ libs2/myAPI.o -shared -o libs2/libmyAPI2.so

g++ libs1/myAPI.o -shared -o libs1/libmyAPI1.so

g++ libs2/myHandler2.o libs2/libmyAPI2.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1.o libs1/libmyAPI1.so -shared -o libs1/libmyHandler1.so

g++ main4.cpp libs1/libmyHandler1.so libs2/libmyHandler2.so -o main6

編譯時讓.so文件名不同,ldd main6查看main6關系列表:

...

libs1/libmyHandler1.so (0x00007f61c7837000)

libs2/libmyHandler2.so (0x00007f61c7635000)

libs1/libmyAPI1.so (0x00007f61c6ce7000)

libs2/libmyAPI2.so (0x00007f61c6ae5000)

...

依然很有道理的樣子,然後編譯,運行

libs1: ADD

libs1: ADD

mutil_ADD2(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS2(1,2,3) = -4

libs1: ADD

libs1: ADD

mutil_ADD1(1,2,3) = 6

libs1: MINUS

libs1: MINUS

mutil_MINUS1(1,2,3) = -4

結果還是不對!!!

修改.so文件名行不通,那麽修改.cpp.h文件名呢?於是有情況3

<實驗8>

目錄結構如下:

libs2/

myAPI.cpp

myAPI.h

libs1/

myAPI1.cpp

myAPI1.h

myHandler1_1.cpp

myHandler1_1.h

myHandler2.cpp

myHandler2.h

main5.cpp

其中libs2/myAPI.cpp與libs1/myAPI1.cpp中都定義有命名為的ADD()MINUS()函數(參數列表,返回值,函數名均相同,區別是函數打印的信息不同),而myHandler2.cppmyHandler1_1.cpp則分別調用llibs2/myAPI.cpp與libs1/myAPI1.cpp中的同名函數。main4.cpp中又調用myHandler.cppmyHandler1_1.cpp中定義的函數。

Makefile

step4:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

g++ libs2/myAPI.o -shared -o libs2/libmyAPI2.so

g++ libs1/myAPI1.o -shared -o libs1/libmyAPI1.so

g++ libs2/myHandler2.o libs2/libmyAPI2.so -shared -o libs2/libmyHandler2.so

g++ libs1/myHandler1_1.o libs1/libmyAPI1.so -shared -o libs1/libmyHandler1_1.so

g++ main5.cpp libs1/libmyHandler1_1.so libs2/libmyHandler2.so -o main7.out

編譯,運行,結果還是不對,好吧,

看來問題就是,函數重名!!!

<實驗9>

最後,修改Makefile,企圖將所有代碼均打包給main6

step4:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

g++ main4.cpp libs2/myAPI.o libs1/myAPI1.o libs2/myHandler2.o libs1/myHandler1_1.o -o main7.out

結果,編譯報錯:

g++ -fPIC -c -o libs2/myAPI.o libs2/myAPI.cpp

g++ -fPIC -c -o libs1/myAPI1.o libs1/myAPI1.cpp

g++ -fPIC -c -o libs2/myHandler2.o myHandler2.cpp

g++ -fPIC -c -o libs1/myHandler1_1.o myHandler1_1.cpp

g++ main4.cpp libs2/myAPI.o libs1/myAPI1.o libs2/myHandler2.o libs1/myHandler1_1.o -o main7

libs1/myAPI1.o:在函數‘ADD(int, int)’中:

myAPI1.cpp:(.text+0x0): `ADD(int, int)‘被多次定義

libs2/myAPI.o:myAPI.cpp:(.text+0x0):第一次在此定義

libs1/myAPI1.o:在函數‘MINUS(int, int)’中:

myAPI1.cpp:(.text+0x43): `MINUS(int, int)‘被多次定義

libs2/myAPI.o:myAPI.cpp:(.text+0x43):第一次在此定義

collect2: error: ld returned 1 exit status

Makefile:2: recipe for target ‘step4‘ failed

make: *** [step4] Error 1

分析:果然,函數多次被定義,是不行的!!!即使c++支持重載,但是也要求參數列表不能相同,事實證明在同一個程序中不能同時引用相同庫的不同版本,主要就是因為函數重復定義

關於動態鏈接庫的分析