遇到如下情況,主程式通過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