標籤:
轉自:http://mypyg.iteye.com/blog/845915
個人創作,歡迎指錯。
牽扯到ELF格式,gcc編譯選項待補,簡單實用的說明一下,對Linux下的so檔案有個實際性的認識。
1.so檔案是什嗎?
2.怎麼產生以及使用一個so動態庫檔案?
3.地址空間,以及安全執行緒.
4.庫的初始化,解析:
5.使用我們自己庫裡的函數替換系統函數:
//-------------------------------------------------------------------------------
1.so檔案是什嗎?
也是ELF格式檔案,共用庫(動態庫),類似於DLL。節約資源,加快速度,代碼升級簡化。
知道這麼多就夠了,實用主義。等有了印象再研究原理。
2.怎麼產生以及使用一個so動態庫檔案?
先寫一個C檔案:s.c
C代碼
- #include <stdio.h>
- int count;
- void out_msg(const char *m)
- {//2秒鐘輸出1次資訊,並計數
- for(;;) {printf("%s %d\n", m, ++count); sleep(2);}
- }
編譯:得到輸出檔案libs.o
gcc -fPIC -g -c s.c -o libs.o
連結:得到輸出檔案libs.so
gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc
一個標頭檔:s.h
C代碼
- #ifndef _MY_SO_HEADER_
- #define _MY_SO_HEADER_
- void out_msg(const char *m);
- #endif
再來一個C檔案來引用這個庫中的函數:ts.c
C代碼
- #include <stdio.h>
- #include "s.h"
- int main(int argc, char** argv)
- {
- printf("TS Main\n");
- out_msg("TS ");
- sleep(5); //這句話可以注釋掉,在第4節的時候開啟就可以。
- printf("TS Quit\n");
- }
編譯連結這個檔案:得到輸出檔案ts
gcc -g ts.c -o ts -L. -ls
執行./ts,嗯:成功了。。。還差點
得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory
系統不能找到我們自己定義的libs.so,那麼告訴他,修改變數LD_LIBRARY_PATH,為了方便,寫個指令碼:e(檔案名稱就叫e,懶得弄長了)
#!/bin/sh
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
執行:./e &
螢幕上就開始不停有資訊輸出了,當然TS Quit你是看不到的,前面是個死迴圈,後面會用到這句
3.地址空間,以及安全執行緒:
如果這樣:
./e &開始執行後,稍微等待一下然後再 ./e&,
這個時候螢幕資訊會怎麼樣呢?全域變數count會怎麼變化?
會是兩個進程交叉輸出資訊,並且各自的count互不干擾,雖然他們引用了同一個so檔案。
也就是說只有代碼是否安全執行緒一說,沒有代碼是否是進程安全這一說法。
4.庫的初始化,解析:
windows下的動態庫載入,卸載都會有初始化函數以及卸載函數來完成庫的初始化以及資源回收,linux當然也可以實現。
ELF檔案本身執行時就會執行一個_init()函數以及_fini()函數來完成這個,我們只要把自己的函數能讓系統在這個時候執行
就可以了。
修改我們前面的s.c檔案:
C代碼
- #include <stdio.h>
- void my_init(void) __attribute__((constructor)); //告訴gcc把這個函數扔到init section
- void my_fini(void) __attribute__((destructor)); //告訴gcc把這個函數扔到fini section
- void out_msg(const char *m)
- {
- printf(" Ok!\n");
- }
- int i; //仍然是個計數器
- void my_init(void)
- {
- printf("Init ... ... %d\n", ++i);
- }
- void my_fini(void)
- {
- printf("Fini ... ... %d\n", ++i);
- }
重新製作 libs.so,ts本是不用重新編譯了,代碼維護升級方便很多。
然後執行: ./e &
可以看到螢幕輸出:(不完整資訊,只是順序一樣)
Init
Main
OK
Quit
Fini
可以看到我們自己定義的初始化函數以及解析函數都被執行了,而且是在最前面以及最後面。
如果s.c中的sleep(5)沒有注釋掉,那麼有機會:
./e&
./e&連續執行兩次,那麼初始化函數和解析函數也會執行兩次,雖然系統只載入了一次libs.so。
如果sleep時候kill 掉後台進程,那麼解析函數不會被執行。
5.使用我們自己庫裡的函數替換系統函數:
建立一個新的檔案b.c:我們要替換系統函數malloc以及free(可以自己寫個記憶體泄露偵查工具了)
C代碼
- #include <stdio.h>
- void* malloc(int size)
- {
- printf("My malloc\n");
- return NULL;
- }
- void free(void* ad)
- {
- printf("My free\n");
- }
老規矩,編譯連結成一個so檔案:得到libb.so
gcc -fPIC -g -c b.c -o libb.o
gcc -g -shared -Wl,-soname,libb.so -o libb.so -lc
修改s.c:重建libs.so
C代碼
- void out_msg()
- {
- int *p;
- p = (int*)malloc(100);
- free(p);
- printf("Stop Ok!\n");
- }
修改指令檔e:
#!/bin/sh
export LD_PRELOAD=${pwd}libb.so:${LD_PRELOAD}
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
關鍵就在LD_PRELOAD上了,這個路徑指定的so將在所有的so之前載入,並且符號會覆蓋後面載入的so檔案中的符號。如果可執行檔的許可權不合適(SID),這個變數會被忽略。
執行:./e &
嗯,可以看到我們的malloc,free工作了。
暫時就想到這麼多了。
linux下動態庫so檔案的一些認識