概述
通過使用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