阅读 362

解决动态库链接过程中的符号冲突问题

动态库中函数调用

环境:

  • clang version 12.0.1

  • cmake version 3.21.2

  • g++ (GCC) 11.1.0

包含两个相同函数的库链接冲突

试想一下,在你的主函数中调用了一个函数,但是这个函数可以在不同的库中都有实现,那主函数中调用的应该是哪一个呢?

link.drawio.png 我们来试验一下,构建测试代码:

. ├── 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.ccSoFunction还是调用work.ccSoFunction? 修改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 复制代码

link.drawio (1).png 不出意外,函数调用顺序为: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 复制代码

link.drawio (2).png 函数的调用顺序为: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的符号信息,执行上面的编译运行命令的时候会出错,因为找不到符号表DoFunctionSoFunction,从而出现未定义的引用.

                 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中人为开启SoFunctionDoFunction两个函数对外可见,修改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中的符号全部不导出

手动编译动态库实现只保留SoFunctionclang++ -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)


作者:bleedingfight
链接:https://juejin.cn/post/7010421599572590629

 伪原创工具 SEO网站优化  https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐