Linux迭代呼叫共享動態庫導致segment fault

JuggLee發表於2014-10-30

    專案需要我編寫了一個動態庫libs2cShareLib.so供同組同事Carson使用,他也也編了一個動態庫libnative.so,並在這個庫內部使用我提供的介面。

昨天下午CAE的Evan編寫的一個動態庫libcapi_pcie.so中用到了libnative.so。這樣Evan的一個工程下就有了3個動態庫libcapi_pcie.so, libnative.so和libs2cShareLib.so,

他的編譯命令列是這樣的:

    "gcc -rdynamic -DKERNEL_64BIT -m64 -DLINUX  -Wall  -o main main.c  -ldl",

    並沒有指定連結相關動態庫,而是在程式中採用手動方式載入動態庫:

    “void *CAPI_LIB_handle = dlopen(”./capi_pcie.so“, RTLD_LAZY); 

     ......

    dlclose(CAPI_LIB_handle);"

    編譯完,執行"./main",前面都執行正常,到最後程式退出時出現segment fault。


    我們嘗試了幾種改動都可以避免出現segment fault:

    1. 註釋掉程式碼最後”(CAPI_LIB_handle);“;

    2.註釋掉libnative.so中呼叫libs2cShareLib.so的部分程式碼並重新編譯libnative.so;


    問題貌似已經定位到是libs2cShareLib.so的問題了,但這個庫是我寫的,經過反覆測試的,我把自己的測試程式拿到Evan那裡跑也是OK的。難道是動態庫中呼叫動態庫會有什麼限制?我又寫了個自測試程式測試libnative.so。編譯引數:

    ”gcc -o test -lnative -lstdc++ main.cpp“

    結果一切正常。


    只能查詢我測試程式和Evan程式的不同了,不同點有兩個:

    1.我的測試程式中的呼叫關係是:test->libnative.so->libs2cShareLib.so,兩層動態庫呼叫;而Evan的程式中是main->libcapi_pcie.so->libnative.so->libs2cShareLib.so,三層動態庫呼叫。

    2.我的測試程式中我是用連結器連結要用到的動態庫;而Evan的程式中是採用手動load的方式。

   

    一個一個來排查,針對第一點,讓Evan修改他的程式,直接load libnative.so,結果還是出錯,說明不是動態庫呼叫層數的關係了。

    針對第二點,修改Evan的編譯選項,指定連結要用到的相關動態庫,而不是程式中手動load。執行,結果正確!!!

    說明不是動態庫本身的問題了,難道說多層動態庫呼叫不能手動load?帶著這個疑問找老大,老大看了一下Evan程式碼,指著”void *CAPI_LIB_handle = dlopen(”./capi_pcie.so“, RTLD_LAZY); “這一行,會不會是這個load引數的問題。百度了一下:

    函式定義
    void * dlopen( const char * pathname, int mode);
    函式描述:
    mode是開啟方式,其值有多個,不同作業系統上實現的功能有所不同,在linux下,按功能可分為三類:
    1、解析方式
        RTLD_LAZY:在dlopen返回前,對於動態庫中的未定義的符號不執行解析(只對函式引用有效,對於變數引用總是立即解析)。
        RTLD_NOW: 需要在dlopen返回前,解析出所有未定義符號,如果解析不出來,在dlopen會返回NULL,錯誤為:: undefined symbol: xxxx.......
    2、作用範圍,可與解析方式通過“|”組合使用。
        RTLD_GLOBAL:動態庫中定義的符號可被其後開啟的其它庫重定位。
        RTLD_LOCAL: 與RTLD_GLOBAL作用相反,動態庫中定義的符號不能被其後開啟的其它庫重定位。如果沒有指明是RTLD_GLOBAL還是RTLD_LOCAL,則預設為RTLD_LOCAL。
    3、作用方式
       RTLD_NODELETE: 在dlclose()期間不解除安裝庫,並且在以後使用dlopen()重新載入庫時不初始化庫中的靜態變數。這個flag不是POSIX-2001標準。
       RTLD_NOLOAD: 不載入庫。可用於測試庫是否已載入(dlopen()返回NULL說明未載入,否則說明已載入),也可用於改變已載入庫的flag,如:先前載入庫的flag為 RTLD_LOCAL,用dlopen(RTLD_NOLOAD|RTLD_GLOBAL)後flag將變成RTLD_GLOBAL。這個flag不是POSIX-2001標準。
       RTLD_DEEPBIND:在搜尋全域性符號前先搜尋庫內的符號,避免同名符號的衝突。這個flag不是POSIX-2001標準。

    果斷把RTLD_LAZY改成RTLD_NOW,重新編譯執行,正常,也沒有segment fault了。

    終於找到原因了,RTLD_LAZY在呼叫dlopen時為了節省時間,並沒有解析所用到的未定義符號,而是在用到的時候再來解析;而RTLD_NOW就是解析所有用到的符號,類似於連結器連結指定動態庫的效果。

   但還是有個問題搞不明白,問什麼在libnative.so中註釋掉呼叫libs2cShareLib.so的程式碼, RTLD_LAZY方式就是可以的呢?

    main->libcapi_pcie.so->libnative.so->libs2cShareLib.so 程式結束出現segment fault錯誤;

    main->libcapi_pcie.so->libnative.so 程式結束,正常退出。

    希望有大神幫忙解惑,小弟感激不盡!

相關文章