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

时间:2014-02-25 03:44:58

标签: c++ shared-libraries symbols

应用程序( app )取决于两个共享库: liba.so libb.so
liba libb 具有与 void Hello()相同的功能,但具有不同的实现。 在运行时加载两个共享库并尝试访问两个版本的Hello() 我通过poco C ++共享库加载liba.so和libb.so,但最终它调用 dlopen()来加载共享库。这是代码:

#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()实现是否会出现任何符号冲突?
事实上,代码进展顺利,但我想知道是否有任何潜在的风险来加载这样的库。

3 个答案:

答案 0 :(得分:3)

  

我的问题是,当app通过dlopen()加载liba.so和libb.so时,两个Hello()实现会不会有任何符号冲突?

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

即使是dlsym函数也不能混淆,因为你传递了dlopen函数返回的句柄,所以它不会变得含糊不清。

(这甚至不会成为同一个库中重载的问题)

答案 1 :(得分:1)

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

使用dlopen加载库时,可以使用dlsym访问其中的所有符号,这些符号将是该库中的正确符号,并且不会污染全局符号空间(除非您使用了RTLD_GLOBAL)。 但是即使库本身定义了符号,也可以使用已经加载的全局符号来解决其依赖性。

考虑将第三方库称为libexternal.so,external.c:

#include <stdio.h>

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

然后考虑liba.so,它在不知不觉中私下实现了一个(注意,表示内部链接的static关键字)。 liba.c:

#include <stdio.h>

static void externalFn()
{
    printf("Private implementation of external function from A.\n");
}

void hello()
{
    printf("Hello from A!\n");
    printf("Calling external from A...\n");
    externalFn();
}

然后考虑libb.so,它会未知地实现一个并将其导出libb.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();
}

然后,与libexternal.so链接的主应用程序会动态加载上述两个库,并在其中调用main.c:

#include <stdio.h>
#include <dlfcn.h>

void externalFn();

int main()
{
    printf("Calling external function from main app.\n");
    externalFn();

    printf("Calling libA stuff...\n");
    void *lib = dlopen("liba.so", RTLD_NOW);
    void (*hello)();
    hello = dlsym(lib, "hello");
    hello();

    printf("Calling libB stuff...\n");
    void *libB = dlopen("libb.so", RTLD_NOW);
    void (*helloB)();
    helloB = dlsym(libB, "hello");
    helloB();

    printf("Calling externalFn via libB...\n");
    void (*externalB)() = dlsym(libB, "externalFn");
    externalB();

    return 0;
}

构建命令为:

#!/bin/bash

echo "Building External..."
gcc external.c -shared -fPIC -o libexternal.so

echo "Building LibA..."
gcc liba.c -shared -fPIC -o liba.so

echo "Building LibB..."
gcc libb.c -shared -fPIC -o libb.so

echo "Building App..."
gcc main.c libexternal.so -ldl -Wl,-rpath,\$ORIGIN -o app

当您运行app时,它会打印:

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-ing来访问libb.so的externalFn()实现。

什么时候可以遇到这个问题?在我们为Linux发行库的情况下,我们尝试使其尽可能独立,因此,如果可以,我们将静态链接每个第三方库依赖项。但是仅添加libwhatever.a将导致您的库导出libwhatever.a中的所有符号 因此,如果消费者应用程序还使用系统预先安装的libwhatever.so,则您的库对libwhatever的符号的符号引用将链接到已加载的库,而不是静态链接的库。如果两者不同,结果将导致崩溃或内存损坏。

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

但是不幸的是,问题并没有就此结束。

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

#include <stdio.h>

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

然后,构建脚本将更改以构建新的外部库,并将所有内容移至plugins目录:

#!/bin/bash

echo "Building External..."
gcc external.c -shared -fPIC -o libexternal.so

echo "Building External2..."
gcc external2.c -shared -fPIC -o libexternal2.so

echo "Building LibA..."
gcc liba.c libexternal2.so -shared -fPIC -Wl,-rpath,\$ORIGIN,--disable-new-dtags -o liba.so

echo "Building LibB..."
gcc libb.c -shared -fPIC -o libb.so

echo "Installing plugin"
mkdir -p plugins
mv liba.so plugins/
mv libexternal2.so plugins/

echo "Building App..."
gcc main.c libexternal.so -ldl -Wl,-rpath,\$ORIGIN,--disable-new-dtags -o app

很明显,liba.c依赖于libexternal2.so,因为我们对其进行了链接,我们甚至设置了RPATH以使链接器在它所在的文件夹中寻找它,因此,即使ldd也不显示它对libexternal.so的引用。全部,只有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,如果要避免与已经存在的全局符号冲突,则在dlopen-libs时必须始终需要指定此标志。因此,如果将标志更改为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

答案 2 :(得分:0)

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

// 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");
}

这是从定义另一个“myfunc”函数的代码的主要部分调用的

// 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()
>

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