首页 > 解决方案 > GCC:用于静态链接到 pthread 的 --whole-archive 配方在最近的 gcc 版本中停止工作

问题描述

针对 pthread 的静态链接在 Linux 上是一个困难的话题。它曾经可以包装-lpthread-Wl,--whole-archive -lpthread -Wl,--no-whole-archive(详细信息可以在这个答案中找到)。

效果是符号(对于 pthread)是强的,而不是弱的。由于在 Ubuntu 18.04 左右(在 gcc 5.4.0 和 gcc 7.4.0 之间),这种行为似乎发生了变化,并且 pthread 符号现在总是以独立于--whole-archive选项的弱符号结束。

正因为如此,-whole-archive配方停止工作。我的问题的目的是了解工具链(编译器、链接器、标准库)中最近发生了什么变化,以及可以做些什么来恢复旧的行为。

例子:

#include <mutex>

int main(int argc, char **argv) {
  std::mutex mutex;
  mutex.lock();
  mutex.unlock();
  return 0;
}

在以下所有示例中,都使用了相同的编译命令:

g++ -std=c++11 -Wall -static simple.cpp  -Wl,--whole-archive -lpthread  -Wl,--no-whole-archive

以前,使用 编译时-static,pthread 符号(例如pthread_mutex_lock)是强符号(标记为Tnm,但现在它们是弱符号( W):

Ubuntu 14.04:docker run --rm -it ubuntu:14.04 bash

$ apt-get update
$ apt-get install g++

$ g++ --version
g++ (Ubuntu 4.8.4-2ubuntu1~14.04.4) 4.8.4

$ nm a.out | grep pthread_mutex_lock
0000000000408160 T __pthread_mutex_lock
00000000004003e0 t __pthread_mutex_lock_full
0000000000408160 T pthread_mutex_lock

Ubuntu 16.04:docker run --rm -it ubuntu:16.04 bash

$ g++ --version
g++ (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609

$ nm a.out | grep pthread_mutex_lock
00000000004077b0 T __pthread_mutex_lock
0000000000407170 t __pthread_mutex_lock_full
00000000004077b0 T pthread_mutex_lock

Ubuntu 18.04:docker run --rm -it ubuntu:18.04 bash

$ g++ --version
g++ (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0

$ nm ./a.out  | grep pthread_mutex_lock
0000000000407010 T __pthread_mutex_lock
00000000004069d0 t __pthread_mutex_lock_full
0000000000407010 W pthread_mutex_lock

把它们加起来:

在更复杂的示例中,这可能导致分段错误。例如,在这段代码中(未修改的文件可以在这里找到):

#include <pthread.h>
#include <thread>
#include <cstring>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex mutex;

void myfunc(int i) {
    mutex.lock();
    std::cout << i << " " << std::this_thread::get_id() << std::endl << std::flush;
    mutex.unlock();
}

int main(int argc, char **argv) {
    std::cout << "main " << std::this_thread::get_id() << std::endl;
    std::vector<std::thread> threads;
    unsigned int nthreads;

    if (argc > 1) {
        nthreads = std::strtoll(argv[1], NULL, 0);
    } else {
        nthreads = 1;
    }

    for (unsigned int i = 0; i < nthreads; ++i) {
        threads.push_back(std::thread(myfunc, i));
    }
    for (auto& thread : threads) {
        thread.join();
    }
}

尝试生成静态二进制文件失败,例如:

$ g++ thread_get_id.cpp -Wall -std=c++11 -O3 -static -pthread -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
$ ./a.out
Segmentation fault (core dumped)

我试图 drop -O3、switching to clang++、切换到 Gold 链接器等。但它总是崩溃。据我了解,静态二进制文件崩溃的原因是基本函数(例如pthread_mutex_lock)最终不会成为强符号。因此,它们在最终的二进制文件中丢失,导致运行时错误。

除了 Ubuntu 18.04,我还可以使用 gcc 10.0.0 在 Arch Linux 上重现相同的行为。但是,在 Ubuntu 14.04 和 16.04 上,可以创建和执行静态二进制文件而不会出现任何错误。

问题:

标签: c++gccpthreadsstatic-linking

解决方案


新的解决方法:-Wl,--whole-archive -lrt -lpthread -Wl,--no-whole-archive


正如 Federico 所指出的,添加-lrt可以防止崩溃。整个问题几乎肯定与实时扩展库 librt 有关。它的计时函数(例如 , clock_gettimeclock_nanosleep用于实现线程。

在 Ubuntu 16.04 和 18.04 之间,glibc 中也有与这些功能相关的变化。我无法弄清楚细节,但代码中有注释:

/* clock_nanosleep 在 2.17 版本中移至 libc;旧的二进制文件可能需要它在 librt 中的符号版本。*/

同样对于较新的提交消息:

提交 79a547b162657b3fa34d31917cc29f0e7af19e4c
作者:Adhemerval Zanella
日期:2019 年 11 月 5 日星期二 19:59:36 +0000

nptl:将 nanosleep 实现移动到 libc

检查 x86_64-linux-gnu 和 powerpc64le-linux-gnu。我还检查了每个受影响的 ABI 的 libpthread.so .gnu.version_d 条目,它们都包含所需的版本(包括导出具有不同版本的 __nanosleep 的架构)。

总结一下,解决方法是添加-lrt. 请注意,在某些示例中(不在此处),排序是相关的。从 gcc 中的测试和其他一些讨论中,我得到的印象是,首先链接 librt 导致的问题比在 pthread 之后链接要少。(在一个例子中,-lpthread -lrt -lpthread似乎只是有效,但不清楚为什么。)


推荐阅读