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

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 閱讀(7987) 評論(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久久久久久久久| 狠狠久久亚洲欧美专区| 亚洲国产成人久久| 久久久久久久久蜜桃| 欧美亚洲免费电影| 欧美专区福利在线| 久久久亚洲一区| 久久婷婷久久| 亚洲第一区在线| 99国产精品久久久| 亚洲欧美偷拍卡通变态| 久久精品国产第一区二区三区最新章节 | 亚洲在线成人精品| 亚洲欧美精品在线| 欧美专区在线播放| 欧美成人精品一区二区| 亚洲第一精品福利| 一本色道久久综合亚洲精品高清 | 久久精品91久久香蕉加勒比| 久久精品夜夜夜夜久久| 欧美电影免费网站| av成人免费在线观看| 亚洲欧美中文日韩在线| 久久亚洲精品一区| 国产精品ⅴa在线观看h| 海角社区69精品视频| 亚洲精品免费电影| 久久精品国产69国产精品亚洲| 久久综合狠狠| 99在线精品免费视频九九视| 亚洲欧美日韩国产中文| 欧美jjzz| 国产亚洲精品久久久| 亚洲伦理网站| 久久久亚洲一区| 中文国产亚洲喷潮| 欧美91视频| 狠狠久久亚洲欧美| 亚洲尤物在线视频观看| 葵司免费一区二区三区四区五区| 亚洲激情成人在线| 久久精品国产久精国产思思| 欧美日韩国产一区二区三区地区 | 亚洲视频导航| 欧美极品aⅴ影院| 国内精品久久久久影院薰衣草| 亚洲啪啪91| 欧美色中文字幕| 久久久久99精品国产片| 欧美日韩成人综合| 亚洲韩国青草视频| 久久天天躁狠狠躁夜夜av| 亚洲一区激情| 欧美丝袜一区二区三区| 99视频一区二区| 亚洲国产精品电影在线观看| 久久精品国产一区二区三区免费看| 国产精品h在线观看| 亚洲美女免费视频| 欧美激情成人在线视频| 久久女同互慰一区二区三区| 国产午夜精品久久久久久免费视 | 亚洲国产网站| 欧美成人高清视频| 亚洲国产精品一区二区第四页av| 久久久亚洲精品一区二区三区| 亚洲综合首页| 国产精品亚洲片夜色在线| 亚洲欧美韩国| 国产精品99久久久久久白浆小说 | 国模叶桐国产精品一区| 午夜激情综合网| 亚洲一区二区三区在线| 国产精品欧美精品| 欧美专区福利在线| 久久久精品国产一区二区三区| 国产日韩三区| 久久亚洲视频| 美国十次了思思久久精品导航| 亚洲福利视频免费观看| 欧美激情1区| 欧美日韩亚洲在线| 午夜精品久久久| 欧美有码视频| 亚洲日本电影| 一区二区三区 在线观看视| 国产精品久久久久影院色老大| 午夜亚洲性色福利视频| 欧美影院一区| 亚洲精品久久久久中文字幕欢迎你| 亚洲激情视频在线| 国产精品乱码人人做人人爱| 久久久精品国产一区二区三区| 久久亚洲综合色一区二区三区| 亚洲欧洲日本国产| 亚洲视频大全| 亚洲国产三级网| 亚洲最快最全在线视频| 国内成+人亚洲| 亚洲精品国产视频| 国产一区二区0| 亚洲精品欧美日韩| 久久人人看视频| 欧美日韩美女| 久久精品国产亚洲高清剧情介绍| 久久免费视频这里只有精品| 99re66热这里只有精品3直播 | 久久婷婷蜜乳一本欲蜜臀| av不卡免费看| 久久精品成人欧美大片古装| 亚洲美洲欧洲综合国产一区| 亚洲免费影视第一页| 亚洲国产专区| 销魂美女一区二区三区视频在线| 最新日韩在线视频| 欧美在线精品一区| 亚洲一区二区三区四区中文| 久久综合狠狠综合久久综青草| 亚洲在线观看免费视频| 农村妇女精品| 麻豆精品精华液| 国产视频久久久久| 国产精品99久久不卡二区| 亚洲精品无人区| 久久亚洲春色中文字幕久久久| 亚洲欧美在线一区| 欧美日韩免费观看一区三区 | 久久久久久一区二区三区| 欧美日韩直播| 亚洲另类黄色| 日韩一区二区高清| 蜜桃av一区二区| 欧美1区2区视频| 国产在线视频欧美一区二区三区| 在线综合视频| 亚洲性色视频| 欧美日韩在线高清| 亚洲人线精品午夜| 日韩亚洲一区二区| 欧美福利在线观看| 亚洲国产日韩精品| 在线观看亚洲| 久久艳片www.17c.com| 久久亚洲一区| 国产一区二区三区精品久久久| av不卡在线观看| 亚洲视频一二区| 欧美色精品天天在线观看视频| 亚洲精品一二三区| 一区电影在线观看| 欧美日韩国产片| aa级大片欧美三级| 亚洲曰本av电影| 国产精品香蕉在线观看| 亚洲免费在线精品一区| 久久狠狠亚洲综合| 在线观看成人一级片| 欧美aⅴ99久久黑人专区| 亚洲精品国精品久久99热| 一级成人国产| 国产乱码精品一区二区三区五月婷| 亚洲欧美日韩在线观看a三区| 欧美影院午夜播放| 精品福利免费观看| 欧美激情视频在线播放| 亚洲一区二区日本| 欧美在线视频全部完| 免费在线一区二区| 一本色道久久综合狠狠躁篇的优点 | 中文精品在线| 国产精品视区| 久久亚洲图片| 一区二区欧美精品| 久久伊人亚洲| 中文在线资源观看网站视频免费不卡 | 久久精品国产亚洲aⅴ| 亚洲激情在线播放| 欧美三区美女| 久久99伊人| 亚洲人久久久| 久久国产精品久久精品国产| 精品99一区二区| 欧美区二区三区| 午夜精品久久久久久久男人的天堂| 久久综合伊人77777蜜臀| 亚洲精品男同| 国产亚洲精品aa午夜观看| 欧美成人自拍视频| 午夜精品久久| 亚洲国产日韩欧美在线99 | 99亚洲视频| 久久亚洲国产精品日日av夜夜| 亚洲精品国产精品国自产观看| 国产精品视频男人的天堂| 久久青草福利网站| 亚洲午夜精品久久| 91久久久久久久久久久久久| 久久成人av少妇免费| 亚洲精品在线电影| 在线观看中文字幕不卡|