linux 動態庫剖析
原作者:M. Jones
連結:https://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/
庫用於將相似函式打包在一個單元中。然後這些單元就可為其他開發人員所共享,並因此有了模組化程式設計這種說法 — 即,從模組中構建程式。Linux 支援兩種型別的庫,每一種庫都有各自的優缺點。靜態庫包含在編譯時靜態繫結到一個程式的函式。動態庫則不同,它是在載入應用程式時被載入的,而且它與應用程式是在執行時繫結的。圖 1 展示了 Linux 中的庫的層次結構。
圖 1. Linux 中的庫層次結構
使用共享庫的方法有兩種:您既可以在執行時動態連結庫,也可以動態載入庫並在程式控制之下使用它們。本文對這兩種方法都做了探討。
靜態庫較適宜於較小的應用程式,因為它們只需要最小限度的函式。而對於需要多個庫的應用程式來說,則適合使用共享庫,因為它們可以減少應用程式對記憶體(包括執行時中的磁碟佔用和記憶體佔用)的佔用。這是因為多個應用程式可以同時使用一個共享庫;因此,每次只需要在記憶體上覆制一個庫。要是靜態庫的話,每一個執行的程式都要有一份庫的副本。
GNU/Linux 提供兩種處理共享庫的方法(每種方法都源於 Sun Solaris)。您可以動態地將程式和共享庫連結並讓 Linux 在執行時載入庫(如果它已經在記憶體中了,則無需再載入)。另外一種方法是使用一個稱為動態載入的過程,這樣程式可以有選擇地呼叫庫中的函式。使用動態載入過程,程式可以先載入一個特定的庫(已載入則不必),然後呼叫該庫中的某一特定函式(圖 2 展示了這兩種方法)。這是構建支援外掛的應用程式的一個普遍的方法。我稍候將在本文探討並示範該應用程式程式設計介面(API)。
圖 2. 靜態連結與動態連結
用 Linux 進行動態連結
現在,讓我們深入探討一下使用 Linux 中的動態連結的共享庫的過程。當用戶啟動一個應用程式時,它們正在呼叫一個可執行和連結格式(Executable and Linking Format,ELF)映像。核心首先將 ELF 映像載入到使用者空間虛擬記憶體中。然後核心會注意到一個稱為 .interp
的 ELF 部分,它指明瞭將要被使用的動態連結器(/lib/ld-linux.so),如清單 1 所示。這與 UNIX® 中的指令碼檔案的直譯器定義(#!/bin/sh)很相似:只是用在了不同的上下文中。
清單 1. 使用 readelf 來顯示程式標題
1234567891011121314151617181920 | [email protected]:~/dl$ readelf -l dl Elf file type is EXEC (Executable file) Entry point 0x8048618 There are 7 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4 INTERP 0x000114 0x08048114 0x08048114 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x00958 0x00958 R E 0x1000 LOAD 0x000958 0x08049958 0x08049958 0x00120 0x00128 RW 0x1000 DYNAMIC 0x00096c 0x0804996c 0x0804996c 0x000d0 0x000d0 RW 0x4 NOTE 0x000128 0x08048128 0x08048128 0x00020 0x00020 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4 ... [email protected]:~dl$ |
注意,ld-linux.so 本身就是一個 ELF 共享庫,但它是靜態編譯的並且不具備共享庫依賴項。當需要動態連結時,核心會引導動態連結(ELF 直譯器),該連結首先會初始化自身,然後載入指定的共享物件(已載入則不必)。接著它會執行必要的再定位,包括目標共享物件所使用的共享物件。LD_LIBRARY_PATH
環境變數定義查詢可用共享物件的位置。定義完成後,控制權會被傳回到初始程式以開始執行。
再定位是通過一個稱為 Global Offset Table(GOT)和 Procedure Linkage Table(PLT)的間接機制來處理的。這些表格提供了 ld-linux.so 在再定位過程中載入的外部函式和資料的地址。這意味著無需改動需要間接機制(即,使用這些表格)的程式碼:只需要調整這些表格。一旦進行載入,或者只要需要給定的函式,就可以發生再定位(稍候在 用 Linux 進行動態載入 小節中會看到更多的差別)。
再定位完成後,動態連結器就會允許任何載入的共享程式來執行可選的初始化程式碼。該函式允許庫來初始化內部資料並備之待用。這個程式碼是在上述 ELF 映像的 .init
部分中定義的。在解除安裝庫時,它還可以呼叫一個終止函式(定義為映像的 .fini
部分)。當初始化函式被呼叫時,動態連結器會把控制權轉讓給載入的原始映像。
用 Linux 進行動態載入
Linux 並不會自動為給定程式載入和連結庫,而是與應用程式本身共享該控制權。這個過程就稱為動態載入。使用動態載入,應用程式能夠先指定要載入的庫,然後將該庫作為一個可執行檔案來使用(即呼叫其中的函式)。但是正如您在前面所瞭解到的,用於動態載入的共享庫與標準共享庫(ELF 共享物件)無異。事實上,ld-linux
動態連結器作為 ELF 載入器和直譯器,仍然會參與到這個過程中。
動態載入(Dynamic Loading,DL)API 就是為了動態載入而存在的,它允許共享庫對使用者空間程式可用。儘管非常小,但是這個 API 提供了所有需要的東西,而且很多困難的工作是在後臺完成的。表 1 展示了這個完整的 API。
表 1. Dl API
函式 | 描述 |
---|---|
dlopen | 使物件檔案可被程式訪問 |
dlsym | 獲取執行了 dlopen 函式的物件檔案中的符號的地址 |
dlerror | 返回上一次出現錯誤的字串錯誤 |
dlclose | 關閉目標檔案 |
該過程首先是呼叫 dlopen
,提供要訪問的檔案物件和模式。呼叫 dlopen
的結果是稍候要使用的物件的控制代碼。mode
引數通知動態連結器何時執行再定位。有兩個可能的值。第一個是 RTLD_NOW
,它表明動態連結器將會在呼叫 dlopen
時完成所有必要的再定位。第二個可選的模式是 RTLD_LAZY
,它只在需要時執行再定位。這是通過在內部使用動態連結器重定向所有尚未再定位的請求來完成的。這樣,動態連結器就能夠在請求時知曉何時發生了新的引用,而且再定位可以正常進行。後面的呼叫無需重複再定位過程。
還可以選擇另外兩種模式,它們可以按位 OR
到 mode
引數中。RTLD_LOCAL
表明其他任何物件都無法使載入的共享物件的符號用於再定位過程。如果這正是您想要的的話(例如,為了讓共享的物件能夠呼叫原始程序映像中的符號),那就使用 RTLD_GLOBAL
吧。
dlopen
函式還會自動解析共享庫中的依賴項。這樣,如果您打開了一個依賴於其他共享庫的物件,它就會自動載入它們。函式返回一個控制代碼,該控制代碼用於後續的 API 呼叫。dlopen
的原型為:
123 | #include < dlfcn.h > void *dlopen( const char *file, int mode ); |
有了 ELF 物件的控制代碼,就可以通過呼叫 dlsym
來識別這個物件內的符號的地址了。該函式採用一個符號名稱,如物件內的一個函式的名稱。返回值為物件符號的解析地址:
1 | void *dlsym( void *restrict handle, const char *restrict name ); |
如果呼叫該 API 時發生了錯誤,可以使用 dlerror
函式返回一個表示此錯誤的人類可讀的字串。該函式沒有引數,它會在發生前面的錯誤時返回一個字串,在沒有錯誤發生時返回 NULL:
1 | char *dlerror(); |
最後,如果無需再呼叫共享物件的話,應用程式可以呼叫 dlclose
來通知作業系統不再需要控制代碼和物件引用了。它完全是按引用來計數的,所以同一個共享物件的多個使用者相互間不會發生衝突(只要還有一個使用者在使用它,它就會待在記憶體中)。任何通過已關閉的物件的 dlsym
解析的符號都將不再可用。
1 | char *dlclose( void *handle ); |
動態載入示例
瞭解了 API 之後,下面讓我們來看一看 DL API 的例子。在這個應用程式中,您主要實現了一個 shell,它允許操作員來指定庫、函式和引數。換句話說,也就是使用者能夠指定一個庫並呼叫該庫(先前未連結於該應用程式的)內的任意一個函式。首先使用 DL API 來解析該庫中的函式,然後使用使用者定義的引數(用來發送結果)來呼叫它。清單 2 展示了完整的應用程式。
清單 2. 使用 DL API 的 Shell
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152相關推薦linux 動態庫剖析原作者:M. Jones連結:https://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/庫用於將相似函式打包在一個單元中。然後這些單元就可為其他開發人員所共享,並因此有了模組化程式設計這種說法 — 即,從模 Linux動態庫剖析M. Tim Jones, 顧問工程師, Emulex Corp. 2008 年 9 月 08 日 動態連結的共享庫是 GNU/Linux® 的一個重要方面。該種庫允許可執行檔案在執行時動態訪問外部函式,從而(通過在需要時才會引入函式的方式)減少它們對記憶體的總體佔用 Linux 動態庫剖析 and Linux動態連結庫程式設計入門下面通過一個簡單的例子開始介紹Linux標準物件。 我們的標準物件檔案含有一個函式,不需要宣告export匯出符號,只需要編譯器設定即可。如下: 設建立一個tools.h檔案以及tools.c檔案 /* ** tools.h */ #include "stdio.h linux動態庫的種種要點統一 pri ont http 指向 try 找到 track linux linux下使用動態庫,基本用起來還是非常easy。但假設我們的程序中大量使用動態庫來實現各種框架/插件,那麽就會遇到一些坑,掌握這些坑才有利於程序更穩健地執行。 本篇先談談動態庫符號 Linux動態庫多重依賴png windows window stdio.h not found 源文件 AR TE 再次 1、動態庫依賴關系 test->hello->world 2、源文件 //world.cpp #include <stdio.h> void wo .netcore在linux下使用P/invoke方式調用linux動態庫glob glibc glibcxx pointer ngs 得到 bst view ons .netcore下已經實現了通過p/invoke方式調用linux的動態鏈接庫(*.so)文件 1 [DllImport(@"libdl.so.2")] 2 Linux 動態庫和靜態庫Linux作業系統中,依據函式庫是否被編譯到程式內部,將其分為兩大類,靜態函式庫和動態函式庫。 Linux下的函式庫放在/lib或/usr/lib,標頭檔案放在/usr/include。 在既有靜態庫又有動態庫的情況下,預設使用動態庫,如果強制使用靜態庫則需要加-static選項支援。 Linux 動態庫載入動態庫執行時搜尋順序 1.LD_PRELOAD LD_PRELOAD是一個環境變數,用於動態庫載入,動態庫載入的優先順序最高; 2.-wl,-rpath 編譯目的碼時指定的動態庫搜尋路徑(指的是用-wl,-rpath),readelf -d 命令可以檢視編譯的目標檔案中rpat 【Linux動態庫目錄】增加linux動態庫目錄動態庫目錄 在Linux下面動態庫目錄載入入口在**/etc/ld.so.conf**檔案 預設該檔案只有一行“include /etc/ld.so.conf.d/*.conf”,其中ld.so.conf.d是預設動態庫的配置的存放目錄。 我們用 sudo vim /etc/ld.s Linux動態庫載入函式dlopen原始碼梳理(二)中大概梳理了整個流程,還有_dl_map_object_from_fd(),以及link_map結構沒有進行分析,在這裡對這兩部分進行分析 由於_dl_map_object_from_fd()比較長,整個函式的程式碼就放到最後作為附錄,前面部分來一點點進行梳理。 一、 一種攔截Linux動態庫API的方法及裝置描述 攔截Linux動態庫API的常規方法,是基於動態符號連結覆蓋技術實現的,基本步驟是 1. 重新命名要攔截的目標動態庫。 2. 建立新的同名動態庫,定義要攔截的同名API,在API內部呼叫原動態庫對應的API。這裡的同名是指與重新命名前動態庫前的名稱相同。 顯而易見 linux動態庫與靜態庫程式設計個人覺得在linux環境下,動態庫和靜態庫的程式設計更加容易. 首先要熟悉gcc的各個引數意義 -E 預編譯 -S 編譯 -c 彙編成二進位制程式碼,-C 生成可執行檔案 1,編寫static.c 2,將static.c編譯成二進位制程式碼:gcc -c static.c&nbs Linux動態庫(.so)符號表最近編譯libbinder.so發現system/lib/libbinder.so只有358K,但單獨編譯生成的obj/SHARED_LIBRARIES/libbinder_intermediates/LINKED/libbinder.so有5M多 Linux動態庫(一)之同名符號In C++, you can mark member functions and static member variables of a class with the visibility attribute. This is useful if you know a particular method 如何檢視linux動態庫中包含哪些函式1、方法1 nm *.so 2、方法2 readelf -a *.so PS:readelf Options are: -a --all Equivalent to: -h -l -S -s -r -d -V -A -I Linux動態庫生成以及呼叫Linux下動態庫檔案的檔名形如 libxxx.so,其中so是 Shared Object 的縮寫,即可以共享的目標檔案。 在連結動態庫生成可執行檔案時,並不會把動態庫的程式碼複製到執行檔案中,而是在執行檔案中記錄對動態庫的引用。 程式執行時,再去載入動態庫檔案。如果動態庫已經載入,則不必重複 Linux動態庫的 靜態載入 和 動態載入 的使用動態庫的靜態載入 math.c #include<stdio.h> int add(int a,int b) { return a+b; } show.c #include<stdio. Linux動態庫.a與動態庫.so的生成與區別、以及.so庫檔案的封裝與使用一、前言 如果有公司需要使用你們產品的一部分功能(通過程式碼呼叫這些功能),如果不想提供原始碼,那麼就可以通過封裝成庫檔案的形式提供給對方使用。本文主要介紹了生成動態庫與靜態庫檔案的過程、以及封裝和使用庫檔案的方法。 二、靜態庫.a與動態庫.so的生成與 linux動態庫so呼叫外部so,執行時出現undefined symbol1、首先排查,C++呼叫了c的庫?是不是需要加上extern "c",尤其是類的動態庫,需要用到工廠模式,create一個物件出來,該工廠函式需要extern "c"宣告。 extern "C" CDbBase* create(); extern "C" void dest 在Linux動態庫Project中連結靜態庫時,遇到error adding symbols: Bad value的問題現象:在生成某個動態庫比如SDS_Utility.so的時候,需要靜態連結某些庫,如libboost_log.a, 報上述錯誤。 原因:靜態庫想連結進動態庫,必須滿足一定的條件,靜態庫需要加-fPIC選項編譯,編譯成Position Independent C |