【发布时间】:2020-01-30 00:19:06
【问题描述】:
我刚刚调试了一个奇怪的问题,我有两个库,我们称之为 libA.so 和 libB.so
应用程序 dlopens libA.so(编辑:它不是:它由 -l 选项链接)这是一个瘦库,然后加载 libB.so 这是实际的实现。
使用 RTLD_NOW 选项调用 dlopen,不传递其他选项。
并且两个库都使用相同的记录器模块,其中记录器的状态存储在全局变量中,因为它们都使用相同的记录器并静态链接到它们,它们中的全局变量具有相同的名称。
加载 libB 时,两个全局变量位于同一地址并发生冲突。所以动态链接器重用了变量的地址来使用libB中的同一个变量。
如果这个变量是在 .cpp 文件的深处定义的,我不确定 C 和 C++ 之间的链接是否不同。
阅读dlopen's documentation 上面写着:
RTLD_GLOBAL
此库定义的符号将可用于后续加载的库的符号解析。
RTLD_LOCAL
这是 RTLD_GLOBAL 的反面,如果两个标志都没有指定,则为默认值。此库中定义的符号不可用于解析后续加载的库中的引用。
所以 RTLD_LOCAL 应该是默认值,即在解析 libB 的符号时不应使用 libA 的符号。但它仍在发生。为什么?
作为一种解决方法,我为此全局添加了可见性(“隐藏”)选项以避免导出。并提出了一张票,让所有符号默认隐藏,所以以后不应该发生这样的碰撞,但我仍然想知道为什么会发生这种情况而不应该发生。
编辑2:
来源示例:
commonvar.h:
#pragma once
#include <iostream>
struct A
{
A()
{
std::cout << "A inited. Address: " << this << "\n";
}
virtual ~A() {}
};
extern A object;
struct POD
{
int x, y, z;
};
extern POD pod;
commonvar.cpp:
#include <string>
#include "commonvar.h"
A object;
POD pod = {1, 2, 3};
啊哈:
#pragma once
extern "C" void foo();
a.cpp:
#include <iostream>
#include "commonvar.h"
using FnFoo = void (*)();
extern "C" void foo()
{
std::cout << "A called.\n";
std::cout << "A: Address of foo is: " << &object << "\n";
std::cout << "A: Address of pod is: " << &pod << "\n";
std::cout << "A: {" << pod.x << ", " << pod.y << ", " << pod.z << "}\n";
pod.x = 42;
}
b.cpp:
#include <iostream>
#include <string>
#include "commonvar.h"
extern "C" void foo()
{
std::cout << "B called.\n";
std::cout << "B: Address of foo is: " << &object << "\n";
std::cout << "B: Address of pod is: " << &pod << "\n";
std::cout << "B: {" << pod.x << ", " << pod.y << ", " << pod.z << "}\n";
}
main.cpp:
#include <dlfcn.h>
#include <iostream>
#include <cassert>
#include "a.h"
using FnFoo = void (*)();
int main()
{
std::cout << "Start of program.\n";
foo();
std::cout << "Loading B\n";
void *b = dlopen("libb.so", RTLD_NOW);
assert(b);
FnFoo fnB;
fnB = FnFoo(dlsym(b, "foo"));
assert(fnB);
fnB();
}
构建脚本:
#!/bin/bash
g++ -fPIC -c commonvar.cpp
ar rcs common.a commonvar.o
g++ -fPIC -shared a.cpp common.a -o liba.so
g++ -fPIC -shared b.cpp common.a -o libb.so
g++ main.cpp liba.so -ldl -o main
main的动态符号:
U __assert_fail
0000000000202010 B __bss_start
U __cxa_atexit
w __cxa_finalize
U dlopen
U dlsym
0000000000202010 D _edata
0000000000202138 B _end
0000000000000bc4 T _fini
U foo
w __gmon_start__
0000000000000860 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
0000000000202020 B _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
liba.so 的动态符号:
0000000000202064 B __bss_start
U __cxa_atexit
w __cxa_finalize
0000000000202064 D _edata
0000000000202080 B _end
0000000000000e6c T _fini
0000000000000bba T foo
w __gmon_start__
0000000000000a30 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000202070 B object
0000000000202058 D pod
U _ZdlPvm
0000000000000dca W _ZN1AC1Ev
0000000000000dca W _ZN1AC2Ev
0000000000000e40 W _ZN1AD0Ev
0000000000000e22 W _ZN1AD1Ev
0000000000000e22 W _ZN1AD2Ev
U _ZNSolsEi
U _ZNSolsEPKv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000201dd0 V _ZTI1A
0000000000000ed5 V _ZTS1A
0000000000201db0 V _ZTV1A
U _ZTVN10__cxxabiv117__class_type_infoE
libb.so 的动态符号:
$ nm -D libb.so
0000000000202064 B __bss_start
U __cxa_atexit
w __cxa_finalize
0000000000202064 D _edata
0000000000202080 B _end
0000000000000e60 T _fini
0000000000000bba T foo
w __gmon_start__
0000000000000a30 T _init
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000202070 B object
0000000000202058 D pod
U _ZdlPvm
0000000000000dbe W _ZN1AC1Ev
0000000000000dbe W _ZN1AC2Ev
0000000000000e34 W _ZN1AD0Ev
0000000000000e16 W _ZN1AD1Ev
0000000000000e16 W _ZN1AD2Ev
U _ZNSolsEi
U _ZNSolsEPKv
U _ZNSt8ios_base4InitC1Ev
U _ZNSt8ios_base4InitD1Ev
U _ZSt4cout
U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
0000000000201dd0 V _ZTI1A
0000000000000ec9 V _ZTS1A
0000000000201db0 V _ZTV1A
U _ZTVN10__cxxabiv117__class_type_infoE
输出:
A inited. Address: 0x7efd6cf97070
Start of program.
A called.
A: Address of foo is: 0x7efd6cf97070
A: Address of pod is: 0x7efd6cf97058
A: {1, 2, 3}
Loading B
A inited. Address: 0x7efd6cf97070
B called.
B: Address of foo is: 0x7efd6cf97070
B: Address of pod is: 0x7efd6cf97058
B: {42, 2, 3}
可以看出变量的地址会发生冲突,但函数的地址不会。
此外,C++ 初始化是特殊的:聚合 pod 变量仅在您看到对 foo() 的调用修改它时才被初始化,但是当 B 被加载时,它不会重新初始化它,而是调用构造函数加载 libb.so 时的完整对象。
【问题讨论】:
-
您确定主应用程序没有使用相同的符号吗?
-
@n.m.是的……但有趣的是,当我将变量包装在
boost::synchronized_value中以确保线程安全时,问题就开始出现了。在此之前,它曾经是一个普通的结构。 C++ 的初始化东西在这里也能起作用吗? -
好吧,看起来你的两个库都在使用来自可执行位的僵硬,我不知道它什么时候发生,什么时候没有。我现在有一个悬而未决的问题stackoverflow.com/questions/58174403/…
-
用源示例编辑了问题。
标签: linux gcc shared-libraries dynamic-linking