Linux靜態連結(庫)、動態連結(庫)、可執行檔載入相關問題(建立、選項、環境變數等)

來源:互聯網
上載者:User

參考:

http://www.cnblogs.com/hanyan225/archive/2010/10/01/1839906.html

http://www.west263.com/info/html/wangzhanyunying/jianzhanjingyan/20080417/70218.html

http://www.cnblogs.com/lidp/archive/2009/12/02/1697479.html

GNU Binutils:http://en.wikipedia.org/wiki/GNU_Binutils

Linux靜態連結(庫)、動態連結(庫)、可執行檔載入相關問題(建立、選項、環境變數等)

(1) 概念
函數庫一般分為兩種,靜態連結庫(static linking lib)和動態連結程式庫(dynamic linking lib),無論是windows還是linux,都是如此,當然,其組織形式和調用方式可能會有所不同,這裡只針對Linux來說明。那麼,動態連結程式庫和靜態連結庫都是編譯得到的,所以,編譯器得到靜態連結庫的連結過程就是靜態連結,動態連結同理。
動態連結程式庫一般是.so為尾碼,靜態連結庫以.a為尾碼。
為了在同一系統中使用不同版本的庫,可以在庫檔案名稱後加上版本號碼為尾碼,但由於程式串連預設以.so為檔案尾碼名。所以為了使用這些庫,通常使用建立符號串連的方式。如下:
ln -s libtest.so.1.0 libtest.so

(2) 靜態連結庫和動態連結程式庫的使用區別
首先,回顧一點,一個程式,從原始碼到運行,包括:編譯(compile)、連結(link)、載入(load)、運行(execute),對應的GNU工具一般為:編譯器compiler(gcc)、連結器linker(ld)、載入器loader(其中動態連結程式庫載入器為ld.so(ld-linux.version.so),在/lib目錄中,如 /lib/ld-linux.so.2,所以不能直接在命令列下運行ld-linux.so,需要完整路徑,載入器一般不需要我們直接運行,在運行可執行程式的載入過程中包含有動態載入的過程)。經常會將編譯和連結統稱為編譯,期間為編譯時間(compile
time);而載入和運行統稱為運行,期間為運行時或執行時(runtime/execution time)。
靜態連結庫:編譯時間(compile time)被使用(更詳細的是連結的時候)。在連結靜態庫的時候,連結器會在其中找到所需要連結的函數,然後將它們拷貝到執行檔案,這種拷貝是完整的拷貝,所以在連結成功後,程式運行不需要靜態庫的參與。
動態連結程式庫:編譯時間和運行時都被使用。在編譯時間,連結器在其中找到所需要的函數(或其他對象檔案),產生地址/位置無關代碼(Position Independent Code (PIC)),並沒有真正的實現拷貝;在運行時(runtime/execution-time),某個程式在運行中要調用某個動態連結程式庫函數的時候,作業系統首先會查看所有正在啟動並執行程式,看在記憶體裡是否已有此庫函數的拷貝了,如果有,則讓其共用那一個拷貝;只有沒有才連結載入。
說明:Linux下進行連結的預設操作是先考慮動態連結程式庫,即如果同時存在靜態和動態庫,不特別指定的話,將與動態庫相串連。

(3) 如何產生靜態庫和動態庫和nm命令瞭解
代碼如下(這裡test1和test2都是foo函數,列印的內容不一樣,下面的內容會使用這兩個連結庫來學習其他內容):

// File: test1.cpp#include <stdio.h>void foo(){printf("This is foo in test1\n");}
// File: test1.h#ifndef TEST1_H#define TEST1_Hvoid foo();#endif
// File: test2.cpp#include <stdio.h>void foo(){printf("This is foo in test2\n");}
// File: test2.h#ifndef TEST2_H#define TEST2_Hvoid foo();#endif

1. 靜態連結.a:

首先,先編譯得到中間檔案.o:

#lstest1.cpp  test1.h  test2.cpp  test2.h#gcc -c test1.cpp -o test1.o#gcc -c test2.cpp -o test2.o#lstest1.cpp  test1.h  test1.o  test2.cpp  test2.h  test2.o#

然後,利用ar命令打包得到.a檔案:

#ar crv libtest1.a test1.oa - test1.o#lslibtest1.a  test1.cpp  test1.h  test1.o  test2.cpp  test2.h  test2.o

(libtest2.a可以同理得到,說明:這裡是由一個.o得到一個.a的情況)

命令:ar

說明:對於需要將多個.o打包成一個.a的情況,可以直接在後面列舉所有的.o檔案即可。另外,ar命令有很多選項,對於.a已經存在,往其中插入模組(.o),如何處理已經存在的.o等等其它的處理情況,這裡不詳細討論。一般的使用是crv選項,c表示不管.a是否已經存在建立新的.a檔案,r表示在庫中插入模組(替換)。當插入的模組名已在庫中存在,則替換同名的模組。假如若干模組中有一個模組在庫中不存在,ar顯示一個錯誤訊息,並不替換其他同名模組。預設的情況下,新的成員增加在庫的結尾處,能夠使用其他任選項來改變增加的位置。v表示顯示詳細操作資訊。具體ar的使用參考:http://www.west263.com/info/html/wangzhanyunying/jianzhanjingyan/20080417/70218.html

關於nm命令,用來列出目的文件的符號清單,不僅僅適用於.a,也用於.so等。也不更多說明,一般常用的就是nm file或者nm -o file(資訊會更詳細),如下:

#ar crv libtest1.a test1.oa - test1.o#ar crv libtest2.a test2.oa - test2.o#ar crv libtest12.a test1.o test2.o a - test1.oa - test2.o#nm libtest1.a test1.o:0000000000000000 T _Z3foov                 U puts#nm libtest2.a test2.o:0000000000000000 T _Z3foov                 U puts#nm libtest12.a test1.o:0000000000000000 T _Z3foov                 U putstest2.o:0000000000000000 T _Z3foov                 U puts#

2. 動態連結得到.so:
首先,使用gcc的-fPIC選項編譯得到PIC(位置無關代碼)的中間檔案。

然後,使用gcc的-shared選項對.o檔案進行連結。

如下:

#gcc -c -fPIC test1.cpp -o test1.o#gcc -c -fPIC test2.cpp -o test2.o#gcc -shared test1.o -o libtest1.so#gcc -shared test2.o -o libtest2.so#gcc -shared test1.o test2.o -o libtest12.sotest2.o: In function `foo()':test2.cpp:(.text+0x0): multiple definition of `foo()'test1.o:test1.cpp:(.text+0x0): first defined herecollect2: ld 返回 1#

(從這裡也可以看到,gcc產生動態連結程式庫會檢查是否有重複定義的情況,上面的靜態打包ar命令就不會檢查。)

說明:必須使用-fPIC選項編譯,否則使用-shared產生動態連結程式庫的時候會出現連結錯誤。當然,上面是編譯和連結分開的,也是可以一起完成,就不需要-c選項了,如gcc -fPIC -shared test.c -o libtest.so等。

關於nm在查看.so時候常見的使用方式:

上面有關於nm用於查看.a檔案的說明,對於.so,通常使用下面的選項查看:

(參考:http://blog.sina.com.cn/s/blog_5dc87df60100bike.html 

http://www.cppblog.com/noflybird/archive/2010/05/06/114618.aspx

 http://apps.hi.baidu.com/share/detail/20625788)

-C:用C/C++的方式顯示函數名(函數名在編譯後會稍微改變一下,demangle和mangle)。

-g:僅顯示外部符號。

我一般使用如下來查看動態連結程式庫裡面定義的函數(也可以用這個查看.a更好):

nm -C -g libtest1.so -A

或者進一步用T過濾結果:

nm -C -g libtest1.so -A | grep T

如下:

#nm -C -g libtest1.so -A | grep Tlibtest1.so:000000000000057c T foo()libtest1.so:00000000000005cc T _finilibtest1.so:0000000000000460 T _init

總結:

1. 產生靜態連結庫的方式:先gcc編譯得到.o檔案,使用ar命令打包.o檔案即可(ar crv xxx.a xxx.o xxx1.o xxx2.o...)。

2. 產生動態連結程式庫的方式:用gcc -fPIC編譯得到.o檔案,然後用-shared選項進行連結即可。

3. 查看靜態連結庫和動態連結程式庫的符號檔案:nm,也可以查看其它檔案的符號檔案。比如:nm -C -g libtest1.so/libtest1.a -A | grep T的形式。

4. 經過上面的折騰,得到了如下的檔案(下面會用到):

#lslibtest12.a  libtest1.a  libtest1.so  libtest2.a  libtest2.so  test1.h  test2.h#

(4) 靜態連結庫和動態連結程式庫的“原始”使用

測試程式:

// File: main.cpp#include "test1.h"int main(){foo();return 0;}

1、和靜態庫連結:

直接參与連結即可:

#lslibtest12.a  libtest1.a  libtest2.a  main.cpp  test1.h  test2.h#gcc main.cpp libtest1.a #./a.out This is foo in test1#

當然,也可以把編譯和連結分開,那麼就先gcc -c main.cpp -o main.o得到main.o,然後gcc main.o libtest1.a即可。

這是最簡單的和靜態庫連結的方式,直接作為gcc的輸入參數(輸入檔案)參與連結。更複雜的情況是,要連結的庫不在目前的目錄下,那麼當然,需要指定檔案的路徑(相對路徑或絕對路徑)如下:

#lslib  main.cpp  test1.h  test2.h#gcc main.cpp ./lib/libtest1.a#./a.out This is foo in test1#

2、和動態庫連結:

直接參与連結即可,和靜態連結庫一樣,gcc xxx.cpp xxx.so libs/abc/xxx.so.....,將要連結的庫作為輸入檔案參數傳遞給gcc即可。

#lslib  main.cpp  test1.h  test2.h#gcc main.cpp lib/libtest1.so #./a.out This is foo in test1#

總結:上面這是最“原始"的方式連連結靜態庫和動態庫,即把庫檔案作為gcc的輸入檔案,規則當然和源檔案和目標檔案類似了,不管是使用相對路徑還是絕對路徑,總之是必須能讓gcc直接找到的。對於這種方式,使用-L或者把libtest1.so複製到/lib、/usr/lib這些方式,都不能簡化為"gcc main.cpp libtest1.so”(當然是libtest1.so不在目前的目錄的情況下)。說明:這一點是很基礎的,但是時間久了反而犯迷糊了,以為-L也能對這種最原始的情況起到作用(gcc main.cpp
libtest1.so -L./lib,其中libtest1.so在lib檔案夾中)。

(5) 使用-l和-L連結靜態庫(-static)和動態庫

上面的最原始的方式連結到動態庫的方式有一個缺點在於:需要指定要連結的庫的相對或絕對路徑。那麼,一個情況是,如果我們的程式需要連結到一些庫,比如linux提供了很多庫如pthread庫等,如果需要連結到它們,當然,使用完整的路徑是可以的。如下:

#gcc /lib64/libpthread.so.0 main.cpp lib/libtest1.so #

這樣的缺點是很明顯的。為了改進,gcc提供了-l的方式來簡化這個問題,在gcc選項中使用-lxxxx指定要連結的庫的庫名,對應的庫名為libxxx.so或libxxx.so,同時,這個庫是存在於“系統庫目錄”中。對於Linux而言,系統庫目錄預設為/lib和/usr/lib(或者/lib64和/usr/lib64,這裡不討論關於32和64位的問題)。另外,-L選項則用於將一個指定的目錄加入到”系統庫“的範疇,其實就是加入到連結器對庫的搜尋路徑了。

所以,對於上面的例子,可以:

A、把libtest1.so複製到/lib中或者/usr/lib中,就可以直接使用:gcc -ltest1 main.cpp編譯了。

B、如果libtest1.so不在搜尋路徑(/lib和/usr/lib中),那麼編譯會得到類似於:/usr/bin/ld: cannot find -ltest1的錯誤。可以使用-L將指定的目錄加入到搜尋路徑,如:

#lslib  main.cpp  test1.h  test2.h#gcc -ltest1 main.cpp -Llib#gcc -ltest1 main.cpp -L./lib#

至於靜態庫,其規則和上面的一樣,同樣有"系統庫目錄"和-l的簡寫以及-L添加搜尋路徑。但是,預設情況下,gcc是進行動態連結的。如果需要使用靜態連結,需要使用-static選項,使用了-static選項後,所有的-l指定的庫都會去搜尋路徑中尋找對應的.a靜態庫。而且,需要注意的是,-static也會改變gcc的預設連結的庫的連結方式,也會連結靜態庫(關於gcc預設連結的庫,比如libc等,預設都是動態庫的,使用了-static後,會連結到libc.a靜態庫,關於哪些庫是gcc的預設連結庫下面會介紹)。

(6) gcc的-include和-I參數

這個內容本來和這裡的內容無關,但是一般總是和-L一起討論,所以就這裡說明一下。-include用於包含標頭檔,功能和#include一樣,一般很少用,一般都是在代碼裡用#include了。-I用於指定額外的“系統標頭檔”的搜尋路徑,對應於-L選項類似的功能。

對於使用<>的#include,標頭檔會去系統標頭檔(/usr/include)搜尋,如果某些標頭檔不在系統標頭檔路徑中,就需要使用-I指定了。

PS:-I和-L都可以在一個命令列下指定多個,增加多個路徑,如gcc -lxxx foo.cpp -Llib1/ -Llib2/ -Llib3/等。

(7) 可執行檔的載入和運行和ldd命令

上面的內容都是關於如何編譯和連結的問題,得到可執行檔,事實上,上面的例子中,有些可執行檔是不能執行的。

對於連結靜態庫的程式,由於靜態連結會將代碼直接拷貝到可執行檔中,連結後的可執行檔是可以不依賴於靜態庫.a直接啟動並執行,所以問題主要是關於使用了動態庫的程式的運行問題。對於使用動態庫的程式,在程式載入過程中,有一個重要的一步是動態庫的載入(有時候也說成動態庫的動態連結),loader為/lib/ld-linux.so(參考http://blog.csdn.net/gengshenghong/article/details/7096511的(3)理解)。

 動態庫的搜尋路徑搜尋的先後順序是:
1.編譯目標代碼時指定的動態庫搜尋路徑;
2.環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;
3.設定檔/etc/ld.so.conf中指定的動態庫搜尋路徑;//只需在在該檔案中追加一行庫所在的完整路徑如"/root/test/conf/lib"即可,然後ldconfig是修改生效。
4.預設的動態庫搜尋路徑/lib;
5.預設的動態庫搜尋路徑/usr/lib。

先看下面的例子,說明一個問題:

#tree.├── lib│   ├── libtest1.so│   └── libtest2.so├── libtest1.so├── libtest2.so├── main.cpp├── test1.h└── test2.h1 directory, 7 files#gcc main.cpp libtest1.so #./a.out ./a.out: error while loading shared libraries: libtest1.so: cannot open shared object file: No such file or directory#gcc main.cpp ./libtest1.so #./a.out This is foo in test1#gcc main.cpp lib/libtest1.so #./a.out This is foo in test1#gcc main.cpp /home/sgeng2/labs_temp/temp/libtest1.so #./a.out This is foo in test1

這裡,使用gcc main.cpp libtest1.so編譯成功了,居然運行說找不到共用庫,使用./就可以了。當然,對於上面的任意一種情況,連結後刪除了.so肯定也是運行不了的。那麼,如果是下面的情況呢:

#lslib  libtest1.so  libtest2.so  main.cpp  test1.h  test2.h#gcc main.cpp ./libtest1.so #./a.out This is foo in test1#../temp/a.out This is foo in test1#cd ..#temp/a.out temp/a.out: error while loading shared libraries: ./libtest1.so: cannot open shared object file: No such file or directory#

會發現,使用gcc mai.cpp ./libtest1.so編譯後,運行時,這個"./libtest1.so“的資訊是儲存了的,所以,只能在目前的目錄運行a.out,如果cd切換到其它目錄,那麼它仍然是去尋找./libtest1.so,自然就找不到了。那麼,如果這裡使用的是lib/libtest1.so連結,也是一樣的,啟動並執行時候,當前工作目錄下需要有一個lib/libtest1.so的檔案,如果使用的是絕對路徑,那麼當然,只要.so沒有刪除,都是可以啟動並執行。可見,這種最“原始"的連結方式,它還是很傻的,局限性比較大。

總結:這種使用"原始"的方式來連結得到可執行檔,.so的路徑資訊是儲存在可執行檔中的,在載入的時候需要尋找對應的.so是否存在。上面的提到了"動態庫的搜尋路徑的搜尋順序",這是用於使用-l和-L來連結的動態連結程式庫的情況,對於這裡的"原始"的方式並無改變,比如使用gcc main.cpp ./libtest1.so編譯後,哪怕將libtest1.so複製到了/usr/lib中也沒用。(個人理解,這種原始的指定路徑的編譯方式,應該屬於"編譯目標代碼時指定的動態庫搜尋路徑”,當然,一般說到這個編譯目標代碼時指定的動態庫搜尋路徑,是指另外一個選項,下一部分說明)。

ldd命令:ldd命令用於顯示一個可執行檔中依賴的所有.so在當前系統中的位置,簡單理解,就是它會類比可執行檔的載入,輸出各個.so檔案的路徑,比如上面的所有路徑的順序,在不同的路徑中放入.so,ldd得到的路徑會顯示實際loader尋找到的路徑。如果找不到.so或者.so有問題,ldd也會報錯。

(8) 編譯目標代碼時指定的動態庫搜尋路徑"-Wl,-rpath,"

gcc提供了-Wl,-rpath,xxx來指定程式運行時候搜尋動態庫的路徑,和-L沒有任何關係,-Wl,-rpath是在編譯時間指定,但是其用於運行時的loader,所以是一個比較特殊的選項,容易和-L混淆。說明:-Wl,-rpath,xxx指定的路徑,如果使用的是相對路徑,那麼啟動並執行時候,也是根據相對路徑來尋找.so,所以其實和上面說的"原始"方式進行連結,應該本質上是一回事。

這個選項可以指定多個路徑,用":"分開即可。如:gcc -Wl,-rpath,path1:path2:path3 main.cpp -ltest -Llib/等。

總結:

1. 對於靜態庫,可以使用"原始"方式(指定路徑作為gcc輸入參數)連結靜態庫,也可以使用-l&&-L的方式,但是,需要使用-static選項。另外,預設的gcc連結方式是動態,所以使用了-static,會讓gcc預設連結的庫也連結靜態版本。

2. 對於使用"原始"方式(指定路徑作為gcc輸入參數)連結動態庫的情況,運行時候限制太多,必須是怎麼編譯連結的,就要保證運行時候按照當時的路徑能找到動態連結程式庫,當然,這種方式很少用,一般都是用-l&&-L來連結動態連結程式庫。

3. 對於使用-l來連結.so的情況,編譯器會到/lib,/usr/lib和-L指定的路徑下尋找-l指定的.so檔案來進行連結。搜尋先後順序為:-L指定的路徑;/usr/lib;/lib。

4. 對於使用-l連結.so的情況,在啟動並執行時候。會根據相應的路徑搜尋.so檔案來進行載入運行,載入的時候,動態庫的搜尋路徑搜尋的先後順序是:編譯目標代碼時指定的動態庫搜尋路徑;環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;設定檔/etc/ld.so.conf中指定的動態庫搜尋路徑;預設的動態庫搜尋路徑/lib;預設的動態庫搜尋路徑/usr/lib。

5. -Wl,-rpath用於編譯目標代碼時指定的動態庫搜尋路徑,和-L沒有關係,一個指定的路徑用於運行時載入.so的搜尋,一個指定的路徑用於編譯時間連結.so中符號的搜尋。

(9) 連結多個庫的問題(多個庫都有同一函數的定義)

比如:兩個.a檔案都定義了函數foo,然後都參與連結,實際使用的是哪一個?如果是兩個.so呢?一個.a和一個.so呢?

(10) 補充:

1. 關於搜尋路徑

上面已經總結了相關的標頭檔和庫檔案的搜尋路徑問題,需要說明的是,實際的GCC的搜尋路徑還可能會和其他因素有關,比如一些環境變數的設定,還有gcc會將自己的安裝目錄下的lib檔案夾或者include檔案夾等等加入搜尋路徑,包括可以通過sysroot等選項改變預設的設定,具體參考http://blog.csdn.net/gengshenghong/article/details/7108634的總結。當然,為了更好的瞭解這些資訊,gcc也提供了幾個相關命令來查看相關資訊。

gcc -print-search-dirs:列印安裝、程式和庫的搜尋路徑。輸出樣本如下:

#gcc --print-search-dirs安裝:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/程式:=/usr/libexec/gcc/x86_64-redhat-linux/4.6.0/:/usr/libexec/gcc/x86_64-redhat-linux/4.6.0/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/bin/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/bin/庫:=/usr/lib/gcc/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../x86_64-redhat-linux/4.6.0/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../lib64/:/lib/x86_64-redhat-linux/4.6.0/:/lib/../lib64/:/usr/lib/x86_64-redhat-linux/4.6.0/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../../x86_64-redhat-linux/lib/:/usr/lib/gcc/x86_64-redhat-linux/4.6.0/../../../:/lib/:/usr/lib/#

gcc -v file.c:顯示編譯file.c時候的詳細資料(其中會顯示一些搜尋標頭檔的順序的資訊和LIBRARY_PATH、COMPILER_PATH等等。也可以直接gcc -v,顯示的就是gcc的基本配置資訊。

gcc -dumpspecs:顯示所有內建 spec 字串。

gcc -dM -E file.c:顯示預先處理file.c中所有的宏定義。

總之,gcc搜尋路徑還是有些複雜的,不需要所有細節全部記清楚,但是至少要知道會有哪些內容需要被考慮。

2. GCC的--sysroot選項

這個選項一般用於交叉編譯,用於指定編譯所需要的標頭檔,及連結庫(的root目錄)等。

3. GCC環境變數和配置選項等

http://www.linuxsir.org/bbs/thread304996.html

http://blog.csdn.net/yangruibao/article/details/6869323

http://jimobit.blog.163.com/blog/static/28325778200991524826935/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.