so檔案動態替換方法及Linux動態庫的用法

2021-10-23 01:49:15 字數 4499 閱讀 1232

在替換so檔案時,如果在不停程式的情況下,直接用 cp new.so old.so 的方式替換程式使用的動態庫檔案會導致正在執行中的程式崩潰。解決的辦法是採用「rm+cp」 或「mv+cp」 來替代直接「cp」 的操作方法。linux系統的動態庫有兩種使用方法:執行時動態鏈結庫,動態載入庫並在程式控制之下使用。

1、為什麼在不停程式的情況下,直接用 cp 命令替換程式使用的 so 檔案,會使程式崩潰?

很多同學在工作中遇到過這樣乙個問題,在替換 so 檔案時,如果在不停程式的情況下,直接用cp new.so old.so的方式替換程式使用的動態庫檔案會導致正在執行中的程式崩潰,退出。這與 cp 命令的實現有關,cp 並不改變目標檔案的 inode,cp 的目標檔案會繼承被覆蓋檔案的屬性而非原始檔。實際上它是這樣實現的:

strace cp libnew.so libold.so 2>&1 |grep open.*lib.*.so

open("libnew.so", o_rdonly|o_largefile) = 3

open("libold.so", o_wronly|o_trunc|o_largefile) = 4

在 cp 使用「o_wronly|o_trunc」 開啟目標檔案時,原 so 檔案的映象被意外的破壞了。這樣動態鏈結器 ld.so 不能訪問到 so 檔案中的函式入口。從而導致 segmentation fault,程式崩潰。ld.so 載入 so 檔案及「再定位」的機制比較複雜,詳情可參見參考文獻2。

2、怎樣在不停止程式的情況下替換so檔案,並且保證程式不會崩潰?

答案是採用「rm+cp」 或「mv+cp」 來替代直接「cp」 的操作方法。

在用新的so檔案 libnew.so 替換舊的so檔案 libold.so 時,如果採用如下方法:

rm libold.so

cp libnew.so libold.so

採用這種方法,目標檔案 libold.so 的 inode 其實已經改變了,原來的 libold.so 檔案雖然不能用 」ls」檢視到,但其 inode 並沒有被真正刪除,直到核心釋放對它的引用。同理,mv只是改變了檔名,其 inode 不變,新檔案使用了新的 inode。這樣動態鏈結器 ld.so 仍然使用原來檔案的 inode 訪問舊的 so 檔案。因而程式依然能正常執行。

到這裡,我們回想在上線操作中在替換可執行程式時,為什麼直接使用「cp new old」這樣的命令時,系統會禁止這樣的操作,並且給出這樣的提示「cp: cannot create regular file `old': text file busy」。這時,我們採用的辦法仍然是用「rm+cp」或者「mv+cp」來替代直接「cp」,這跟以上提到的so檔案的替換有同樣的道理。

但是,為什麼系統會阻止 cp 覆蓋可執行程式,而不阻止覆蓋 so 檔案呢?這是因為 linux 有個 demand paging 機制,所謂「demand paging」,簡單的說,就是系統為了節約物理記憶體開銷,並不會程式執行時就將所有頁(page)都載入到記憶體中,而只有在系統有訪問需求時才將其載入。

「demand paging」要求正在執行中的程式映象(注意,並非檔案本身)不被意外修改,因此核心在啟動程式後會鎖定這個程式映象的 inode。對於 so 檔案,它是靠 ld.so 載入的,而ld.so畢竟也是使用者態程式,沒有權利去鎖定inode,也不應與核心的檔案系統底層實現耦合。

3、linux動態庫的使用方法 linux 支援兩種型別的庫靜態庫和動態庫。 靜態庫包含在編譯時靜態繫結到乙個程式的函式。我們這裡關心的是動態庫。動態庫是在載入應用程式時被載入的,而且它與應用程式是在執行時繫結的。

程式使用動態庫的方法有兩種:

(1)、在執行時動態鏈結庫

這種方法是動態的將程式和共享庫鏈結並讓 linux 在執行時載入庫。

例:fc@fengchun~/project/so$ cat foo.c

#include

void foo()

printf("infoo\n");

cat test1.c

#include

int main(int argc, char * ar**)

loop:

foo();

sleep(1);

goto loop;

return 0;

首先將檔案 foo.c 編譯成動態庫 libtest.so:

gcc foo.c -fpic -shared -o libtest.so

然後將檔案 test1.c 編譯可執行程式:

gcc test1.c -l. -ltest -o test1

編譯引數 「-l.」 指定編譯器在當前目錄(.)查詢動態庫檔案,編譯引數 「-ltest」指定編譯器連線庫檔案 libtest.so。

執行可執行程式 test1

./tsest1

程式將在螢幕上每間隔1秒列印1次「infoo」,此時可以償試執行一下用直接cp覆蓋的方法替換libtest.so檔案。

cp libtest.so libtest1.so #即使用完全相同的so檔案覆蓋

cp libtest1.so libtest.so

可以看到程式立即崩潰退出。

fc@fengchun~/project/so$ ./test1

infoo

infoo

segmentation fault

(2)、在執行時動態載入庫並在程式控制之下使用它們。

通過這種方法使用動態庫,對於庫檔案本身的編寫和編譯與方法1是相同的。但在可執行程式中需要使用乙個稱為動態載入的過程,這樣程式可以有選擇地呼叫庫中的函式。動態載入(dynamic loading,dl)api 就是為了動態載入而存在的,它允許共享庫對使用者空間程式可用。儘管非常小,但是這個 api 提供了所有需要的東西,而且很多困難的工作是在後台完成的。

動態載入api主要包括以下函式:

#include

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

char *dlerror(void);

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

int dlclose(void *handle);

以下是乙個使用動態載入api的例子:

fc@fengchun~/project/so$ cat foo.c

#include

void foo()

printf("infoo\n");

fc@fengchun~/project/so$ cat test2.c

#include

#include

#include

int main( )

void *dl_handle;

float (*func)();

char *error;

/* open the shared object */

dl_handle = dlopen( "./libtest.so", rtld_lazy );

if (dl_handle) {

printf( "!!! %s\n", dlerror() );

return;

while (1) {

/* resolve the symbol (method) from the object */

func = dlsym( dl_handle, "foo" );

error = dlerror();

if (error = null) {

printf( "!!! %s\n", error );

return;

/* call the resolved method and print the result */

(*func)();

sleep(1);

/* close the object */

dlclose( dl_handle );

return;

首先將檔案 foo.c 編譯成動態庫 libtest.so:

gcc foo.c -fpic -shared -o libtest.so

然後將檔案 test2.c 編譯可執行程式:

gcc -rdynamic -o test2 test2.c -ldl

編譯引數「-rdynamic」 用來通知鏈結器將所有符號新增到動態符號表中(目的是能夠通過使用 dlopen 來實現向後跟蹤),編譯引數 「-ldl」指定編譯器連線庫 libdl

執行可執行程式 test2:

./tsest2

程式將在螢幕上每間隔1秒列印1次「infoo」,此時可以償試執行一下用直接cp覆蓋的方法替換libtest.so檔案。

cp libtest.so libtest1.so #即使用完全相同的so檔案覆蓋

cp libtest1.so libtest.so

可以看到程式立即崩潰退出。

fc@fengchun~/project/so$ ./test2

infoo

infoo

segmentation fault

動態so庫修改方法

對於linux和android開發者,有時可能需要檢視或者修改.so檔案,下面來講述如何檢視或者修改so檔案。ida pro 010 editor 注意安裝完ida後通常會生成兩個快捷圖示32位和64位,這個是對應於so的,如果so是32位的,則開啟32位的32位的快捷圖示 開啟之後,載入so,通常...

IDA動態除錯so檔案

1 將ida安裝目錄的 dbgsrc android server或者 android server nonpie複製到虛擬機器的data資料夾 2 使用ida開啟so檔案 4 配置虛擬機器端的監聽 5 使用adb forward命令進行埠的 將裝置被除錯端的埠 到遠端除錯端中 6 設定ida的de...

C linux動態庫so匯出及使用

存在不相容的函式 控制linux動態庫的匯出函式 使用linux動態庫 靜態載入 其他問題 第一次嘗試匯出linux動態庫,包裝log4cpp,遇到的問題做個記錄。在官網上下下來包過後,官網的安裝說明不全 stdcall cdecl if defined msc ver defined win32 ...