C++ 使用libpthread在共享库中未定义行为,但在ELF中没有将其作为依赖项

C++ 使用libpthread在共享库中未定义行为,但在ELF中没有将其作为依赖项,c++,linux,gcc,pthreads,gold-linker,C++,Linux,Gcc,Pthreads,Gold Linker,当链接“正确”(进一步解释)时,下面的两个函数调用将无限期地阻塞pthread调用,实现cv.notify\u one和cv.wait\u for: // let's call it odr.cpp, which forms libodr.so std::mutex mtx; std::condition_variable cv; bool ready = false; void Notify() { std::chrono::milliseconds(100); std::uniq

当链接“正确”(进一步解释)时,下面的两个函数调用将无限期地阻塞pthread调用,实现
cv.notify\u one
cv.wait\u for

// let's call it odr.cpp, which forms libodr.so

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void Notify() {
  std::chrono::milliseconds(100);
  std::unique_lock<std::mutex> lock(mtx);
  ready = true;
  cv.notify_one();
}

void Get() {
  std::unique_lock<std::mutex> lock(mtx);
  cv.wait_for(lock, std::chrono::milliseconds(300));
}
另一方面,对于以下任何一种情况,我们都不会遇到错误:

  • 叮当声++
  • bfd连接器
  • 没有明确的
    -lpthread
  • -lpthread
    但根据需要使用
    -Wl,--no
注:这一次,我们有:

  • NOTYPE
    和no
    libpthread.so
    dependency
  • libpthread.so
    依赖性
如图所示:

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    10: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __pthread_key_create
$ clang++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    24: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __pthread_key_create@GLIBC_2.2.5 (7)

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=bfd -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    14: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __pthread_key_create

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out  0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    18: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __pthread_key_create

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold -Wl,--no-as-needed -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    10: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __pthread_key_create@GLIBC_2.2.5 (4)
可在此处找到编译/运行的完整示例:


\u pthread\u key\u create
时,使用pthread中断shlib的是
弱的
,并且没有
libpthread。因此可以在ELF中找到
依赖项?动态链接器是否从
libc.so
(存根)获取pthread符号而不是
libpthread.so

这里发生了很多事情:gcc和clang之间的差异,gnu ld和gold之间的差异,
--根据需要
链接器标志,两种不同的故障模式,甚至可能还有一些计时问题

让我们从如何使用POSIX线程链接程序开始

编译器的
-pthread
标志就是您所需要的。它是一个编译器标志,所以在编译使用线程的代码和链接最终可执行文件时都应该使用它。在链接步骤上使用
-pthread
时,编译器将自动在链接行的正确位置提供
-lpthread
标志

通常,仅在链接最终可执行文件时使用,而在链接共享库时不使用。如果您只是想使库线程安全,但又不想强制使用库的每个程序都与pthreads链接,那么您需要使用运行时检查来查看pthreads库是否已加载,并且仅当pthread库已加载时才调用pthread api。在Linux上,这通常是通过检查“金丝雀”来完成的——例如,对任意符号进行弱引用,如
\uuupthread\ukey\ucreate
,该符号仅在加载库时定义,如果程序链接时没有它,则值为0

但是,在您的例子中,您的库
libodr.so
很大程度上取决于线程,因此将其与
-pthread
标志链接是合理的

这将我们带到第一个失败模式:如果对两个链接步骤都使用g++和gold,程序将抛出
std::system\u error
,并说需要启用多线程。这是由于
--as needed
标志造成的。默认情况下,GCC会根据需要将
--传递给链接器,而clang(显然)不会。使用
--根据需要
,链接器将只记录解析强引用的库依赖项。由于对pthread API的所有引用都很弱,因此它们都不足以告诉链接器应该将libpthread.so添加到依赖项列表中(通过动态表中的
dtu NEEDED
条目)。更改为clang或添加
-Wl,--no-as-needed
标志可解决此问题,程序将加载pthread库

但是,等等,为什么在使用Gnu链接器时不需要这样做呢?它使用相同的规则:只有强引用才会将库记录为依赖项。区别在于GNULD还考虑来自其他共享库的引用,而gold只考虑来自常规对象文件的引用。事实证明,pthread库提供了几个libc符号的覆盖定义,并且有来自
libstdc++的强引用。因此
引用了其中一些符号(例如,
write
)。这些强引用足以让GNULD将
libpthread.so
记录为依赖项。这与其说是设计,不如说是意外;我认为改变黄金来考虑其他共享库的引用实际上是一个有力的解决办法。我认为正确的解决方案是,当您使用
-pthread
时,GCC将
-no-as-needed
放在
-lpthread
标志前面

这就引出了一个问题:为什么在使用POSIX线程和黄金链接器时,这个问题没有一直出现。但这是一个小测试程序;一个更大的程序几乎肯定会包含对那些
libpthread.so
重写的libc符号的强引用

现在让我们看看第二种故障模式,如果您使用g++、gold和
-lpthread链接
libodr.so
,则
Notify()
Get()
都会无限期阻塞

Notify()
中,在调用
cv.Notify\u one()
时,在函数结束时一直保持锁。您只需按住锁即可设置就绪标志;如果我们更改它以便在此之前释放锁,那么调用
Get()
的线程将在300毫秒后超时,并且不会阻塞。因此,实际上是对
notify_one()
的调用阻塞了,程序处于死锁状态,因为
Get()
正在等待同一个锁

那么,为什么只有当
\uu pthread\u key\u create
FUNC
而不是
NOTYPE
时它才会阻塞呢?我认为符号的类型是一种误导,而真正的问题是由于gold没有记录符号版本以供未添加为所需库的库解决的引用。
wait\u for
的实现调用了
pthread\u cond\u timedwait
,它在
libpthread
libc
中都有两个版本。可能是加载程序将引用绑定到错误的版本,导致无法解锁互斥锁而导致死锁。我对gold做了一个临时补丁来记录这些版本,这使程序能够正常工作。不幸的是,
$ clang++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    24: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __pthread_key_create@GLIBC_2.2.5 (7)

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=bfd -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    14: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __pthread_key_create

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out  0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    18: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __pthread_key_create

$ g++ -fPIC -shared -o build/libodr.so build/odr.cpp.o -fuse-ld=gold -Wl,--no-as-needed -lpthread && readelf -d build/libodr.so | grep Shared && readelf -Ws build/libodr.so | grep -m1 __pthread_key_create && ./a.out 
 0x0000000000000001 (NEEDED)             Shared library: [libpthread.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libm.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
    10: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __pthread_key_create@GLIBC_2.2.5 (4)