使用dlopen加载动态库
概述
通过使用dlopen接口可以实现运行时的动态库函数调用,需要知道动态库中的函数原型。
以下实现Linux C/C++使用dlopen的基本示例,并说明链接选项-rdynamic的作用,提供动态加载可执行文件的示例。
接口
dlopen(), dlsym(), dlclose(), dlerror() 均为Linux系统实现的动态链接接口。
#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
CMakeLists.txt
# 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)
src/main.c
// 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;
}
src/add.c
// file : add.c
int add(int a, int b) { return a+b; };
./dlopen_test
1 add 2 is 3
C++
与C版本的区别在于,由于动态库函数通过C++编译器完成编译,需要注意命名修饰。当main中使用不带修饰的名称"add"获取函数地址时,add()实现需要使用extern "C"进行处理。
CMakeLists.txt
# 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)
src/main.cpp
// 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;
}
src/add.cpp
// file : add.cpp
#ifdef __cplusplus
extern "C" {
#endif
int add(int a, int b) { return a+b; };
#ifdef __cplusplus
}
#endif
./dlopen_test_cpp
1 add 2 is 3
-rdynamic
在CMakeLists.txt文件中设置了以下的链接选项,这里选项的设置参考了网络文章,为使用dlopen,选项-ldl是必须的。
set(CMAKE_EXE_LINKER_FLAGS "-ldl -rdynamic")
但是当不设置-rdynamic时也可以完成编译链接并正确运行。那么-rdynamic选项起什么作用呢。
关于-rdynamic,查看man gcc,有以下说明,
-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.
默认情况下符号只会从共享库中导出,当链接器设置-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的作用等价。
-Wl的作用
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。
CMakeLists.txt
# 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})
src/main.cpp
// 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;
}
./dlopen_test_elf
1 add 2 is -1
readelf --dyn-syms
通过使用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的作用在于导出可执行文件的符号信息。
参考资料
- 采用dlopen、dlsym、dlclose加载动态链接库 - 呆呆的张先生 - 简书
- 采用dlopen、dlsym、dlclose加载动态链接库【总结】 - Rabbit_Dale - 博客园
- dlopen 与dlsym-好喜儿-ChinaUnix博客
- dlopen dlsym dlclose解析 - yujixi123的专栏 - CSDN博客
- linux动态库加载的秘密 - LiuYanYGZ - 博客园
- c - What exactly does "-rdynamic" do and when exactly is it needed_ - Stack Overflow
- shared libraries - Why do we need -rdynamic option in gcc_ - Stack Overflow
使用dlopen加载动态库的更多相关文章
- Linux下c函数dlopen实现加载动态库so文件代码举例
dlopen()是一个强大的库函数.该函数将打开一个新库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了. ...
- LoadLibrary加载动态库失败
[1]LoadLibrary加载动态库失败的可能原因以及解决方案: (1)dll动态库文件路径不对.此场景细分为以下几种情况: 1.1 文件路径的确错误.比如:本来欲加载的是A文件夹下的动态库a.dl ...
- 【转载】cocos2dx 中 Android NDK 加载动态库的问题
原文地址:http://blog.csdn.net/sozell/article/details/10551309 cocos2dx 中 Android NDK 加载动态库的问题 闲聊 最近在接入各 ...
- QT常用代码之加载动态库和弹出对话框
作者:朱金灿 来源:http://blog.csdn.net/clever101 加载动态库的代码: typedef void (*Execute)(); // 定义导出函数类型 QString st ...
- Windows平台LoadLibrary加载动态库搜索路径的问题
一.背景 在给Adobe Premiere/After Effects等后期制作软件开发第三方插件的时候,我们总希望插件依赖的动态库能够脱离插件的位置,单独存储到另外一个地方.这样一方面可以与其他程序 ...
- QLibrary 加载动态库
阅读本文大概需要 6.6分钟 一般情况下在没有头文件支持情况下,想要引入某个动态库,最好的办法就是使用「动态加载」的方法,在Qt中一般使用QLibyary来操作 常用 api QLibrary(con ...
- C++加载动态库的形式来实现封装
目录结构 └── test ├── CMakeLists.txt ├── base.h //设置接口 ├── drive.cpp //具体实现 └── main.cpp //test CMakeLis ...
- QT/C++插件式框架、利用智能指针管理内存空间的实现、动态加载动态库文件
QT.C++插件式框架.主要原理还是 动态库的动态加载. dlopen()函数.下面为动态加载拿到Plugininstance对应指针.void**pp=(void**)dlsym(handle,&q ...
- lua加载动态库缺乏相应的系统库
错误信息: 使用lua测试lm2动态库时,加载时出现如下错误 jfyuan@jfy11-B85M-D2V:~/temp/service/soft/code/ginger_resty/cores/lm2 ...
随机推荐
- python内置常量是什么?
摘要:学习Python的过程中,我们会从变量常量开始学习,那么python内置的常量你知道吗? 一个新产品,想熟悉它,最好的办法就是查看说明书! 没错,Python也给我们准备了这样的说明书--Pyt ...
- The Preliminary Contest for ICPC Asia Nanjing 2019 A The beautiful values of the palace(树状数组+思维)
Here is a square matrix of n * nn∗n, each lattice has its value (nn must be odd), and the center val ...
- Java 窗口 小马图像窗口
写在前面: eclipse接着爽到 全是借鉴的,东改西改,而且记不住原网址了 两个月前写的,忘了思路,嗯,It just works 运行效果: 图像随便选(放到*jar所在目录*\\pictures ...
- 2017, X Samara Regional Intercollegiate Programming Contest E. Bonuses and Teleports (思维,模拟)
题意:在\(x\)轴上有很多传送点和钻石,当位于传送点上时,可以传送到其他任意传送点(不记操作数),位于钻石上时可以吃掉它,每次可以移动一个单位,问最少多少次可以吃掉所有的钻石. 题解:对于某个位置上 ...
- Codeforces Round #654 (Div. 2) D. Grid-00100 (构造)
题意:构造一个\(n\)x\(n\)只含\(0\)和\(k\)个\(1\)的矩阵,统计每一行每一列\(1\)的sum,然后构造一个权值最大行和最小行的差的平方加权值最大列和最小列的差的平方的最小和(\ ...
- C#Assembly、程序集、装配件、命名空间以及类型的关系
Assembly = 程序集 = 装配件 命名空间是类的逻辑组织形式,程序集是类的物理组织形式. 程序集其实和命名空间没有什么必然的联系. 程序集1: namespace1{ public class ...
- 国产网络损伤仪SandStorm -- 只需要上下拖拽能调整链路规则顺序
国产网络损伤仪SandStorm(弱网络测试)可以模拟出带宽限制.时延.时延抖动.丢包.乱序.重复报文.误码.拥塞等网络状况,在实验室条件下准确可靠地测试出网络应用在真实网络环境中的性能,以帮助应用程 ...
- Yocto项目介绍及入门 -- 嵌入师工程师必备利器
目录 写在前面 1. Yocto项目是什么 2. Yocto项目有什么用 3. 如何快速上手Yocto项目 4. 带你通过Yocto项目编译一个自定义镜像文件 写在前面 博主目前从事BMC工作,由于公 ...
- LEETCODE - 1181【前后拼接】
class Solution { public: string gethead(string str){//获取头单词 string ret = ""; int strlen = ...
- windows7 更新失败,无法开机处理方法
记录一次今天同事笔记本无法开机的故障处理 windows7系统,安装更新失败,无法开机,卡在配置更新界面 处理方法,强制关机,开机按F8,进入安全模式,安全模式还会卡在配置更新界面,但是卡一会会进入系 ...