引言
在Linux系统上进行动态库链接时,gcc编译器的-Wl,-Bsymbolic/-Wl,-Bsymbolic-functions选项可以影响符号的绑定方式。本文将通过一个实际示例展示-Wl,-Bsymbolic/-Wl,-Bsymbolic-functions的作用。
示例代码
首先,我们写一个动态库,再写一个应用程序链接这个动态库调用其中的函数。
创建动态库
首先,创建一个动态库liboriginal.so,其中包含两个函数,一个是对外的接口函数api_function,另一个是库内部函数print_hello。函数api_function会调用函数print_hello。
// original.h
#pragma once
extern void api_function();
// original.cpp
#include <stdio.h>
#include "original.h"
void print_hello() {
printf("Original function prints hello\n");
}
void api_function() {
print_hello();
}
# CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(original)
SET(CMAKE_CXX_FLAGS "-fPIC -g")
# 指定源代码目录
AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR} LIBSRC)
# 指定静态库生成的目录
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
ADD_LIBRARY(original SHARED ${LIBSRC})
# cmake 在构建一个新的target时,清理掉其他使用这个名字的库
SET_TARGET_PROPERTIES(original PROPERTIES CLEAN_DIRECT_OUTPUT 1)
应用程序链接liboriginal.so
创建一个简单的应用程序main.cpp,调用api_function()函数:
// main.cpp
#include <stdio.h>
#include "original.h"
int main() {
api_function();
return 0;
}
# CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(test)
# 指定源代码目录
AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR} LIBSRC)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/../liborigina
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/../liboriginal/lib
ADD_EXECUTABLE(test ${LIBSRC})
TARGET_LINK_LIBRARIES(test original)
编译完成后,我们运行一下可执行程序。
[root@VM-8-2-centos build]# ./test
Original function prints hello
替换动态库的函数实现
接下来,我们创建一个新的动态库libreplacement.so,替换掉liboriginal.so的函数实现,参考我之前写的文章《替换动态库的函数实现》。
// replacement.h
#pragma once
extern void print_hello();
// replacement.cpp
#include <stdio.h>
#include "replacement.h"
void print_hello() {
printf("Replacement function prints hello\n");
}
# CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(replacement)
SET(CMAKE_CXX_FLAGS "-fPIC -g ")
# 指定源代码目录
AUX_SOURCE_DIRECTORY(${PROJECT_SOURCE_DIR} LIBSRC)
# 指定静态库生成的目录
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 生成动态库
ADD_LIBRARY(replacement SHARED ${LIBSRC})
# cmake 在构建一个新的target时,清理掉其他使用这个名字的库
SET_TARGET_PROPERTIES(replacement PROPERTIES CLEAN_DIRECT_OUTPUT 1)
使用LD_PRELOAD加载
运行之前生成的可执行程序,并使用LD_PRELOAD环境变量预先加载libreplacement.so。
[root@VM-8-2-centos build]# LD_PRELOAD=../../libreplacement/lib/libreplacement.so ./test
Replacement function prints hello
可以看到,print_hello函数被新库的实现替换了。那么,能不能不被别的库替换函数print_hello的实现呢?答案是能,接下来我们要学习一下。
-Wl,-Bsymbolic/-Wl,-Bsymbolic-function编译选项
这两个参数都是传给链接器的,作用是:优先使用本地符号,而不是全局符号。我们来看看使用后会有什么效果。
编译动态库
在Linux系统上进行动态库链接时,-Wl,-Bsymbolic或-Wl,-Bsymbolic-function选项可以影响符号的绑定方式。为了让前文中动态库中的函数不被替换,我们在编译动态库liboriginal.so时增加-Wl,-Bsymbolic-function编译选项,编译出新的动态库liboriginal.so。
# CMakeLists.txt
...省略
SET(CMAKE_CXX_FLAGS "-fPIC -g -Wl,-Bsymbolic-functions")
...省略
运行可执行程序
应用程序test无需重新编译,使用LD_PRELOAD运行预先加载libreplacement.so。
[root@VM-8-2-centos build]# LD_PRELOAD=../../libreplacement/lib/libreplacement.so ./test
Original function prints hello
大家可以看到,这次liboriginal.so的print_hello函数没有被libreplacement.so替换掉。
原理分析
最后,我们来了解一下原理。动态库函数的实现被替换的原因是:可执行程序优先加载了libreplacement.so中的函数的同名符号,导致符号冲突。本文通过在编译liboriginal.so时使用-Wl,-Bsymbolic-functions编译选项来解决。
现在来了解下使用的编译选项的功能:(1)编译选项-Wl,-Bsymbolic会优先使用本地符号,包括变量和函数;(2)编译选项-Wl,-Bsymbolic-functions会优先使用本地的函数符号。
这两个编译选项都能够防止动态库之间或可执行程序与动态库之间的符号冲突问题;在本文中,将这个选项应用到liboriginal.so中,当此库中的api_function函数调用print_hello函数时会优先使用自己库中的函数实现。
结束语
本文通过实例展示了-Wl,-Bsymbolic/-Wl,-Bsymbolic-function编译选项在动态库中的作用,以及在使用LD_PRELOAD机制时带来的影响。
需要注意的是,-Wl,-Bsymbolic由于强制变量也优先使用本地的,可能导致单例模式失效,以后会再写文章分享。最终本文使用-Wl,-Bsymbolic-functions选项来解决符号冲突问题,建议编译动态链接库时都带上此编译参数。