1. 程式人生 > >linux 編譯,連結和載入

linux 編譯,連結和載入

1.   序

最近在折騰各種.so,碰到了一些問題,一開始對於很多錯誤也沒有頭緒,茫然不知所措。索性化了一天多時間將<<程式設計師的自我修養—連結、裝載與庫>>中部分內容略讀了一遍,主要是關於編譯,連結和載入這塊的。於是順便做個筆記,方便以後回顧。基本上知道了這些,對於編譯,連結和載入過程中產生的各種問題,應該就能從根本上理解並解決了。其實以前上學時也看過那本經典的<<Linker and loader>>,當時還寫了篇<<連結器和載入器原理>>,不過此次會更細緻深入地瞭解下整個編譯連結和載入過程,並結合經常碰到的問題,提出一些解決方案。

廣義的程式碼編譯過程,實際上應該細分為:預處理,編譯,彙編,連結。

預處理過程,負責標頭檔案展開,巨集替換,條件編譯的選擇,刪除註釋等工作。gcc –E表示進行預處理。

編譯過程,負載將預處理生成的檔案,經過詞法分析,語法分析,語義分析及優化後生成彙編檔案。gcc –S表示進行編譯。

彙編,是將彙編程式碼轉換為機器可執行指令的過程。通過使用gcc –C或者as命令完成。

連結,負載根據目標檔案及所需的庫檔案產生最終的可執行檔案。連結主要解決了模組間的相互引用的問題,分為地址和空間分配,符號解析和重定位幾個步驟。實際上在編譯階段生成目標檔案時,會暫時擱置那些外部引用,而這些外部引用就是在連結時進行確定的。連結器在連結時,會根據符號名稱去相應模組中尋找對應符號。待符號確定之後,連結器會重寫之前那些未確定的符號的地址,這個過程就是重定位。

-WL:這個選項可以將指定的引數傳遞給連結器。

比如使用”-Wl,-soname,my-soname”,GCC會將-soname,my-soname傳遞給連結器,用來指定輸出共享庫的SO-NAME。

-shared:表示產生共享物件,產生的程式碼會在裝載時進行重定位。但是無法做到讓一份指令由多個程序共享。因為單純的裝載時重定位會對程式中所有指令和資料中的絕對地址進行修改。要做到讓多個程序共享,還需要加上-fPIC。

-fPIC:地址無關程式碼,是為了能讓多個程序共享一份指令。基本思想就是將指令中需要進行修改的那部分分離出來,跟資料放到一塊。這樣指令部分就可以保持不變,而需要變化的那部分則與資料一塊,每個程序都有自己的一份副本。

-export-dynamic:預設情況下,連結器在產生可執行檔案時,為了減少符號表大隻會將那些被其他模組引用到的符號放到動態符號表。也就是說,在共享模組引用主模組時,只有那些在連結時被共享模組引用到的符號才會被匯出。當程式使用dlopen()載入某個共享模組時,如果該共享模組反向引用了主模組的符號,而該符號可能在連結時因為未被其他模組引用而未被匯出到動態符號表,這樣反向引用就會失敗。這個引數就是用來解決這個問題的。它表示,連結器在產生可執行檔案時,將所有全域性符號匯出動態符號表。

-soname:指定輸出共享庫的SO-NAME。

-I:。指定標頭檔案搜尋路徑。

-l:指定連結某個庫。指定連結的比如是libxxx.so.x.y.z的一個庫,只需要寫-lxxx即可,編譯器根據當前環境,在相關路徑中查詢名為xxx的庫。xxx又稱為共享庫的連結名(link name)。不同的庫可能具有同樣的連結名,比如動態和靜態版本,libxxx.a libxxx.so。如果連結時採用-lxxx,那麼連結器會根據輸出檔案的情況(動態/靜態)選擇合適的版本。比如如果ld採用了-static引數,就會使用靜態版本,如果使用了-Bdynamic(這也是預設情況),就會使用動態版本。

-L:指定連結時查詢路徑,多個路徑用逗號分隔

-rpath:這種方式可以指定產生的目標程式的共享庫查詢路徑。還有一個類似選項-rpath-link,與-rpath選項的區別在於,-rpath選項指定的目錄被硬編碼到可執行檔案中,-rpath-link選項指定的目錄只在連結階段生效。這兩個選項都是連結器ld的選項。更多連結器選項可以通過man ld檢視。

.so和.a的生成,可執行檔案的生成。.a的生成只需要編譯階段,而可執行檔案的生成還需要進行連結。靜態庫檔案的生成很簡單,主要就是分兩步,第一步將原始檔生成目標檔案,可以使用gcc –c,第二步就是將目標檔案打包,可以通過ar實現。所以該過程只要求原始檔能夠通過gcc –c這個命令即可。

共享庫的生成要複雜一些。可以有三種方法生成:
$ld -G
$gcc -shared
$libtool
用ld最複雜,用gcc -shared就簡單的多,但是-shared並非在任何平臺都可以使用。-shared 該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。GNU提供了一個更好的工具libtool,專門用來在各種平臺上生成各種庫。在編譯生成某個.so檔案時,比如liba.so,雖然它裡面可能用到了libb.so的東西,但是在生成a.so時是可以不加-lb的,因為so的生成不會進行符號解析和重定位。

以GCC為例,它在編譯靜態庫/動態庫時到底使用了什麼命令?比如:gcc –v -shared hello.c -o libhello.so。ld –G用來產生.so檔案,也是gcc連結時實際呼叫的命令。

生成可執行檔案時,如果連結的是靜態庫,那麼連結器會按照靜態連結規則,將對應的符號引用進行重定位。而如果是動態庫,連結器會將這個符號標記為動態連結的符號,不進行重定位,而是在裝載時再進行。所以,儘管是動態連結,如果是已經進入到了連結階段,那麼也需要能在相應的.so中找到某符號的定義,否則也會引發Undefined reference to的連結錯誤。因為連結器只有通過.so檔案,才能判斷某符號是個動態連結符號,所以也需要讀取這些.so檔案,找到相應符號的定義。

#include有兩種寫法形式,分別是:

#include <> : 直接到系統指定的某些目錄中去找某些標頭檔案。

#include “” : 先到原始檔所在資料夾去找,然後再到系統指定的某些目錄中去找某些標頭檔案。

gcc尋找標頭檔案的路徑(按照1->2->3的順序):

 1. 在gcc編譯原始檔的時候,通過引數-I指定標頭檔案的搜尋路徑,如果指定路徑有多個路徑時,則按照指定路徑的順序搜尋標頭檔案。命令形式如:“gcc -I /path/where/theheadfile/in sourcefile.c“,這裡原始檔的路徑可以是絕對路徑,也可以是相對路徑。比如設當前路徑為/root/test,include_test.c如果要包含標頭檔案“include/include_test.h“,有兩種方法:

1)include_test.c中#include “include/include_test.h”或者#include "/root/test/include/include_test.h",然後gcc include_test.c即可

2)include_test.c中#include <include_test.h>或者#include <include_test.h>,然後gcc –I include include_test.c也可

 2.通過查詢gcc的環境變數來搜尋標頭檔案位置,分別是:

CPATH/C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH。 

 3. 再在預設目錄下搜尋,分別是:

/usr/include

/usr/local/include

/usr/lib/gcc-lib/i386-linux/2.95.2/include

最後一行是gcc程式的庫檔案地址,各個使用者的系統上可能不一樣。gcc在預設情況下,都會指定到/usr/include資料夾尋找標頭檔案。 gcc還有一個引數:-nostdinc,它使編譯器不再系統預設的標頭檔案目錄裡面找標頭檔案,一般和-I聯合使用,明確限定標頭檔案的位置。在編譯驅動模組時,由於特殊的需求必須強制GCC不搜尋系統預設路徑,也就是不搜尋/usr/include,要用引數-nostdinc,還要自己用-I引數來指定核心標頭檔案路徑,這個時候必須在Makefile中指定。

當#include使用相對路徑的時候,gcc最終會根據上面這些路徑,來最終構建出標頭檔案的位置。如#include <sys/types.h>就是包含檔案/usr/include/sys/types.h

Q: 有幾個庫檔案A.a、B.a、common.a,前兩者用到了定義在後者中的例程,如果把   common.a放在前面,連結器報告存在無法解析的符號名,放在最後則無問題。
A: Floyd Davidson <[email protected]>
連結器按照命令列上指定順序搜尋庫檔案和目標檔案(.a .o),二者之間的區別在   於.o檔案被全部連結進來,而只從庫檔案中析取所需模組,僅當某個模組可以解析當前尚未成功解析的符號時,該模組被析取後連結進來。如果庫檔案無法解析任何當前尚未成功解析的符號,不從中析取也不發生連結。

Unix程式設計新手的常見問題是數學函式並不在標準C庫中,而是在libm.a中   

cc -lm foo.c

這裡foo.c用到了數學庫中的符號,但是連結器無法正確解析。當搜尋到libm.a時,   來自foo.c的數學函式符號尚未出現,因此不需要析取libm.a的任何模組。接下來 foo.o連結進來,增加了一批尚未成功解析的符號,但已經沒有libm.a可供使用了, 因此數學庫必須在foo.o之後被搜尋到。

 cc foo.c –lm

在你的問題中,如果common.a首先被搜尋到,因為不匹配尚未成功解析的符號,而被丟棄。結果A.a和B.a真正連結進來的時候,已經沒有庫可以解析符號了。

連結時需要告訴連結器,在哪裡找到庫檔案?以靜態還是動態的方式連結庫檔案?預設情況下使用動態方式連結,這要求存在對應的.so動態庫檔案,如果不存在,則尋找相應的.a靜態庫檔案。若在編譯時向gcc傳入-static選項,則使用靜態方式連結,這要求所有庫檔案都必須有對應的*.a靜態庫。

1.gcc會去找-L
2.再找gcc的環境變數LIBRARY_PATH  
3.再找內定目錄 /lib /usr/lib /usr/local/lib,這是當初編譯gcc時,寫在檔案裡的

需要澄清一下:

l  ldconfig做的事情都與執行程式時有關,跟編譯時一點關係都沒有

l  需要注意的是LD_LIBRARY_PATH通常只是在程式執行時告訴loader該去哪查詢共享庫,比如gcc編譯時的連結器可能就不會去查詢LD_LIBRARY_PATH。

l  查詢時如果找到了同名的動態庫和靜態庫如何處理?當我們指定一個路徑下的庫檔名時,假如此時同時存在xxx.a和xxx.so的兩個庫形式,那麼優先選擇.so連結(共享庫優先)。如果使用了-static,找到了.so是否會使用?如果使用了-Bdynamic,那找到了.a會不會使用?.a的生成能否依賴.so?它所依賴的這些.so能否不加入-l引數列表?

l  系統中連結器與載入器的區別?編譯時的連結器是ld,載入器則位於ld-linux.so。載入器是否去/etc/ld.so.conf中目錄下去尋找所需的.so,還是依賴於ldconfig去更新ld.so.cache檔案,?答案是依賴於ldconfig去更新cache。

l  系統中的ld與gcc採用的連結器的區別?gcc –v檢視gcc呼叫 as/ld 之類程式的時候傳給它們的引數。通過gcc命令進行連結,與直接使用ld的區別?庫查詢路徑是否不同?gcc的LIBRARY_PATH,應該是gcc本身用的環境變數。

l  GCC,gcc與g++?GCC在預處理階段會呼叫cpp,在編譯階段呼叫gcc或g++,彙編階段呼叫as,最後連結階段比較複雜,在GCC內部的這一步呼叫如下:
$ ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o 
/usr/lib/crti.o /usr/lib/gcc-lib/i686/3.3.1/crtbegin.o
-L/usr/lib/gcc-lib/i686/3.3.1 hello.o -lgcc -lgcc_eh
-lc -lgcc -lgcc_eh /usr/lib/gcc-lib/i686/3.3.1/crtend.o
/usr/lib/crtn.o

GCC(GNU Compiler Collection,GNU編譯器套裝),是一套由 GNU 開發的程式語言編譯器。它是一套以 GPL 及 LGPL 許可證所發行的自由軟體,也是 GNU計劃 的關鍵部分,亦是自由的 類Unix 及蘋果計算機 Mac OS X 作業系統的標準編譯器。GCC(特別是其中的C語言編譯器)也常被認為是跨平臺編譯器的事實標準。

GCC 原名為 GNU C 語言編譯器(GNU C Compiler),因為它原本只能處理 C語言。GCC 很快地擴充套件,變得可處理 C++。之後也變得可處理 Fortran、Pascal、Objective-C、Java, 以及 Ada 與其他語言。

gcc在後臺實際上經歷了預處理,彙編,編譯,連結這幾個過程,我們可以通過-v引數檢視它的編譯細節,如果想看某個具體的編譯過程,則可以分別使用-E,-S,-c和 -O,對應的後臺工具則分別為cpp,cc1/gcc/g++,as,ld。

符號可以分為強符號和弱符號。比如初始化了的全域性變數就是強符號,未初始化的全域性變數為弱符號。針對強弱符號,有如下規則:

l  不允許強符號重定義,及不同的目標檔案中不能有同名的強符號。Multiple definition錯誤就是違反了這種規定。

l  如果一個符號在某個檔案中是強符號,在另一個檔案中是弱符號,那麼選擇強符號。

l  如果一個弱符號出現在多個目標檔案中,則選擇其中佔用空間最大的那個。

強弱符號都是針對變數定義,對於引用則無效,當然針對應用,也有強引用與弱引用。對於強引用來說,在連結成可執行檔案時,如果找不到對應變數的定義則會報錯。對於弱引用,在這種情況下,不會報錯,連結器會採用一個預設值。

弱符號和弱引用的存在允許使用者定義自己的實現去覆蓋現有的實現。這樣就可以更靈活的對程式進行擴充或裁減。

連結時符號未定義。每個目標檔案中,都可能存在一些,undefined型別的符號,這種型別的符號需要在連結時,能夠在連結產生的全域性符號表中找到其定義,如果找不到,連結器就會產生該錯誤資訊。產生的原因有很多,比如少連結了某個庫,符號寫錯了等等。

Linux系統通過execve()系統呼叫來執行程式,系統會為相應格式的檔案查詢合適的裝載處理函式。elf格式檔案的載入主要通過load_elf_binary()完成,有如下過程:

l  檢查檔案格式有效性,比如magic number,檔案頭

l  尋找動態連結的.interp段,設定動態連結器路徑

l  根據elf檔案描述,對elf檔案進行對映,比如程式碼,資料

l  初始化elf程序環境,比如程序啟動時EDX暫存器地址應該是DT_FINI地址

l  將系統呼叫的返回地址,修改成elf可執行檔案入口點,該入口點取決於程式的連結方式,對於靜態連結的可執行檔案,這個入口點就是elf檔案中e_entry所指向的地址,對於動態連結的elf,程式入口點就是動態連結器。{如何判斷elf採用的是靜態連結還是動態連結的?可以通過對它執行ldd命令,如果是靜態連結,會輸出:statically linked}

load_elf_binary()執行完畢後,可執行檔案就被裝入了記憶體,接下來就可以執行了。

如上,對於動態連結的可執行檔案在真正執行前,實際上它的很多外部符號還處於無效狀態,還未與實際的so檔案聯絡起來,因此還有一個動態連結過程。作業系統會首先載入動態連結器ld.so(/lib/ld-linux.so.2),載入完這個so後,系統就會把控制權交給它,然後它會進行一些初始化操作,根據當前環境引數對可執行檔案進行動態連結工作。動態連結器會尋找所需要的.so檔案並進行裝載,然後進行符號查詢及重定位。如果找不到所需要的符號定義就會產生“undefined symbol”錯誤。

在elf檔案中有一個.interp段,該段指定了所要採用的動態連結器路徑。作業系統會根據該段內容,選擇載入的動態連結器。可以通過objdump –s a.out檢視該段內容,也可以如下命令來檢視:readelf –l a.out|grep interpreter。

.dynamic段,儲存了動態連結器所需的基本資訊,比如依賴於哪些共享物件,動態連結符號表的位置,動態連結重定位表的位置,共享物件初始化程式碼的位置。可以通過readelf –d a.out來檢視該段內容,其中比較重要的有如下幾個:

DT_RPATH:.so搜尋路徑

DT_INIT:初始化程式碼地址

DT_FINIT:結束程式碼地址

DT_NEED:依賴的.so

.dynsym段,動態符號表段。可以通過readelf –sD *.so來檢視

如何確定所需要的載入庫?

如何解析符號?

重名的檔案如何處理?

由於動態連結器本身也是一個.so,因此首先要完成自己的重定位過程,稱為“自舉(bootstrap)”。

完成自舉之後,動態連結器會將可執行檔案和它本身的符號表合併到到一個全域性符號表中。然後連結器開始查詢所需要的.so,在前面提到的.dynamic段中有一個DT_NEEDED,它指出了可執行檔案所依賴的.so,據此連結器就可以找到所需要的.so集合,然後開始裝載該.so,完成後再從它需要的.so集合中取出一個.so,如果在此時找不到某個.so檔案就會產生“cannot open shared object file”錯誤。如果找到相應檔案,就會開啟它,讀取相應的elf檔案頭和.dynamic段,然後將其程式碼段和資料段進行對映。如果該.so還依賴於其他.so,就會把其他.so加入到待裝載集合中,實際上是一個圖的遍歷問題,上面的過程實際上就是廣度優先遍歷。

當新物件被載入之後,它的符號表會被合併到全域性符號表中,所以當所有物件載入之後,全域性符號表中應該包含了程序進行動態連結所需要的所有符號。這個過程裡面存在一個稱為“全域性符號介入(global symbol interpose)”問題,假設a.out依賴了c.so和d.so,而c.so依賴於a.so,d.so依賴於b.so,但是a.so和b.so存在一個同名的符號,此時編譯連結無法發現這個同名的符號,因為編譯a.out時只需要連結c.so和d.so即可,這樣連結器無法發現在a.so和b.so中存在同名的符號,這樣只能載入時解決,而動態連結器有這樣的一個規則:當一個符號需要加入全域性符號表時,如果相同的符號名已經存在,那麼後加入的符號會被忽略。所以,對於這種情況要格外注意。

上面步驟完成後,就會開始進行符號解析和重定位,連結器會開始遍歷可執行檔案和每個.so的重定位表,將它們的GOT/PLT中每個需要重定位的位置進行修正。此時,如果某些符號還是無法解析,就會報出“undefined symbol”錯誤。

重定位完成之後,如果.so中有“.init”段,動態連結器會執行這裡的程式碼,以實現初始化過程,比如靜態/全域性物件的初始化。相應的也會有“.finit”來進行退出操作。可執行檔案本身的.init,不會由動態連結器執行,而是通過程式自身的初始化程式碼完成。

重定位和初始化完成後,動態連結器所要做的事情就完成了,之後就會把控制權交給可執行程式的入口,開始執行程式。

上面的載入過程是在程式啟動時由系統完成的,對於程式本身是透明的。如果程式想在執行時自行載入某個動態庫,實現類似外掛之類的機制,則需要使用如下幾個函式。

用於開啟一個動態庫,將其載入到程序的地址空間,完成初始化過程。C語言原形是:

        void * dlopen(const char *filename, int flag);

如果檔名filename是以“/”開頭,也就是使用絕對路徑,那麼dlopne就直接使用它,而不去查詢某些環境變數或者系統設定的函式庫所在的目錄了。否則dlopen()就會按照下面的次序查詢函式庫檔案:

1. 環境變數LD_LIBRARY指明的路徑。

2. /etc/ld.so.cache中的函式庫列表。

3./lib目錄,然後/usr/lib。不過一些很老的a.out的loader則是採用相反的次序,也就是先查 /usr/lib,然後是/lib。

同時如果filename值設為NULL的話,那麼dlopen將返回全域性符號表的控制代碼。也就是說可以在執行時找到全域性符號表裡的任何一個符號,並可以執行它們。全域性符號表中包括了可執行程式本身,被動態連結器載入到程序中的所有動態庫以及通過dlopen開啟並使用了RTLD_GLOBAL方式的模組中的符號。

dlopen()函式中,引數flag的值必須是RTLD_LAZY或者RTLD_NOW,RTLD_LAZY的意思是resolve undefined symbols as code from the dynamic library is executed,而RTLD_NOW的含義是resolve all undefined symbols before dlopen() returns and fail if this cannot be done'。如果有任何未定義的符號引用的繫結工作無法完成,那麼dlopen就會返回錯誤,這兩種方式必須二選一。此外還有RTLD_GLOBAL,可以與上面兩種方式之一一起使用,表示將被載入的模組的全域性符號合併到程序的全域性符號表中,這樣以後載入的模組就可以使用這些符號。

如果有好幾個函式庫,它們之間有一些依賴關係的話,例如X依賴Y,那麼你就要先載入那些被依賴的函式。例如先載入Y,然後載入X。dlopen()函式的返回值是一個控制代碼,然後後面的函式就通過使用這個控制代碼來做進一步的操作。如果開啟失敗dlopen()就返回一個NULL。如果一個函式庫被多次開啟,它會返回同樣的控制代碼。

同時dlopen還會執行被載入模組的.init段來進行模組的初始化操作。

這個函式在一個已經開啟的函式庫裡面查詢給定的符號。這個函式如下定義:

        void * dlsym(void *handle, char *symbol);

函式中的引數handle就是由dlopen開啟後返回的控制代碼,symbol是一個以’\0’結尾的字串。如果dlsym()函式沒有找到需要查詢的symbol,則返回NULL。dlsym的返回值對於不同的符號型別具有不同的意義,如果符號是個函式,則返回的就是函式地址,如果是變數,就是變數的地址,如果是常量就是該常量的值。如果該常量值剛好是NULL,那如何區分它是沒有找到該常量,還是該常量就是NULL呢?這就需要看dlerror(),如果符號找到了dlerror()就會返回NULL,否則會返回相應的錯誤資訊。

符號優先順序,前面已經提過,在載入過程中如果發現符號名衝突,先載入的符號會優先,這種優先順序方式成為裝載序列(load ordering)。程序通過dlopen載入物件時,動態連結器在進行符號解析和重定位時,都是採用裝載序列。

但是在使用dlsym進行符號地址查詢時,如果是在全域性符號表中進行查詢,即在dlopen時,引數filename為NULL,由於全域性符號表採用的是裝載序列,因此dlsym使用的也是裝載序列。但是,如果是通過某個dlopen開啟的共享物件進行符號查詢的話,採用的是一種稱為依賴序列(dependency ordering)的優先順序方式。即它會以被開啟的那個共享物件為根節點,進行廣度優先遍歷,直到找到該符號。{!因為如果dlopen呼叫時,未採用RTLD_GLOBAL方式的話,那麼被開啟物件的符號表不會加入全域性符號表,這樣它和它依賴的模組可能就會都有自己的符號表,而沒有一個全域性的符號表}

通過呼叫dlerror()函式,我們可以獲得最後一次呼叫dlopen(),dlsym(),或者dlclose()的錯誤資訊。 它的返回值型別是char *,如果返回NULL,表示上一次呼叫成功,如果不是NULL,則代表相應的錯誤資訊。

dlopen()函式的反過程就是dlclose(),dlclose()用於將已經載入的模組解除安裝。系統會維護一個載入引用計數器,當呼叫dlclose的時候,就把這個計數器的計數減一,如果計數器為0,則真正的釋放掉。真正釋放的時候,如果函式庫裡面有_fini()這個函式,則自動呼叫_fini()這個函式,做一些必要的處理。dlclose()返回0表示成功,非0值表示錯誤。

X代表主版本號,y代表次版本號,z代表釋出版本號。主版本號代表庫的重大升級,不同主版本號的庫之間是不相容的。次版本號代表了庫的增量升級,即增加一些新的符號介面,且保持原來的符號不變,具有向後相容性,即依賴於舊版本庫的程式在新版本庫上也可以執行。釋出版本號,代表了庫的一些bug fix和效能優化,不新增任何新的介面,也不對現有介面進行修改。

由上可知主版本號和此版本號實際上決定了程式的介面。通常程式中都會包含了所依賴的庫的名稱和主版本號,系統是通過一種稱為SO-NAME的命名機制來記錄共享庫的依賴關係。每個共享庫都有一個對應的SO-NAME,即名稱和主版本號。在linux系統中,會為每個共享庫在它所在的目錄建立一個跟SO-NAME相同的並且指向它的軟連線。比如系統中有一個/lib/liba.1.1.2那麼系統的共享庫管理程式就會建立一個/lib/liba.1的軟連線。

SO-NAME會指向系統中相同主版本號的最新版的那個庫,使用SO-NAME的目的在讓所有依賴於某個共享庫的模組,在編譯,連結和執行時,都使用SO-NAME,而不使用詳細的版本號。前面提過,在.dynamic段中的DT_NEEDED描述了模組所依賴的共享庫,為了保證相容性,採用SO-NAME進行.dynamic段的依賴描述。通過readelf –d liba.so,可以檢視到elf檔案的DT_NEEDED部分。

待看。

ldconfig,當系統安裝或更新一個共享庫就需要執行這個工具,它會遍歷所有的預設共享庫目錄,比如/lib /usr/lib,然後更新所有的軟連線,使它們指向最新版的共享庫;如果是新安裝的共享庫,則會為它建立軟連線。

動態連結器對儲存在.dynamic段的DT_NEEDED的共享庫的查詢有一定的規則,如果DT_NEEDED儲存的是絕對路徑,那麼動態連結器就按這個路徑去查詢,如果是相對路徑,那麼動態連結器會在/lib /usr/lib和/etc/ld.so.conf配置檔案指定的目錄中進行查詢。

ld.so.conf是一個配置檔案,可能包含其他的配置檔案,存放了路徑資訊。為了避免每次查詢共享庫都去遍歷這些目錄,linux系統提供了ldconfig。ldconfig除了負責每個共享庫目錄下的共享庫SO-NAME的建立刪除和更新外,它還會將這些SO-NAME收集起來存放到/etc/ld.so.cache中。當查詢共享庫時,直接去/etc/ld.so.cache裡找即可,而它的結構則是專為查詢優化過的。如果在這裡沒有找到,它會繼續查詢/lib和/usr/lib,如果還未找到就宣告失敗。

所以,如果在系統指定的共享庫目錄下,新增刪除或更新任何一個共享庫,或者更改了/etc/ld.so.conf的配置,都應該執行ldconfig這個程式。以便調整SO-NAME和/etc/ld.so.cache,而很多安裝軟體包實際在往系統安裝共享庫之後都會呼叫ldconfig。

此外,還可以通過LD_LIBRARY_PATH來改變庫搜尋路徑。如果為程序設定了LD_LIBRARY_PATH,那麼在啟動時,動態連結器會首先在LD_LIBRARY_PATH指定的目錄下查詢。但是,有不少聲音主張要避免使用 LD_LIBRARY_PATH 變數,尤其是作為全域性變數。這些聲音是:
* LD_LIBRARY_PATH is not the answer - http://prefetch.net/articles/linkers.badldlibrary.html
* Why LD_LIBRARY_PATH is bad - http://xahlee.org/UnixResource_dir/_/ldpath.html
* LD_LIBRARY_PATH - just say no - http://blogs.sun.com/rie/date/20040710

綜上,動態連結器會按照如下順序載入或查詢共享庫:

l  連結時由-rpath選項指定的目錄(已被硬編碼到可執行檔案中)

l  LD_LIBRARY_PATH指定的目錄

l  路徑快取檔案/etc/ld.so.cache指定的路徑

l  預設共享庫目錄,先/usr/lib,後/lib

LD_PRELOAD,可以預先裝載一些共享庫。由LD_PRELOAD指定的檔案,會在動態連結庫按指定規則搜尋共享庫之前載入。比LD_LIBRARY_PATH指定的還要優先,同時無論程式是否依賴於它,LD_PRELOAD指定的共享庫都會被裝載。由於全域性符號介入這一機制,LD_PRELOAD指定的庫的符號會覆蓋後面載入的庫的同名符號。這就使得我們可以修改標準c庫的某些或某幾個函式,而不影響其他使用。比如我們可以在實驗程式執行時通過設定LD_PRELOAD,來讓系統優先載入我們修改後的庫。

我們將函式和變數稱為符號,函式名和變數名就是符號名。每個目標檔案都有一個符號表。表中記錄了所用到的所有符號。通常,連結過程只關注全域性性的符號,即那些本模組引用自別處的符號,及可能被別處引用的在本模組定義的那些。

可以使用readelf,objdump,nm來檢視符號。此外使用c++filt工具可以檢視被編譯器改名(為支援過載及名字空間等機制)後的符號對應的原始名稱。

動態載入時,如果找不到某個符號引用的定義,就會產生該錯誤。通常是該符號所在的動態庫未被載入,也就是說DT_NEEED缺少了某個.so。解決方式就是在連結程式時,使用-l指定所需要的庫。

原因可能有多種:比如malloc了一段記憶體空間,但是寫的時候越界了,這實際上會導致corruption;連結了同一個庫的動態和靜態兩個版本,且該庫內具有全域性或靜態變數;不同的庫內含有相同的全域性或靜態變數。這裡主要關注下由於連結多個具有同名變數的庫的情況。

在通常情況下,共享庫都是通過使用附加選項 -fpic 或 -fPIC 進行編譯,從目的碼產生位置無關的程式碼(Position Independent Code,PIC),使用 -shared選項將目的碼放進共享目標庫中。位置無關程式碼需要能夠被載入到不同程序的不同地址,並且能得以正確的執行,故其程式碼要經過特別的編譯處理:位置無關程式碼(PIC)對常量和函式入口地址的操作都是採用基於基暫存器(base register)BASE+ 偏移量的相對地址的定址方式。即使程式被裝載到記憶體中的不同地址,即 BASE 值不同,而偏移量是不變的,所以程式仍然可以找到正確的入口地址或者常量。

然而,當應用程式連結了多個共享庫,如果在這些共享庫中,存在相同作用域範圍的同名靜態成員變數或者同名 ( 非靜態 ) 全域性變數,那麼當程式訪問完靜態成員變數或全域性變數結束析構時,由於某記憶體塊的 double free 會導致 core dump,這是由於 Linux 編譯器的缺陷造成的。

連結時符號查詢原理如下:

           1、應用程式在連結階段時,會順序生成符號表。也就是說,在應用程式中涉及到的符號,會在連結檔案中逐個順次查詢
           2、一旦查詢到符號,就停止本符號的查詢工作,轉向第二個符號的查詢
           3、如果沒有用到.a裡的符號,即查詢的過程中沒有涉及到該.a,則不會在程式中連結該.a
           4、對於.so,無論是否涉及到符號查詢,均會進行載入
           5、so的載入和解除安裝會涉及到自身記憶體分配和釋放,而.a不會(.a相當於.o的集合,.o直接靜態編譯到應用程式,成為程式一部分)
           6、.a和.o有不同,.a是.o的集合,但是,.o必定會載入,.a不一定會載入(只加載符號表相關的.o)

這樣,對於有不同庫的同名全域性變數,只會產生一個符號,但是由於.so本身在解除安裝的時候會對全域性變數進行析構,同時如果多個共享庫,或者程式本身具有該全域性變數,這樣就會出現重複free的情況,導致double free錯誤。

解決方法:使用選項-fpie或-fPIE,此時生成的共享庫不會為靜態成員變數或全域性變數在GOT中建立對應的條目(通過objdump或readelf命令可以檢視),從而避免了由於靜態物件“在同一地址構造兩次,析構兩次”而對同一記憶體區域釋放兩次引起的程式 core dump(重複構造沒有問題,但是重複析構會導致double free,但是如果建構函式有記憶體分配動作,是否會導致記憶體洩露?)。

選項-fpie和-fPIE與-fpic及-fPIC的用法很相似,區別在於前者總是將生成的位置無關程式碼看作是屬於程式本身,並直接連結進該可執行程式,而非存入全域性偏移表GOT中;這樣,對於同名的靜態或全域性物件的訪問,其構造與析構操作將保持一一對應。

還有如果具有同名變數的庫一個是.so和一個是.a,將.so放到前面,也可以避免這個問題,根據上面的連結規則,當在.so中找到該符號時,那麼.a中的內容就不會被連結了。更詳細內容可以參考這兩篇文章:庫衝突 技巧:多共享動態庫中同名物件重複析構問題的解決方法

LD_DEBUG這個環境變數可以開啟動態庫的除錯功能,輸出很多資訊,對於開發除錯動態庫很有幫助。比如設定LD_DEBUG=files,就可以看到整個裝載過程。此外它還可以設定為:

l  bindings:顯示動態連結的符號繫結過程

l  libs:顯示共享庫的查詢過程

l  versions:顯示符號的版本依賴關係

l  reloc:顯示重定位過程

l  symbols:顯示符號表查詢過程

l  statistics:顯示動態連結過程中的各種統計資訊

l  all:顯示以上所有資訊

l  help:顯示幫助資訊

file程式是用來判斷檔案型別的,比如可以用命令file xxx.tar.tar看一下檔案型別,然後用tar加適當的引數解壓。在這裡主要是可以通過file來檢視某檔案是否連結了動態庫。

$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386,
version 1 (SYSV), dynamically linked (uses shared
libs), not stripped

最後一個not stripped表示該檔案含符號表(可以利用命令strip去除符號表)

用於列出目標檔案的符號。可以通過-C選項,來顯示符號的可讀形式,即未被mangle的形式。-D可以用來顯示動態符號。其中U型別的符號,代表該目標檔案中未定義的那些symbol,而這些symbol通常都是定義在其他檔案中,T表示該symbol在此檔案中有定義。所以,nm最常用的地方在於,檢視這個檔案中是否包含某函式的定義,包含哪些未定義符號,因此在產生連結問題時,通常需要對U和T型別格外關注

c++filt可以將mangle的符號進行還原。

ldd可以用來檢視.so所依賴的共享庫檔案列表及未找到的.so,ldd –r還會報告缺少的目標物件和函式。

ldd實際上是個指令碼,能夠顯示可執行模組的dependency,其原理是通過設定一系列的環境變數,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。當LD_TRACE_LOADED_OBJECTS環境變數不為空時,任何可執行程式在執行時,它都會只顯示模組的dependency,而程式並不真正執行。

ldd顯示可執行模組的dependency的工作原理,其實質是通過ld-linux.so(elf動態庫的裝載器)來實現的。我們知道,ld-linux.so模組會先於executable模組程式工作,並獲得控制權,因此當上述的那些環境變數被設定時,ld-linux.so選擇了顯示可執行模組的dependency。

它是專門針對 ELF 檔案格式的解析器,但是它並不提供反彙編功能,可以通過objdump進行反彙編。readelf可以用來檢視頭資訊,符號資訊,動態重定位資訊等elf內部各個部分。

ldconfig是一個動態連結庫管理命令。為了讓動態連結庫為系統所共享,還需執行動態連結庫的管理命令--ldconfigldconfig  命令的用途,主要是在預設搜尋目錄(/lib和/usr/lib)以及動態庫配置檔案/etc/ld.so.conf內所列的目錄下,搜尋出可共享的動態連結庫(格式如前介紹,lib*.so*),進而創建出動態裝入程式(ld.so)所需的連線和快取檔案.快取檔案預設為/etc/ld.so.cache,此檔案儲存已排好序的動態連結庫名字列表。

可以用來清理共享庫和可執行檔案中的符號資訊和除錯資訊。也可以通過ld –s或ld –S,二者區別是:-S消除除錯符號資訊,-s消除所有符號資訊。也可以通過gcc引數”-Wl,-s”或”-Wl,-S”來向ld傳遞這兩個引數。

無論是編譯,連結還是載入,本質上都需要去尋找某些符號的定義。當找不到對應的符號時通常都會產生某些錯誤,類似於上面提到的那些。

實際上基本上都需要確定以下問題。需要哪些符號的定義?去哪裡尋找這些符號的定義?找到的符號定義是否滿足要求?

具體來說,比如動態庫載入時,需要確定需要載入哪些.so?去哪找到這些.so?這些.so中是否包含了所有的未定義符號?載入哪些.so,是由可執行檔案及各個.so的.dynamic段中的DT_NEEDED決定的。去哪尋找這些.so,則是由各種路徑確定的。如果出現.so檔案找不到或符號未定義或版本不正確之類的動態庫相關問題時,通常的分析步驟如下:

l  檢視-l引數,確定可執行程式已經連結了所需的庫檔案

l  使用find,在當前系統中查詢,看是否能夠找到該庫檔案

l  確定系統當前的庫搜尋路徑

l  如果可以找到該庫檔案,則需要確定它所在目錄是否已經在庫搜尋路徑中,如果不在則將它加入合適路徑

l  如果它剛好也在庫搜尋路徑中,需要確定路徑中是否還有同名的庫檔案

l  如果它是唯一的存在於庫搜尋路徑中的庫檔案,則詳細分析該檔案:

?  使用ldd檢視該.so的依賴性

?  使用nm檢視其符號表,找到未定義符號,nm –a *.so|grep U

l  如果還找不到問題所在,設定LD_DEBUG環境變數,進行除錯

此外,如果是詭異的編譯問題,還可以利用gcc的-E,-S,-c和 –O分階段進行,檢視每個階段的輸出是否正常。同時為每個命令加上-v,檢視實際執行的命令,同時還可以看到標頭檔案及庫檔案的搜尋路徑。比如可能因為巨集替換,導致某些使用者定義的變數被其他的一些巨集定義替換,或者因為標頭檔案搜尋路徑順序,導致找到了一個錯誤的標頭檔案。

最近碰到的一個問題是:呼叫dlopen時產生undefined symbol,採用了RTLD_NOW方式。對於undefined symbol:

類似上面的步驟,使用ldd檢視它現在依賴的.so,使用nm找到那個未定義的symbol,如果是c++程式,使用c++filt將它轉換為可讀形式,確認該symbol定義所在的庫,然後判斷該庫是否已經在載入列表中,或者庫的版本是否正確。

最終確定原因是:我們的可執行程式,本身會呼叫dlopen去在執行時載入某些.so,但是這些.so檔案在生成時的-l引數,沒有加入那些該.so所依賴的其他.so。這樣該.so檔案的DT_NEEDED列表就是不完整的,這樣載入器就不會去載入它們,這樣某些符號就找不到它們的定義。

解決方案有兩個:一個是保持現有.so不變,我們在編譯可執行檔案的-l引數中加入這些必需的庫的。另一個是修改現有.so,在-l中加入它所依賴的那些庫。第一個方案,只需要修改可執行檔案即可,但是這種方式不是一種徹底的解決方案,因為比如該.so檔案被其他程式使用時,仍然會產生這種問題,因此根本的解決方案是,.so檔案本身在編譯時就要在-l引數中寫入它所依賴的那些庫。

//test.h

void test();

//test.c

#include <iostream>

using namespace std;

void test()

{

   cout << “test” << endl;

}

//main.c

#include “test.h”

int main()

{

test();

return 0;

}

gcc –v -c test.c; ar r libtest.a test.o

[[email protected]]$gcc -v -c test.cpp

Using built-in specs.

Target: x86_64-redhat-linux

Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux

Thread model: posix

gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

 /usr/libexec/gcc/x86_64-redhat-linux/4.1.2/cc1plus -quiet -v -D_GNU_SOURCE test.cpp -quiet -dumpbase test.cpp -mtune=generic -auxbase test -version -o /tmp/ccdMLquk.s

ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../x86_64-redhat-linux/include"

#include "..." search starts here:

#include <...> search starts here:

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2/x86_64-redhat-linux

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2/backward

 /usr/local/include

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/include

 /usr/include

End of search list.

GNU C++ version 4.1.2 20080704 (Red Hat 4.1.2-46) (x86_64-redhat-linux)

        compiled by GNU C version 4.1.2 20080704 (Red Hat 4.1.2-46).

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

Compiler executable checksum: 927721cb17bef594f560fa66ec50ff62

 as -V -Qy -o test.o /tmp/ccdMLquk.s

GNU assembler version 2.17.50.0.6-12.el5 (x86_64-redhat-linux) using BFD version 2.17.50.0.6-12.el5 20061020

可以看到加上-v之後,就能看到各個步驟的具體命令,還能看到標頭檔案搜尋路徑。

gcc  -shared test.cpp -o libtest.so –fPIC -v

[[email protected]]$gcc  -shared test.cpp -o libtest.so -fPIC -v

Using built-in specs.

Target: x86_64-redhat-linux

Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux

Thread model: posix

gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

 /usr/libexec/gcc/x86_64-redhat-linux/4.1.2/cc1plus -quiet -v -D_GNU_SOURCE test.cpp -quiet -dumpbase test.cpp -mtune=generic -auxbase test -version -fPIC -o /tmp/ccv7EbFP.s

ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../x86_64-redhat-linux/include"

#include "..." search starts here:

#include <...> search starts here:

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2/x86_64-redhat-linux

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2/backward

 /usr/local/include

 /usr/lib/gcc/x86_64-redhat-linux/4.1.2/include

 /usr/include

End of search list.

GNU C++ version 4.1.2 20080704 (Red Hat 4.1.2-46) (x86_64-redhat-linux)

        compiled by GNU C version 4.1.2 20080704 (Red Hat 4.1.2-46).

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072

Compiler executable checksum: 927721cb17bef594f560fa66ec50ff62

 as -V -Qy -o /tmp/ccmzTeZF.o /tmp/ccv7EbFP.s

GNU assembler version 2.17.50.0.6-12.el5 (x86_64-redhat-linux) using BFD version 2.17.50.0.6-12.el5 20061020

 /usr/libexec/gcc/x86_64-redhat-linux/4.1.2/collect2 --eh-frame-hdr -m elf_x86_64 --hash-style=gnu -shared -o libtest.so /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crti.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtbeginS.o -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2 -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2 -L/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 /tmp/ccmzTeZF.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtendS.o /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crtn.o

LIBRARY_PATH=.;export LIBRARY_PATH

g++ main.cpp -ltest

gcc標頭檔案搜尋路徑

-iquote用於搜尋"#include "file""形式的標頭檔案

-I

C_PATH:類似於-I但優先順序在-I之後,可以用於任何型別語言的預處理(比如c,c++)

-isystem

C_INCLUDE_PATH:c語言的,類似於-isystem,但優先順序在-isystem之後

CPLUS_INCLUDE_PATH:c++的

OBJC_INCLUDE_PATH:Objective-C的

gcc庫檔案搜尋路徑

-L

LIBRARY_PATH:可以用來指定庫檔案搜尋路徑,但是優先順序在-L之後

LD_LIBRARY_PATH:對於gcc編譯不起作用,只與載入有關

測試-static,如果無.a,有.so是否可以?答案是必須是.a的庫,否則不行

測試-Bdynamic,如果無.so,有.a是否可以?答案是可以

測試-rpath:g++ main.cpp -ltest -Wl,-rpath=. ,-rpath只對載入起作用,對連結無作用,通過它可以把執行時需要的動態庫絕對路徑寫在可執行檔案裡

測試LD_PRELOAD:在載入階段器作用,無論可執行檔案鏈不連結,載入器都會載入它

測試LD_LIBRARY_PATH:在載入階段起作用

測試ldconfig:可以直接對當前路徑應用ldconfig,這樣也可以將其加入

測試/etc/ld.so.conf:直接只將路徑新增到該檔案,不起作用,必須執行ldconfig

測試/etc/ld.so.cache:載入時會直接從該處查詢

程式設計師的自我修養—連結、裝載與庫


7.   附錄

相關推薦

linux 編譯,連結載入

1.   序 最近在折騰各種.so,碰到了一些問題,一開始對於很多錯誤也沒有頭緒,茫然不知所措。索性化了一天多時間將<<程式設計師的自我修養—連結、裝載與庫>>中部分內容略讀了一遍,主要是關於編譯,連結和載入這塊的。於是順便做個筆記,方便以後回顧。基本上知道了這些,對於編譯,連結

Linux連結連結小結

每個linux儲存裝置的分割槽被格式化成檔案系統後, 一般生成兩個部分: 第一部分是inode;inode是用來儲存資料屬性資訊的; 第二部分是block;block是用來儲存實際資料的 例如照片 視訊; inode具有指向檔案實體的功能,但是inode唯獨不包含檔名 檔名儲存在它上一級目錄的block中。

LInux連結連結的建立、刪除

軟連結:     1.以路徑的形式存在     2.軟連結可以跨檔案系統,硬連結不可以     3.軟連結可以對一個不存在的檔名進行連結     4.軟連結可以對

Linux連結連結簡單指引

在Linux中的連結方式有兩種,軟連結和硬連結。 軟連結的方式 link -s /home/hadoop/aaa.txt /home/aaa.txt 這種連結方式,相當於建立一個快捷方式,我可以使

linux連結連結

                簡而言之:軟連結相當於windows中的快捷方式硬連結相當於一個災備系統,資料存放在兩處,與複製不同的是兩處之間存在同步機制,一處資料的改變會實時同步到另一處,另外一處資料如果被刪除了,不會影響到另一處的資料.下面是詳細的介紹: 存在兩種不同型別的連結,軟連結和硬連結。修改其中

linux連結連結的區別

1.原理上: 硬連結(hard link):A是B的硬連結(A和B都是檔名),則A的目錄項中的inode節點號與B的目錄項中的inode節點號相同,即一個inode節點對應兩個不同的檔名,兩個檔名指向同一個檔案,A和B對檔案系統來說是完全平等的。如果刪除了其中一個,對另外一個沒有影響。每增加一個

Linux編譯連結之庫

簡要記錄linux下編譯靜態庫和動態庫的方法, 1.靜態庫(*.a) 編譯:cc -Wall -c ctest1.c ctest2.c 建立靜態庫:ar -cvq libctest.a ctest.o ctest2.o 顯示靜態庫中的檔案列表(建立符號表):ar -t li

ARM Linux編譯連結過程分析

cmd_vmlinux := arm-iwmmxt-linux-gnueabi-ld -EL-p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_ta

深入編譯連結執行

我們先看下面這份程式碼: #include<stdio.h> int gdata1=10; int gdata2=0; int gdata3; static int gdata4=11;

linux編譯x86arm平臺的x264

摘抄網址:http://blog.csdn.net/baliguan163/article/details/11773363 參考網址:http://blog.csdn.net/evsqiezi/article/details/8467822 參考網址:http://bl

Linux下編寫載入 .ko 檔案(驅動模組檔案)

一、.ko 檔案介紹 .ko檔案是kernel object檔案(核心模組),該檔案的意義就是把核心的一些功能移動到核心外邊, 需要的時候插入核心,不需要時解除安裝。   二、優點 (1)這樣可以縮小核心體積; (2)使用方便。 三、.ko檔案一般的用處

linux 編譯連結libcurl異常處理

今天使用libcurl在程式連結的時候的時候提示了一下的錯誤: strerror.c:(.text+0x3f3): undefined reference to `idna_strerror' ./Libcurl/libcurl.a(libcurl_la-timeval.o

linux連結連結以及inode

1、inode a、inode是index node,中文為索引節點。 b、inode包含以下內容: * 檔案的位元組數   * 檔案擁有者的User ID   * 檔案的Group ID   * 檔案的讀、寫、執行許可權   * 檔案的時間戳,共有三個:ctim

Linux連結軟連線的理解

    為了解決資訊能獨立於程序之外長期被儲存引入了檔案,檔案能同時被多個程序使用。對於所有類Unix系統,除了程序以外全都為檔案,而Linux在此基礎上引進了目錄的概念即資料夾,這就使得Linux的檔案可以被分類管理,同時是的Linux的檔案系統形成一個層級結構的目錄樹。 

:程式是怎樣被連結載入的?

個真實的例子 我們通過一個簡小的連結例項來結束對連結過程的介紹。圖 3 所示為一對 C 語言原始碼 檔案,m.c 中的主程式呼叫了一個名為 a 的例程,而呼叫了庫例程 strlen 和 write 的 a 例程 bbs.theithome.com 在 a.c 中。 ----

程式時如何被連結載入的???

執行 PE 可執行檔案 啟動一個 PE 可執行程式的過程是相對簡單的。  讀入檔案的第一頁,其中有 DOS 頭部,PE 頭部和區段頭部等。  確定地址空間的目標區域是否有效,如果不可用則另分配一塊區域。  根據各區段頭部的資訊,將檔案中的所有區段對映到地址空間的適當位

Linux連結符號連結的區別

什麼是目錄 Linux檔案系統是樹狀結構的。根目錄下存在一系列子目錄。目錄裡邊有檔案或者子目錄。 但問題在於: 1. 目錄是什麼? 2. 檔案又是什麼? 檔案是:資料+屬性(比如名字、建立時間、所有者之類) 目錄是:一個列表,列表中的每一項是:in

Linux連結連結

一、硬連結         硬連結有稱實際連結,是指通過索引節點來進行連結,在Linux中,所有節點都會有一個編號,稱為inode,多個檔名指向同一個索引節點是被允許的,這種連結被稱為硬連結。硬連結的作用是允許一個檔案擁有多個有效的檔名。這樣使用者就可以簡歷硬連結指向同一個

flask學習筆記(七):URL連結載入靜態檔案

靜態檔案的載入,需要先新建資料夾static,在資料夾下再新建css、js、images資料夾,在這些資料夾中存放css、js、images。 同時也要用到url_for模組。 注意css、js、images使用的標籤不同 <!DOCTYPE html>

Linux連結連結簡介

本文主要介紹了Linux系統中的連結檔案。   檔案系統   在Linux系統中,將檔案分為兩個部分: