經過使用dlopen
接口能夠實現運行時的動態庫函數調用,須要知道動態庫中的函數原型。html
如下實現Linux C/C++
使用dlopen
的基本示例,並說明連接選項-rdynamic
的做用,提供動態加載可執行文件的示例。linux
dlopen(), dlsym(), dlclose(), dlerror() 均爲Linux系統實現的動態連接接口。shell
#include <dlfcn.h> // 以指定模式打開指定的動態鏈接庫文件,並返回一個句柄給調用進程。 // flag中必須設置如下的mode: // RTLD_LAZY 暫緩決定,等有須要時再解出符號 // RTLD_NOW 當即決定,返回前解除全部未決定的符號。 void *dlopen(const char *filename, int flag); // 當動態連接庫操做函數執行失敗時,能夠返回出錯信息,返回值爲NULL時,表示沒有錯誤信息。 char *dlerror(void); // handle是由dlopen打開動態連接庫後返回的指針,symbol就是要求獲取的函數的名稱,函數返回值是void*,指向函數的地址,供調用使用。 void *dlsym(void *handle, const char *symbol); // 將該.so的引用計數減一,當引用計數爲0時,將它從系統中卸載。 int dlclose(void *handle);
爲了使用dlopen
接口,須要設置連接選項-ldl
。網絡
# C dlopen test cmake_minimum_required(VERSION 3.10) project(dlopen_test C) set(WORK_DIR "${CMAKE_SOURCE_DIR}") set(CMAKE_C_FLAGS "-O0 -ggdb") set(CMAKE_EXE_LINKER_FLAGS "-ldl -rdynamic") file(GLOB_RECURSE SRC_FILES ${WORK_DIR}/src/main.c) file(GLOB_RECURSE lib_files ${WORK_DIR}/src/add.c) add_library(add SHARED ${lib_files}) link_directories(${CMAKE_BINARY_DIR}) add_executable(${PROJECT_NAME} ${SRC_FILES}) target_link_libraries(${PROJECT_NAME} add)
// file : main.c #include <stdio.h> #include <stdlib.h> // EXIT_FAILURE #include <dlfcn.h> // dlopen, dlerror, dlsym, dlclose typedef int(* FUNC_ADD)(int, int); // define alias of function pointer const char* dllPath = "./libadd.so"; int main() { void* handle = dlopen( dllPath, RTLD_LAZY ); if( !handle ) { fprintf( stderr, "[%s](%d) dlopen get error: %s\n", __FILE__, __LINE__, dlerror() ); exit( EXIT_FAILURE ); } do{ // for resource handle FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); printf( "1 add 2 is %d \n", add_func(1,2) ); }while(0); // for resource handle dlclose( handle ); return 0; }
// file : add.c int add(int a, int b) { return a+b; };
1 add 2 is 3
與C版本的區別在於,因爲動態庫函數經過C++編譯器完成編譯,須要注意命名修飾。當main中使用不帶修飾的名稱"add"獲取函數地址時,add()實現須要使用extern "C"
進行處理。app
# C++ dlopen test cmake_minimum_required(VERSION 3.10) project(dlopen_test_cpp CXX) set(WORK_DIR "${CMAKE_SOURCE_DIR}") set(CMAKE_CXX_FLAGS "-O0 -ggdb") set(CMAKE_EXE_LINKER_FLAGS "-ldl -rdynamic") file(GLOB_RECURSE SRC_FILES ${WORK_DIR}/src/main.cpp) file(GLOB_RECURSE lib_files ${WORK_DIR}/src/add.cpp) add_library(add SHARED ${lib_files}) link_directories(${CMAKE_BINARY_DIR}) add_executable(${PROJECT_NAME} ${SRC_FILES}) target_link_libraries(${PROJECT_NAME} add)
// file : main.cpp #include <stdio.h> #include <stdlib.h> // EXIT_FAILURE #include <dlfcn.h> // dlopen, dlerror, dlsym, dlclose typedef int(* FUNC_ADD)(int, int); // define alias of function pointer const char* dllPath = "./libadd.so"; int main() { void* handle = dlopen( dllPath, RTLD_LAZY ); if( !handle ) { fprintf( stderr, "[%s](%d) dlopen get error: %s\n", __FILE__, __LINE__, dlerror() ); exit( EXIT_FAILURE ); } do{ // for resource handle FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); printf( "1 add 2 is %d \n", add_func(1,2) ); }while(0); // for resource handle dlclose( handle ); return 0; }
// file : add.cpp #ifdef __cplusplus extern "C" { #endif int add(int a, int b) { return a+b; }; #ifdef __cplusplus } #endif
1 add 2 is 3
在CMakeLists.txt文件中設置瞭如下的連接選項,這裏選項的設置參考了網絡文章,爲使用dlopen,選項-ldl
是必須的。函數
set(CMAKE_EXE_LINKER_FLAGS "-ldl -rdynamic")
可是當不設置-rdynamic
時也能夠完成編譯連接並正確運行。那麼-rdynamic
選項起什麼做用呢。ui
關於-rdynamic
,查看man gcc
,有如下說明,this
-rdynamic.net
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.插件
默認狀況下符號只會從共享庫中導出,當連接器設置-rdynamic
後,將使得ELF可執行程序可以導出符號。或許在動態加載插件中有必定用途。
關於--export-dynamic
,查看man ld
,有如下說明,
-E
--export-dynamic
--no-export-dynamic
When creating a dynamically linked executable, using the -E option or the --export-dynamic option causes the linker to add all symbols to the dynamic symbol table. The dynamic symbol table is the set of symbols which are visible from dynamic objects at run time.
If you do not use either of these options (or use the --no-export-dynamic option to restore the default behavior), the dynamic symbol table will normally contain only those symbols which are referenced by some dynamic object mentioned in the link.
If you use
"dlopen"
to load a dynamic object which needs to refer back to the symbols defined by the program, rather than some other dynamic object, then you will probably need to use this option when linking the program itself.You can also use the dynamic list to control what symbols should be added to the dynamic symbol table if the output format supports it. See the description of --dynamic-list.
Note that this option is specific to ELF targeted ports. PE targets support a similar function to export all symbols from a DLL or EXE ; see the description of --export-all-symbols below.
在以上的解釋中,指出在使用dlopen
加載動態目標時,可能須要引用一個程序自身而非其它動態目標定義的符號,即連接這個程序自身。
所以,經過開啓這個選項能夠實現動態加載可執行文件。那麼,因爲在以上的兩個示例中連接目標並不是可執行文件,能夠不用加入連接選項-rdynamic
。
在gcc中,-rdynamic
與-Wl,-E
和-Wl,--export-dynamic
的做用等價。
If the linker is being invoked indirectly, via a compiler driver (e.g. gcc) then all the linker command line options should be prefixed by -Wl, (or whatever is appropriate for the particular compiler driver) like this:
gcc -Wl,--start-group foo.o bar.o -Wl,--end-group
This is important, because otherwise the compiler driver program may silently drop the linker options, resulting in a bad link.
即經過編譯器調用連接器並指定連接選項時,須要在前面加上-Wl
,避免連接選項被編譯器忽略,致使連接失敗。
下面提供一個示例,動態加載可執行文件。
須要在編譯時使用-fpie/-fPIE
並在連接時使用-pie
。相似-fpic/-fPIC
,區別在於生成的代碼供可執行文件連接。
在此示例中,將動態加載可執行文件,-rdynamic
爲必須使用的連接選項。
其中在main.cpp中實現一個進行減法的add()函數,並將加載可執行文件dlopen_test_elf。
# Executable dlopen test cmake_minimum_required(VERSION 3.10) project(dlopen_test_elf CXX) set(WORK_DIR "${CMAKE_SOURCE_DIR}") set(CMAKE_CXX_FLAGS "-O0 -ggdb -fpie") set(CMAKE_EXE_LINKER_FLAGS "-ldl -rdynamic -pie") file(GLOB_RECURSE SRC_FILES ${WORK_DIR}/src/main.cpp) add_executable(${PROJECT_NAME} ${SRC_FILES})
// file : main.cpp #include <stdio.h> #include <stdlib.h> // EXIT_FAILURE #include <dlfcn.h> // dlopen, dlerror, dlsym, dlclose extern "C"{ int add(int a, int b) { return a-b; }; } typedef int(* FUNC_ADD)(int, int); // define alias of function pointer const char* dllPath = "./dlopen_test_elf"; int main() { void* handle = dlopen( dllPath, RTLD_LAZY ); if( !handle ) { fprintf( stderr, "[%s](%d) dlopen get error: %s\n", __FILE__, __LINE__, dlerror() ); exit( EXIT_FAILURE ); } do{ // for resource handle FUNC_ADD add_func = (FUNC_ADD)dlsym( handle, "add" ); printf( "1 add 2 is %d \n", add_func(1,2) ); }while(0); // for resource handle dlclose( handle ); return 0; }
1 add 2 is -1
經過使用readelf --dyn-syms
也能夠觀察使用/不使用鏈接選項-rdynamic
時,生成二進制文件中的符號信息是不一樣的。
不使用-rdynamic
時,
Symbol table '.dynsym' contains 15 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2) 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlerror@GLIBC_2.2.5 (3) 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlclose@GLIBC_2.2.5 (3) 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlopen@GLIBC_2.2.5 (3) 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlsym@GLIBC_2.2.5 (3) 13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stderr@GLIBC_2.2.5 (2) 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.2.5 (2)
使用-rdynamic
時,
Symbol table '.dynsym' contains 29 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.2.5 (2) 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__ 3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.2.5 (2) 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.2.5 (2) 6: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable 8: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlerror@GLIBC_2.2.5 (3) 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlclose@GLIBC_2.2.5 (3) 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlopen@GLIBC_2.2.5 (3) 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND dlsym@GLIBC_2.2.5 (3) 13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND stderr@GLIBC_2.2.5 (2) 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fprintf@GLIBC_2.2.5 (2) 15: 0000000000202068 0 NOTYPE GLOBAL DEFAULT 25 __data_start 16: 0000000000202070 8 OBJECT GLOBAL DEFAULT 25 dllPath 17: 0000000000202080 0 NOTYPE GLOBAL DEFAULT 26 _end 18: 0000000000202078 0 NOTYPE GLOBAL DEFAULT 25 _edata 19: 0000000000202068 0 NOTYPE WEAK DEFAULT 25 data_start 20: 0000000000000a40 0 FUNC GLOBAL DEFAULT 13 _start 21: 0000000000000ca0 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used 22: 0000000000000c20 101 FUNC GLOBAL DEFAULT 13 __libc_csu_init 23: 0000000000000b55 22 FUNC GLOBAL DEFAULT 13 add 24: 0000000000202078 0 NOTYPE GLOBAL DEFAULT 26 __bss_start 25: 0000000000000b6b 179 FUNC GLOBAL DEFAULT 13 main 26: 0000000000000968 0 FUNC GLOBAL DEFAULT 11 _init 27: 0000000000000c90 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini 28: 0000000000000c94 0 FUNC GLOBAL DEFAULT 14 _fini
所以,-rdynamic
的做用在於導出可執行文件的符號信息。