C++ 加载具有相同符号的两个共享库时是否存在符号冲突

C++ 加载具有相同符号的两个共享库时是否存在符号冲突,c++,shared-libraries,symbols,C++,Shared Libraries,Symbols,一个应用程序(app)依赖于两个共享库:liba.so和libb.so liba和libb具有相同的void Hello()功能,但实现方式不同。 在运行时加载两个共享库,并尝试访问Hello()的两个版本。 我加载了LIBA。因此,LIB。因此,通过PoC+C++共享库,但最终调用 dLund()/Stult>加载共享库。代码如下: #include "Poco/SharedLibrary.h" using Poco::SharedLibrary; typedef void (*HelloFu

一个应用程序(app)依赖于两个共享库:liba.solibb.so
libalibb具有相同的void Hello()功能,但实现方式不同。 在运行时加载两个共享库,并尝试访问Hello()的两个版本。
我加载了LIBA。因此,LIB。因此,通过PoC+C++共享库,但最终调用<强> dLund()/Stult>加载共享库。代码如下:

#include "Poco/SharedLibrary.h"
using Poco::SharedLibrary;
typedef void (*HelloFunc)(); // function pointer type


int main(int argc, char** argv)
{
    std::string path("liba");
    path.append(SharedLibrary::suffix()); // adds ".so"
    SharedLibrary library(path);
    HelloFunc func = (HelloFunc) library.getSymbol("hello");
    func();

    std::string path2("libb");
    path2.append(SharedLibrary::suffix()); // adds ".so"
    SharedLibrary library2(path2);
    HelloFunc func2 = (HelloFunc) library2.getSymbol("hello");
    func2();

    library.unload();
    library2.unload();

    return 0;
}
我的问题是,当应用程序通过dlopen()加载liba.so和libb.so时,两个Hello()实现是否存在符号冲突?
事实上,代码运行得很好,但我想知道这样加载库是否有潜在的风险

我的问题是,当应用程序通过dlopen()加载liba.so和libb.so时,两个Hello()实现是否存在符号冲突

没有。这些是返回的地址,两个动态加载的库都将位于单独的地址空间中

即使是dlsym函数也不能混淆,因为您传递了dlopen函数返回的句柄,因此它无论如何都不会变得模棱两可


(对于同一个库中的重载,这甚至不是一个问题)

TL;DR:如果要防止已加载的全局符号在dlopen()时劫持库,请始终使用
RTLD\u DEEPBIND

使用dlopen加载库时,可以使用dlsym访问其中的所有符号,这些符号将是该库中的正确符号,并且不会污染全局符号空间(除非使用RTLD_global)但即使库本身定义了符号,其依赖项仍使用已加载的全局符号(如果可用)进行解析。

考虑一个称为libexternal.so,external.c的第三方库:

#include <stdio.h>

void externalFn()
{
    printf("External function from the EXTERNAL library.\n");
}

然后考虑<代码> LBB.所以它不知不觉地实现一个并导出它,LBB.C:

#include <stdio.h>

void externalFn()
{
    printf("External implementation from B\n");
}

void hello()
{
    printf("Hello from B!\n");
    printf("Calling external from B...\n");
    externalFn();
}
当您运行
应用程序时,它会打印:

Calling external function from main app.
External function from the EXTERNAL library.
Calling libA stuff...
Hello from A!
Calling external from A...
Private implementation of external function from A.
Calling libB stuff...
Hello from B!
Calling external from B...
External function from the EXTERNAL library.
Calling externalFn via libB...
External implementation from B
您可以看到,当libb.so调用
externalFn
时,将调用
libexternal.so
中的一个!但是您仍然可以通过dlsym访问libb.so的externalFn()实现

你什么时候会遇到这个问题?在我们的例子中,当我们为Linux提供库时,我们试图使它尽可能自包含,因此如果可以,我们静态链接每个第三方库依赖项。但是仅仅添加libwhater.a将导致库导出libwhater.a中的所有符号 因此,如果消费者应用程序还使用系统预安装的libwhater.So,那么您的库对libwhater符号的符号引用将与已加载的库相链接,而不是静态链接的库。如果两者不同,结果就是崩溃或内存损坏

解决方法是使用链接器脚本来防止导出不需要的符号,以避免混淆动态链接器

但不幸的是,问题并不止于此

LibA的供应商决定在一个插件目录中提供多个库。因此,他们将externalFn()的实现转移到自己的库external2.c中:

#include <stdio.h>

void externalFn()
{
    printf("External function from the EXTERNAL2 library.\n");
}
很明显,liba.c依赖于libexternal2。因此,由于我们针对libexternal2进行了链接,我们甚至设置了RPATH,使链接器在其所在的文件夹中查找它,因此,即使ldd也不显示对libexternal2的引用。因此,只显示libexternal2.so:

$ ldd liba.so
    linux-vdso.so.1 (0x00007fff75870000)
    libexternal2.so => /home/calmarius/stuff/source/linking/plugins/./libexternal2.so (0x00007fd9b9bcd000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd9b97d5000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fd9b9fdd000)
因此,将应用程序更改为从plugins目录加载liba.So。 所以它应该正常工作,对吗?错了!运行应用程序,您将获得以下信息:

Calling external function from main app.
External function from the EXTERNAL library.
Calling libA stuff...
Hello from A!
Calling external from A...
External function from the EXTERNAL library.
Calling libB stuff...
Hello from B!
Calling external from B...
External function from the EXTERNAL library.
Calling externalFn via libB...
External implementation from B
您可以看到,现在甚至连libA都会将链接的应用程序调用到库中,而不是lib链接的应用程序

解决办法是什么?由于glibc 2.3.4(自2004年以来一直存在)有一个选项
RTLD_DEEPBIND
,如果您想避免与已存在的全局符号冲突,则在打开库时必须始终指定此标志。因此,如果我们将标志更改为
RTLD_NOW | RTLD_DEEPBIND
,我们将在运行应用程序时得到预期结果:

Calling external function from main app.
External function from the EXTERNAL library.
Calling libA stuff...
Hello from A!
Calling external from A...
External function from the EXTERNAL2 library.
Calling libB stuff...
Hello from B!
Calling external from B...
External implementation from B
Calling externalFn via libB...
External implementation from B

我遇到过这样的问题,我举了一个例子:

// lib.h
#pragma once

int libfunc();
实现使用名为“myfunc”的函数

// lib.c
#include "lib.h"

#include <stdio.h>

int myfunc()
{
        return printf("lib myfunc()\n");
}

int libfunc()
{
        myfunc();
        return printf("libfunc()\n");
}

可以看出,调用的不是库中的函数,而是已解析的函数,即main.c文件中的函数。

因此,dlopen的行为是否不同于加载时动态链接,如果找到应用程序的符号,将链接到应用程序的符号,而不是库中的符号?这是一个更好的解释!这极大地帮助我调试了一个我遇到的情况!这应该是公认的答案。在不知道警告的情况下,很容易创建动态链接相关的bug,这需要很长时间进行调试。
Calling external function from main app.
External function from the EXTERNAL library.
Calling libA stuff...
Hello from A!
Calling external from A...
External function from the EXTERNAL library.
Calling libB stuff...
Hello from B!
Calling external from B...
External function from the EXTERNAL library.
Calling externalFn via libB...
External implementation from B
Calling external function from main app.
External function from the EXTERNAL library.
Calling libA stuff...
Hello from A!
Calling external from A...
External function from the EXTERNAL2 library.
Calling libB stuff...
Hello from B!
Calling external from B...
External implementation from B
Calling externalFn via libB...
External implementation from B
// lib.h
#pragma once

int libfunc();
// lib.c
#include "lib.h"

#include <stdio.h>

int myfunc()
{
        return printf("lib myfunc()\n");
}

int libfunc()
{
        myfunc();
        return printf("libfunc()\n");
}
// main.c
#include <stdio.h>
#include "lib.h"

int myfunc()
{
        return printf("main myfunc()\n");
}

int main(void)
{
        libfunc();
        return 0;
}
> gcc -shared -fPIC -o liblib.so lib.c
> file liblib.so
liblib.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=a66c7c56b191995df01dbb0a6a94e2358716b369, with debug_info, not stripped
> gcc main.c -o main -L. -llib
> LD_LIBRARY_PATH=. ./main
main myfunc()
libfunc()
>