动态库中函数调用
环境:
- clang version 12.0.1
- cmake version 3.21.2
- g++ (GCC) 11.1.0
包含两个相同函数的库链接冲突
试想一下,在你的主函数中调用了一个函数,但是这个函数可以在不同的库中都有实现,那主函数中调用的应该是哪一个呢?
我们来试验一下,构建测试代码:
.
├── CMakeLists.txt
├── include
│ ├── second.h
│ └── work.h
├── main.cc
└── src
├── CMakeLists.txt
├── second.cc
└── work.cc
./CMakeLists.txt文件内容:
cmake_minimum_required(VERSION 3.14)
project(symbol)
include_directories(include)
add_subdirectory(src)
add_executable(${PROJECT_NAME} main.cc)
target_link_libraries(${PROJECT_NAME} second work)
./include/second.h
#pragma once
#include <iostream>
void SoFunction();
void DoFunction();
./include/work.h
#pragma once
#include <iostream>
void SoFunction();
src/CMakeLists.txt内容:
include_directories(${CMAKE_PROJECT_PATH}/include)
add_library(second SHARED second.cc)
add_library(work SHARED work.cc)
src/second.cc
#include "second.h"
void SoFunction()
{
std::cout << "conflict function call\n";
}
void DoFunction()
{
std::cout<<"DoFunction\n";
SoFunction();
}
src/work.cc
#include "work.h"
void SoFunction() { std::cout << "Call Sofunctiuon\n"; }
./main.cc
#include <iostream>
void SoFunction();
int main() {
std::cout<<"1. Main start... \n";
SoFunction();
std::cout<<"SoFunction call finished\n";
return 0;
}
构建运行:mkdir build && pushd build && cmake .. && make && ./symbol && popd && rm -rf build
执行结果如下:
conflict function call
SoFunction call finished
可以看到主函数调用了second.cc里面的SoFunction,因为我们先link的时libsecond.so的动态库,自然后link的libwork.so会被覆盖,如果我们在./CMakeLists.txt中调整link顺序,你将能得到不同的结果.
包含两个相同函数的动态库调用覆盖问题
如果我们在主函数中调用second/DoFunction其会调用SoFunction,那么这时会调用second.cc的SoFunction还是调用work.cc的SoFunction?
修改main函数进行测试:
#include <iostream>
#include "second.h"
#include "work.h"
int main() {
std::cout<<"1. Main start... \n";
DoFunction();
std::cout<<"DoFunction call finished\n";
SoFunction();
return 0;
}
这时候我们不修改link顺序,保持second先link,我们得到下面的结果:
1. Main start...
Second DoFunction
conflict function call
DoFunction call finished
conflict function call
不出意外,函数调用顺序为:
main.cc/DoFunction->second.cc/DoFunction->second.cc/SoFunction->work.cc/SoFunction
现在我们修改./CMakeLists.txt以调整link顺序target_link_libraries(${PROJECT_NAME} work second),重新运行代码,我们将得到下面的结果:
1. Main start...
Second DoFunction
Call Sofunctiuon
DoFunction call finished
Call Sofunctiuon
函数的调用顺序为:
main.cc/DoFunction->second.cc/DoFunction->work.cc/SoFunction->work.cc/SoFunction,这时因为函数SoFunction因为libwork.so先link,函数使用其的.那如何link先后顺序发生变化的情况下仍然实现同一个库的函数相互调用?
我们先看看两个库中的符号信息:nm -CD src/libwork.so,内容如下:
U __cxa_atexit@GLIBC_2.2.5
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000001129 T SoFunction()
U std::ios_base::Init::Init()@GLIBCXX_3.4
U std::ios_base::Init::~Init()@GLIBCXX_3.4
U std::cout@GLIBCXX_3.4
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<c
har> >&, char const*)@GLIBCXX_3.4
libsecond.so里面的内容:
U __cxa_atexit@GLIBC_2.2.5
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000001159 T DoFunction()
0000000000001139 T SoFunction()
U std::ios_base::Init::Init()@GLIBCXX_3.4
U std::ios_base::Init::~Init()@GLIBCXX_3.4
U std::cout@GLIBCXX_3.4
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<c
har> >&, char const*)@GLIBCXX_3.4
解决符号冲突导致的错误调用
隐藏符号
我们可以通过设置invisiable让动态库的符号是否对外可见,例如我们可以让libwork.so的符号对外不可见,那么我们就可以无论怎样都能调用到second的函数.你可以设置src/CMakeLists.txt的内容如下:
include_directories(${CMAKE_PROJECT_PATH}/include)
if(UNIX AND CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
add_library(work SHARED work.cc)
endif()
add_library(second SHARED second.cc)
上面的编译过程会自动将两个库的符号隐藏(CXX_FLAG在当前CMakeLists.txt一直生效,不能直接设置一个加参数,一个不加),这时候我们看到libwork.so的符号信息,执行上面的编译运行命令的时候会出错,因为找不到符号表DoFunction和SoFunction,从而出现未定义的引用.
U __cxa_atexit@GLIBC_2.2.5
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U std::ios_base::Init::Init()@GLIBCXX_3.4
U std::ios_base::Init::~Init()@GLIBCXX_3.4
U std::cout@GLIBCXX_3.4
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@GLIBCXX_3.4
这里我们使用手动link生成最后的二进制文件:clang++ main.cc -Iinclude -L. -lsecond -lwork -o symbol
这时候我们执行的时候将屏蔽libwork.so里面的符号,从而实现只调用second.cc里面的SoFunction.结果如下:
1. Main start...
Second DoFunction
conflict function call
SoFunction call finished
conflict function call
函数代码中禁用
在libsecond.so中人为开启SoFunction和DoFunction两个函数对外可见,修改second.cc(单独编译second.cc为libsecond.soclang++ -fvisibility=hidden -Iinclude src/second.cc -fPIC -shared -o libsecond.so)
#include "second.h"
__attribute__ ((visibility ("default"))) void SoFunction()
{
std::cout << "conflict function call\n";
}
__attribute__ ((visibility ("default"))) void DoFunction()
{
std::cout<<"Second DoFunction\n";
SoFunction();
}
然后在src/CMakeLists.txt开启visibility,编译即可正常运行,因为work.cc里面的符号没有对外暴露,所以我们不会调到work.cc里面的SoFunction.
通过文件设置导出函数表
在include/export.symb文件中添加下面信息:
{
global: *SoFunction*;
local: *;
};
- global:对应的是你想导出的函数
- local区域代表不想导出的符号,*号表示除了global中的符号全部不导出
手动编译动态库实现只保留SoFunction
clang++ -Wl,--version-script=include/export.symb -s -Iinclude src/second.cc -fPIC -shared -o libsecond.so,此时libsecond.so的内容如下:
U __cxa_atexit@GLIBC_2.2.5
w __cxa_finalize@GLIBC_2.2.5
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000001190 T DoFunction()
U std::ios_base::Init::Init()@GLIBCXX_3.4
U std::ios_base::Init::~Init()@GLIBCXX_3.4
U std::cout@GLIBCXX_3.4
U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<c
har> >&, char const*)@GLIBCXX_3.4
输出如下:
1. Main start...
Second DoFunction
conflict function call
DoFunction call finished
Call Sofunctiuon
这里的输出有点不一样(我们屏蔽了second.cc里面的SoFunction)
参考
部分内容参考了:编译链接时如何解决符号冲突问题