经过使用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
的做用在于导出可执行文件的符号信息。