個人創(chuàng)作,歡迎指錯。
牽扯到ELF格式,gcc編譯選項待補,簡單實用的說明一下,對Linux下的so文件有個實際性的認識。
1.so文件是什么?
2.怎么生成以及使用一個so動態(tài)庫文件?
3.地址空間,以及線程安全.
4.庫的初始化,解析:
5.使用我們自己庫里的函數(shù)替換系統(tǒng)函數(shù):
//-------------------------------------------------------------------------------
1.so文件是什么?
也是ELF格式文件,共享庫(動態(tài)庫),類似于DLL。節(jié)約資源,加快速度,代碼升級簡化。
知道這么多就夠了,實用主義。等有了印象再研究原理。
2.怎么生成以及使用一個so動態(tài)庫文件?
先寫一個C文件:s.c
C代碼
#include <stdio.h>
int count;
void out_msg(const char *m)
{//2秒鐘輸出1次信息,并計數(shù)
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文件來引用這個庫中的函數(shù):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節(jié)的時候打開就可以。
printf("TS Quit\n");
}
編譯鏈接這個文件:得到輸出文件ts
gcc -g ts.c -o ts -L. -ls
執(zhí)行./ts,嗯:成功了。。。還差點
得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory
系統(tǒng)不能找到我們自己定義的libs.so,那么告訴他,修改變量LD_LIBRARY_PATH,為了方便,寫個腳本:e(文件名就叫e,懶得弄長了)
#!/bin/sh
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
執(zhí)行:./e &
屏幕上就開始不停有信息輸出了,當然TS Quit你是看不到的,前面是個死循環(huán),后面會用到這句
3.地址空間,以及線程安全:
如果這樣:
./e &開始執(zhí)行后,稍微等待一下然后再 ./e&,
這個時候屏幕信息會怎么樣呢?全局變量count會怎么變化?
會是兩個進程交叉輸出信息,并且各自的count互不干擾,雖然他們引用了同一個so文件。
也就是說只有代碼是否線程安全一說,沒有代碼是否是進程安全這一說法。
4.庫的初始化,解析:
windows下的動態(tài)庫加載,卸載都會有初始化函數(shù)以及卸載函數(shù)來完成庫的初始化以及資源回收,linux當然也可以實現(xiàn)。
ELF文件本身執(zhí)行時就會執(zhí)行一個_init()函數(shù)以及_fini()函數(shù)來完成這個,我們只要把自己的函數(shù)能讓系統(tǒng)在這個時候執(zhí)行
就可以了。
修改我們前面的s.c文件:
C代碼
#include <stdio.h>
void my_init(void) __attribute__((constructor)); //告訴gcc把這個函數(shù)扔到init section
void my_fini(void) __attribute__((destructor)); //告訴gcc把這個函數(shù)扔到fini section
void out_msg(const char *m)
{
printf(" Ok!\n");
}
int i; //仍然是個計數(shù)器
void my_init(void)
{
printf("Init ... ... %d\n", ++i);
}
void my_fini(void)
{
printf("Fini ... ... %d\n", ++i);
}
重新制作 libs.so,ts本是不用重新編譯了,代碼維護升級方便很多。
然后執(zhí)行: ./e &
可以看到屏幕輸出:(不完整信息,只是順序一樣)
Init
Main
OK
Quit
Fini
可以看到我們自己定義的初始化函數(shù)以及解析函數(shù)都被執(zhí)行了,而且是在最前面以及最后面。
如果s.c中的sleep(5)沒有注釋掉,那么有機會:
./e&
./e&連續(xù)執(zhí)行兩次,那么初始化函數(shù)和解析函數(shù)也會執(zhí)行兩次,雖然系統(tǒng)只加載了一次libs.so。
如果sleep時候kill 掉后臺進程,那么解析函數(shù)不會被執(zhí)行。
5.使用我們自己庫里的函數(shù)替換系統(tǒng)函數(shù):
創(chuàng)建一個新的文件b.c:我們要替換系統(tǒng)函數(shù)malloc以及free(可以自己寫個內(nèi)存泄露檢測工具了)
C代碼
#include <stdio.h>
void* malloc(int size)
{
printf("My malloc\n");
return NULL;
}
void free(void* ad)
{
printf("My free\n");
}
老規(guī)矩,編譯鏈接成一個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
關(guān)鍵就在LD_PRELOAD上了,這個路徑指定的so將在所有的so之前加載,并且符號會覆蓋后面加載的so文件中的符號。如果可執(zhí)行文件的權(quán)限不合適(SID),這個變量會被忽略。
執(zhí)行:./e &
嗯,可以看到我們的malloc,free工作了。
暫時就想到這么多了。