天天看點

rdynamic和-whole-archive

遇到如下情況,主程式通過dlopen來打開.so檔案,但是.so用到了主程式的log函數。

編譯so時,通過引用主程式頭檔案來編譯通過,頭檔案有log函數聲明:

  extern "C" { 

   void print()

  }

在主程式的.c檔案裡有函數的具體實作。

但是dlopen後運作so中函數時,出現找不到相應的symbol。

這時候就需要在編譯主程式ld時加上參數-rdynamic,該參數的作用是:将訓示連接配接器把所有符号(而不僅僅隻是程式已使用到的外部符号,但不包括靜态符号,比如被static修飾的函數)都添加到動态符号表(即.dynsym表)裡,以便那些通過dlopen()或backtrace()(這一系列函數使用.dynsym表内符号)這樣的函數使用。

-rdynamic

Pass the flag ‘-export-dynamic’ to the ELF linker, on targets that support

it. This instructs the linker to add all symbols, not only used ones, to the

dynamic symbol table. This option is needed for some uses of dlopen or to

allow obtaining backtraces from within a program.

-g是編譯選項,而-rdynamic是連結選項

參考:http://www.lenky.info/archives/2013/01/2190

小例子:

a.cc

#include "stdio.h"  

#include <dlfcn.h>  

extern "C" {  

void print()  

{  

    printf("I am in so file!\n");  

}  

void fun()  

    void * err = dlopen("./libtmp.so", RTLD_LAZY);  

    printf("dlopen = %p\n", err);  

    if (err == NULL) {  

        printf("err=%s\n", dlerror());  

    }     

a.h

extern "C" void print();  

extern "C" void fun();  // 函數聲明和定義都要有extern “C”,或者都沒有,否則調用時出現undefined symbol fun  

#define NODE_MODULE \  

extern "C" { \  

   static void print_main() __attribute__((constructor)) // dlopen時會自動調用該contructor函數</span>  

   static void print_main() { \  

       print(); \  

   } \  

}   

so.cc

#include "a.h"  

NODE_MODULE  

foo.h

<span style="font-size:18px;">void foo();</span>  

foo.cc

void foo()  

    printf("foo === \n");  

main.cc

int main(void)  

    fun();  

    return 0;  

Makefile

all:dynamic  

libtmp.so:so.cc  

    g++ -fPIC -shared -o $@ $^   

a.o:  

    g++ -c a.cc -fPIC  

liba.a:a.o  

    ar -r $@  $^   

libso.so: foo.cc liba.a  

    g++ -fPIC -shared -o $@ $< -L./ -la -Wl,--whole-archive -la  -Wl,--no-whole-archive -ldl  

dynamic:libso.so libtmp.so  

    g++ -o $@ main.cc -Wl,--rpath=. -L./ -lso rdynamic  

clean:  

    rm dynamic liba.a a.o libtmp.so  

運作dynamic後輸出為:

I am in so file!  

dlopen = 0xdeb030  

如果沒有-rdynamic,則輸出為:

dlopen = (nil)  

err=./libtmp.so: undefined symbol: print  

如果沒有-Wl,--whole-archive -la  -Wl,--no-whole-archive,也會有錯誤:undefined symbol: print

--whole-archive 可以把 在其後面出現的靜态庫包含的函數和變量輸出到動态庫,--no-whole-archive 則關掉這個特性

使用readelf -s libso.so | grep fun來檢視libso.so的符号表裡是否有fun這個函數暴露出來。有--whole-archive的可以查到fun,而沒有--whole-archive的,則找不到fun

先理清一下code

可執行檔案dynamic依賴與libso.so,而libso.so有包含liba.a,在liba.a的函數fun調用dlopen來打開libtmp.so

主函數調用liba.a的函數來打開libtmp.so

-fvisibility=hidden

  設定預設的ELF鏡像中符号的可見性為隐藏。使用這個特性可以非常充分的提高連接配接和加載共享庫的性能,生成更加優化的代碼,提供近乎完美的API輸出和防止符号碰撞。我們強烈建議你在編譯任何共享庫的時候使用該選項。

-fvisibility-inlines-hidden

        預設隐藏所有内聯函數,進而減小導出符号表的大小,既能縮減檔案的大小,還能提高運作性能,我們強烈建議你在編譯任何共享庫的時候使用該選項

是以編譯的時候也不能有-fvisibility=hidden和-fvisibility-inlines-hidden。如果有,也會在dlopen時造成錯誤:undefined symbol

總結:

本執行個體雖小,但用到了不少編譯選項

a: __attribute__((constructor))

主程式main函數之前被執行或dlopen時被執行

b: -rdynamic

ld時将動态庫的的所有符号都輸出到符号表,以便dlopen和backtrace也能調用

c: --whole-archive -la -Wl,--no-whole-archive

靜态庫的符号導入到動态庫的符号表中,預設是hidden的

d: -fvisibility=hidden和-fvisibility-inlines-hidden

ELF鏡像中符号的可見性為隐藏(在實驗過程中不太好用,待研究)

在編譯nodejs第三方子產品時都會碰到這樣的問題,第三方子產品依賴與nodejs進行編譯,而第三方子產品又是通過dlopen來打開的,這就要求nodejs編譯時将一下第三方子產品需要的函數都暴露出來。

參考:

http://www.fx114.net/qa-225-106759.aspx

http://os.chinaunix.net/a2010/0112/1060/000001060902_3.shtml