青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

loop_in_codes

低調做技術__歡迎移步我的獨立博客 codemaro.com 微博 kevinlynx

linux動態庫的種種要點

linux下使用動態庫,基本用起來還是很容易。但如果我們的程序中大量使用動態庫來實現各種框架/插件,那么就會遇到一些坑,掌握這些坑才有利于程序更穩健地運行。

本篇先談談動態庫符號方面的問題。

測試代碼可以在github上找到

符號查找

一個應用程序test會鏈接一個動態庫libdy.so,如果一個符號,例如函數callfn定義于libdy.so中,test要使用該函數,簡單地聲明即可:

// dy.cpp libdy.so
void callfn() {
    ...
}

// main.cpp test
extern void callfn();

callfn();

在鏈接test的時候,鏈接器會統一進行檢查。

同樣,在libdy.so中有相同的規則,它可以使用一個外部的符號,在它被鏈接/載入進一個可執行程序時才會進行符號存在與否的檢查。這個符號甚至可以定義在test中,形成一種雙向依賴,或定義在其他動態庫中:

// dy.cpp libdy.so
extern void mfunc();

mfunc();

// main.cpp test
void mfunc() {
    ...
}

在生成libdy.so時mfunc可以找不到,此時mfunc為未定義:

$ nm libdy.so | grep mfun
U _Z5mfuncv

但在libdy.so被鏈接進test時則會進行檢查,試著把mfunc函數的定義去掉,就會得到一個鏈接錯誤:

./libdy.so: undefined reference to `mfunc()'

同樣,如果我們動態載入libdy.so,此時當然可以鏈接通過,但是在載入時同樣得到找不到符號的錯誤:

#ifdef DY_LOAD
    void *dp = dlopen("./libdy.so", RTLD_LAZY);
    typedef void (*callfn)();
    callfn f = (callfn) dlsym(dp, "callfn");
    f();
    dlclose(dp);
#else
    callfn();
#endif

得到錯誤:

./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv

結論:基于以上,我們知道,如果一個動態庫依賴了一些外部符號,這些外部符號可以位于其他動態庫甚至應用程序中。我們可以再鏈接這個動態庫的時候就把依賴的其他庫也鏈接上,或者推遲到鏈接應用程序時再鏈接。而動態加載的庫,則要保證在加載該庫時,進程中加載的其他動態庫里已經存在該符號。

例如,通過LD_PRELOAD環境變量可以讓一個進程先加載指定的動態庫,上面那個動態加載啟動失敗的例子,可以通過預先加載包含mfunc符號的動態庫解決:

$ LD_PRELOAD=libmfun.so ./test
...

但是如果這個符號存在于可執行程序中則不行:

$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ nm test | grep mfunc
0000000000400a00 T _Z5mfuncv
$ ./test
...
./test: symbol lookup error: ./libdy.so: undefined symbol: _Z5mfuncv

符號覆蓋

前面主要講的是符號缺少的情況,如果同一個符號存在多分,則更能引發問題。這里談到的符號都是全局符號,一個進程中某個全局符號始終是全局唯一的。為了保證這一點,在鏈接或動態載入動態庫時,就會出現忽略重復符號的情況。

這里就不提同一個鏈接單位(如可執行程序、動態庫)里符號重復的問題了

函數

當動態庫和libdy.so可執行程序test中包含同名的函數時會怎樣?根據是否動態加載情況還有所不同。

當直接鏈接動態庫時,libdy.so和test都會鏈接包含func函數的fun.o,為了區分,我把func按照條件編譯得到不同的版本:

// fun.cpp
#ifdef V2
extern "C" void func() {
    printf("func v2\n");
}
#else
extern "C" void func() {
    printf("func v1\n");
}
#endif

// Makefile
test: libdy obj.o mainfn
    g++ -g -Wall -c fun.cpp -o fun.o # 編譯為fun.o
    g++ -g -Wall -c main.cpp #-DDY_LOAD
    g++ -g -Wall -o test main.o obj.o fun.o -ldl mfun.o -ldy -L.

libdy: obj
    g++ -Wall -fPIC -c fun.cpp -DV2 -o fun-dy.o  # 定義V2宏,編譯為fun-dy.o
    g++ -Wall -fPIC -shared -o libdy.so dy.cpp -g obj.o fun-dy.o

這樣,test中的func就會輸出func v1;libdy.so中的func就會輸出func v2。test和libdy.o確實都有func符號:

$ nm libdy.so | grep func
0000000000000a60 T func

$nm test | grep func
0000000000400a80 T func

在test和libdy.so中都會調用func函數:

// main.cpp test
int main(int argc, char **argv) {
    func();
    ...
    callfn(); // 調用libdy.so中的函數
    ...
}

// dy.cpp libdy.so
extern "C" void callfn() {
    ... 
    printf("callfn\n");
    func();
    ...
}

運行后發現,都調用的是同一個func

$ ./test
...
func v1
...
callfn
func v1

結論,直接鏈接動態庫時,整個程序運行的時候符號會發生覆蓋,只有一個符號被使用。在實踐中,如果程序和鏈接的動態庫都依賴了一個靜態庫,而后他們鏈接的這個靜態庫版本不同,則很有可能因為符號發生了覆蓋而導致問題。(靜態庫同普通的.o性質一樣,參考淺析靜態庫鏈接原理)

更復雜的情況中,多個動態庫和程序都有相同的符號,情況也是一樣,會發生符號覆蓋。如果程序里沒有這個符號,而多個動態庫里有相同的符號,也會覆蓋。

但是對于動態載入的情況則不同,同樣的libdy.so我們在test中不鏈接,而是動態載入:

int main(int argc, char **argv) {
    func();
#ifdef DY_LOAD
    void *dp = dlopen("./libdy.so", RTLD_LAZY);
    typedef void (*callfn)();
    callfn f = (callfn) dlsym(dp, "callfn");
    f();
    func();
    dlclose(dp);
#else
    callfn();
#endif
    return 0;
}

運行得到:

$ ./test
func v1
...
callfn
func v2
func v1

都正確地調用到各自鏈接的func

結論,實踐中,動態載入的動態庫一般會作為插件使用,那么其同程序鏈接不同版本的靜態庫(相同符號不同實現),是沒有問題的。

變量

變量本質上也是符號(symbol),但其處理規則和函數還有點不一樣(是不是有點想吐槽了)。

// object.h
class Object {
public:
    Object() {
#ifdef DF
        s = malloc(32);
        printf("s addr %p\n", s);
#endif
        printf("ctor %p\n", this);
    }

    ~Object() {
        printf("dtor %p\n", this);
#ifdef DF
        printf("s addr %p\n", s);
        free(s);
#endif
    }

    void *s;
};

extern Object g_obj;

我們的程序test和動態庫libdy.so都會鏈接object.o。首先測試test鏈接libdy.so,test和libdy.so中都會有g_obj這個符號:

// B g_obj 表示g_obj位于BSS段,未初始化段

$ nm test | grep g_obj
0000000000400a14 t _GLOBAL__I_g_obj
00000000006012c8 B g_obj
$ nm libdy.so | grep g_obj
000000000000097c t _GLOBAL__I_g_obj
0000000000200f30 B g_obj

運行:

$ ./test
ctor 0x6012c8
ctor 0x6012c8
...
dtor 0x6012c8
dtor 0x6012c8

g_obj被構造了兩次,但地址一樣。全局變量只有一個實例,似乎在情理之中。

動態載入libdy.so,變量地址還是相同的:

$ ./test
ctor 0x6012a8
...
ctor 0x6012a8
...
dtor 0x6012a8
dtor 0x6012a8

結論,不同于函數,全局變量符號重復時,不論動態庫是動態載入還是直接鏈接,變量始終只有一個。

但詭異的情況是,對象被構造和析構了兩次。構造兩次倒無所謂,浪費點空間,但是析構兩次就有問題。因為析構時都操作的是同一個對象,那么如果這個對象內部有分配的內存,那就會對這塊內存造成double free,因為指針相同。打開DF宏實驗下:

$ ./test
s addr 0x20de010
ctor 0x6012b8
s addr 0x20de040
ctor 0x6012b8
...
dtor 0x6012b8
s addr 0x20de040
dtor 0x6012b8
s addr 0x20de040

因為析構的兩次都是同一個對象,所以其成員s指向的內存被釋放了兩次,從而產生了double free,讓程序coredump了。

總結,全局變量符號重復時,始終會只使用一個,并且會被初始化/釋放兩次,是一種較危險的情況,應當避免在使用動態庫的過程中使用全局變量。

posted on 2014-11-04 00:55 Kevin Lynx 閱讀(7995) 評論(1)  編輯 收藏 引用 所屬分類: c/c++

評論

# re: linux動態庫的種種要點 2015-06-11 09:09 sdhzdmzzl

我昨天也碰到了這個問題。可以參加http://www.ibm.com/developerworks/cn/linux/l-cn-sdlstatic/  回復  更多評論   

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美精品成人91久久久久久久| 亚洲精品视频啊美女在线直播| 亚洲天堂男人| 欧美成人黄色小视频| 亚洲视频网站在线观看| 欧美电影免费观看高清| 狠狠操狠狠色综合网| 先锋影音网一区二区| 亚洲精品乱码久久久久久日本蜜臀| 久久精彩免费视频| 国产老女人精品毛片久久| 亚洲少妇中出一区| 91久久夜色精品国产九色| 久久综合五月| 影音先锋久久久| 久久久久久久综合色一本| 亚洲欧美在线播放| 国产精品美女久久久免费| 亚洲一区不卡| 日韩小视频在线观看专区| 欧美精品在线一区二区三区| 亚洲啪啪91| 欧美激情视频一区二区三区不卡| 久久久爽爽爽美女图片| 一区精品在线播放| 久久乐国产精品| 久久精品99国产精品日本| 国产亚洲人成a一在线v站| 久久国产精品99久久久久久老狼| 亚洲视频一起| 国产精品素人视频| 麻豆成人在线| 欧美国产一区视频在线观看| 91久久精品日日躁夜夜躁欧美| 免费日韩av片| 猛干欧美女孩| 亚洲电影免费观看高清| 欧美一区二区三区久久精品| 国产亚洲欧美一区二区三区| 久久久久久伊人| 久久久91精品国产| 影音先锋久久久| 欧美大成色www永久网站婷| 裸体一区二区| 久久久久久久91| 国产精品日韩欧美一区二区| 欧美亚洲免费高清在线观看| 午夜精彩视频在线观看不卡| 国模私拍一区二区三区| 免费视频一区| 欧美国产亚洲视频| 洋洋av久久久久久久一区| 日韩一级精品| 久久免费视频这里只有精品| 亚洲国产日韩一区| 亚洲人成人99网站| 欧美午夜宅男影院在线观看| 欧美一级二级三级蜜桃| 久久精品男女| 亚洲精品美女在线| 一本久久精品一区二区| 国产精品天天看| 美女精品在线| 女同一区二区| 亚洲综合丁香| 久久精品盗摄| 亚洲蜜桃精久久久久久久| 一区二区三区高清视频在线观看 | 亚洲精品永久免费| 一区二区三区日韩| 国内精品视频666| 91久久精品国产91久久性色tv| 国产精品v一区二区三区| 久久久999| 欧美精品播放| 久久精品日韩一区二区三区| 久久躁狠狠躁夜夜爽| 在线亚洲成人| 欧美中文字幕在线| 99国产精品久久久| 亚洲欧美日韩一区二区在线| 亚洲国产精品黑人久久久| 一本色道久久综合狠狠躁篇怎么玩 | 一区二区三区鲁丝不卡| 午夜亚洲福利| 亚洲精品一二三| 亚洲在线视频| 亚洲人www| 亚洲欧美日韩视频二区| 亚洲日本在线视频观看| 亚洲欧美久久久| 91久久极品少妇xxxxⅹ软件| 亚洲综合不卡| 亚洲精品社区| 久久国产精品色婷婷| 亚洲深夜激情| 久久久人成影片一区二区三区观看| 亚洲视频精选在线| 久久久久久穴| 香蕉视频成人在线观看| 欧美精品1区| 久久夜色精品国产亚洲aⅴ| 欧美亚洲第一区| 欧美国产综合| 国产一区av在线| 亚洲精品在线一区二区| 永久免费视频成人| 亚洲一区在线观看免费观看电影高清| 亚洲激情小视频| 欧美一区二区三区久久精品| 亚洲午夜久久久| 欧美成人免费大片| 久久嫩草精品久久久精品一| 国产精品久久久久久久电影| 亚洲国产天堂网精品网站| 国产主播在线一区| 亚洲尤物视频网| 在线一区视频| 欧美高清视频www夜色资源网| 久久嫩草精品久久久精品| 国产精品亚洲综合一区在线观看| 亚洲人成网站777色婷婷| 亚洲福利视频一区二区| 欧美有码在线视频| 欧美一区二区三区婷婷月色| 欧美视频精品在线观看| 亚洲欧洲偷拍精品| 亚洲国产婷婷| 久久久久久999| 久久久久久穴| 国产偷自视频区视频一区二区| 亚洲视频www| 亚洲在线观看视频| 欧美日韩一区二区三区在线| 最新日韩在线| 亚洲精品久久久久| 欧美1区视频| 欧美肥婆bbw| 亚洲国产高清aⅴ视频| 久久精品在这里| 久久在线免费| 激情校园亚洲| 久久久噜噜噜久久人人看| 久久欧美肥婆一二区| 国产一区二区丝袜高跟鞋图片| 午夜久久久久久| 久久精品国产一区二区三| 国产农村妇女精品一二区| 亚洲欧美日本日韩| 性亚洲最疯狂xxxx高清| 国产精品永久免费在线| 亚洲欧美中文字幕| 欧美自拍偷拍| 韩国久久久久| 久久全国免费视频| 欧美成在线观看| 亚洲精品视频免费| 欧美激情一区二区三区在线视频观看 | 亚洲电影网站| 美女视频黄 久久| 亚洲国产精品一区二区第一页| 亚洲精品日韩欧美| 欧美日本一道本| 中文久久精品| 欧美一区激情| 黄网动漫久久久| 蜜月aⅴ免费一区二区三区 | 欧美在线一二三四区| 久久综合一区| 亚洲欧洲日韩女同| 欧美日韩国产123| 亚洲视频在线观看网站| 久久超碰97中文字幕| 精品av久久707| 欧美xxx在线观看| av成人黄色| 久久99在线观看| 在线不卡中文字幕播放| 欧美大成色www永久网站婷| 亚洲毛片在线观看.| 午夜一区二区三区不卡视频| 韩国一区二区三区在线观看| 欧美成人免费播放| 一区二区三区欧美在线观看| 久久精品国产精品亚洲综合| 亚洲电影成人| 欧美三级电影一区| 欧美一区二区三区婷婷月色 | 黄色精品一区二区| 美女福利精品视频| 一本色道久久综合亚洲精品小说| 欧美中文字幕在线视频| 亚洲丁香婷深爱综合| 欧美日精品一区视频| 销魂美女一区二区三区视频在线| 欧美成人精品| 午夜电影亚洲| 亚洲福利精品| 国产精品青草久久久久福利99| 久久久亚洲高清|