尽管库已加载,但动态链接器不解析符号

时间:2021-01-11 17:06:10

标签: linux c++11 gcc linker shared-libraries

我在我的大型项目中偶然发现了以下问题:我有一组相互依赖和依赖外部库的库。在一个依赖项(“libvtkCommonCore-*.so”)中,有不同的变体,它们需要互换使用。变体具有不同的后缀(“libvtkCommonCore-custom1.so”、“libvtkCommonCore-custom2.so”等)。因此,我无法将需要符号的库直接链接到提供库。相反,我将使用它的库的应用程序链接到适当的变体,然后加载我自己的库。

这种方法通常有效,但在某些情况下会失败,我在找出问题所在时有点迷茫。

这种情况有效: Sketch of situation 1 (“libA”需要来自“libvtkCommonCore”的符号。它在运行时由“libB”中一些静态对象的构造函数使用带有标志 RTLD_LAZY|RTLD_GLOBAL 的“dlopen”调用加载。libvtkCommonCore* 和 libB 在构建时链接到一个可执行文件)

这种情况现在停止工作: Sketch of situation 2 (实际上与之前相同,但由于 libvtkCommonCore* 和 libB 在构建时链接到另一个库 libC 的事实而变得复杂。该库在运行时使用“dlopen”从可执行文件加载)

我通过将 LD_DEBUG 设置为“文件”、“符号”和/或“绑定”来调查这个案例并研究输出。它表明 libvtkCommonCore* 一直在加载、初始化并保存在内存中,并且在加载 libA 之前。当链接尝试解析 libA 中的“SymbolX”时,它不会搜索 libvtkCommonCore,尽管它会搜索需要相同符号的其他库。

注意:我使用 Linux (Ubuntu 20) 和最近的 Gcc 和 CMake。情况 1 中的可执行文件和情况 2 中的“libC”都是使用标志“-Wl,--add-needed -Wl,--no-as-needed”构建的。

注意 2:如果我在设置 LD_PRELOAD=libvtkCommonCore-custom1.so 的情况下在情况 2 中启动可执行文件,则不会出现错误。

如果您提供有关如何继续调试此问题的任何提示,我将不胜感激。

问题的最小示例由这些文件组成:

libvtkCommonCore-custom1.cpp:

#include <iostream>

void SymbolX()
{
    std::cout<<"This just does nothing useful."<<std::endl;
}

libA.cpp:

void SymbolX(); // in libvtkCommonCore-custom1.so

struct LibAStaticObject
{
    LibAStaticObject()
    {
        SymbolX();
    }
} libAStaticObject;

libB.cpp:

#include <dlfcn.h>
#include <iostream>

class LibALoader
{
public:
    LibALoader()
    {
        void *handle = dlopen ( "libA.so", RTLD_LAZY|RTLD_GLOBAL|RTLD_NODELETE );
        if ( !handle ) 
        {
            std::cerr<<"Could not load module library libA!\nReason: " << dlerror() << std::endl;
        }
    }
} libAloader;

libC.cpp

/*empty*/

executable_situation1.cpp:

#include <iostream>

int main(int argc, char*argv[])
{
    std::cout<<"starting."<<std::endl;
    return 0;
}

executable_situation2.cpp

#include <iostream>
#include <dlfcn.h>

class LibCLoader
{
public:
    LibCLoader()
    {
        void *handle = dlopen ( "libC.so", RTLD_LAZY|RTLD_GLOBAL|RTLD_NODELETE );
        if ( !handle ) 
        {
            std::cerr<<"Could not load module library libC.so!\nReason: " << dlerror() << std::endl;
        }
    }
} libCloader;

int main(int argc, char*argv[])
{
    std::cout<<"starting."<<std::endl;
    return 0;
}

CMakeLists.txt:

add_library(vtkCommonCore-custom1 SHARED libvtkCommonCore-custom1.cpp)

add_library(A SHARED libA.cpp)

add_library(B SHARED libB.cpp)
target_link_libraries(B dl)

add_library(C SHARED libC.cpp)
target_link_libraries(C vtkCommonCore-custom1 B)
set_target_properties(C PROPERTIES LINK_FLAGS "-Wl,--add-needed -Wl,--no-as-needed -Wl,--copy-dt-needed-entries")

add_executable(executable_situation1 executable_situation1.cpp)
target_link_libraries(executable_situation1 vtkCommonCore-custom1 B)
set_target_properties(executable_situation1 PROPERTIES LINK_FLAGS "-Wl,--add-needed -Wl,--no-as-needed -Wl,--copy-dt-needed-entries") #"-Wl,--no-as-needed")

add_executable(executable_situation2 executable_situation2.cpp)
target_link_libraries(executable_situation2 dl)

通过以下命令运行它:

$ mkdir build
$ cd build 
$ cmake .. && make 
$ LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./executable_situation1 
This just does nothing useful. 
starting. 
$ LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./executable_situation2 
./executable_situation2: symbol lookup error: ./libA.so: undefined symbol: _Z7SymbolXv

1 个答案:

答案 0 :(得分:0)

事实上,问题是在情况 2 中 libvtkCommonCore 不在 libA 的查找范围内,而在情况 1 中它在全局范围内。

我发现的唯一(可能是丑陋的)解决方案是放入一种存根库,该库使用带有选项“RTLD_GLOBAL”的“dlopen”加载 libvtkCommonCore 和 libB。这会将 libvtkCommonCore 置于全局查找范围内。然后将新库链接到 libC 而不是它的直接依赖项。