• <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>

            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 閱讀(7960) 評論(1)  編輯 收藏 引用 所屬分類: c/c++

            評論

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

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

            亚洲va中文字幕无码久久| 99久久精品国产一区二区蜜芽 | 久久WWW免费人成—看片| 国产精品中文久久久久久久| 久久国产视屏| 人人狠狠综合88综合久久| 国产三级观看久久| 欧美日韩中文字幕久久久不卡| 久久不见久久见免费影院www日本| 国产精品久久久久乳精品爆 | 一本色道久久99一综合| 漂亮人妻被中出中文字幕久久| 香蕉aa三级久久毛片| 少妇熟女久久综合网色欲| 欧美亚洲国产精品久久高清| 久久99精品久久久大学生| 99久久精品国产一区二区| 人妻少妇久久中文字幕一区二区 | 91精品国产综合久久婷婷| 久久久国产精品亚洲一区| 国产精品岛国久久久久| 国产精品一区二区久久精品无码 | 国产精品久久久福利| 久久精品亚洲精品国产欧美| 亚洲精品第一综合99久久| 久久久久亚洲AV成人片| 久久91精品国产91久久小草| 狠狠色丁香婷婷综合久久来来去| 色偷偷88欧美精品久久久| 久久狠狠爱亚洲综合影院 | 久久无码一区二区三区少妇| 九九精品久久久久久噜噜| AV无码久久久久不卡蜜桃| 久久午夜综合久久| 久久久久久久久久久久中文字幕| 国产亚洲美女精品久久久| 久久精品国产亚洲av麻豆蜜芽 | 久久强奷乱码老熟女网站| 久久成人精品视频| 国产亚洲精久久久久久无码77777| 草草久久久无码国产专区|