GCC: --whole-archive 静态链接到 pthread 的配方在最近的 gcc 版本中停止工作
GCC: --whole-archive recipe for static linking to pthread stopped working in recent gcc versions
针对 pthread 的静态链接是 Linux 上的一个难题。它曾经用于将 -lpthread
包装为 -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
(详细信息可以在 answer 中找到)。
结果是符号(对于 pthread)是 strong, not weak。从大约 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
)很强(被 nm
标记为 T
),但现在它们很弱(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
总结一下:
- Ubuntu 14.04 和 16.04:
T pthread_mutex_lock
(强符号)
- Ubuntu 18.04:
W pthread_mutex_lock
(弱符号)
在更复杂的示例中,这可能会导致分段错误。比如这段代码中(未修改的文件可以在here中找到):
#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)
我尝试删除 -O3
、切换到 clang++
、切换到 Gold 链接器等。但它总是崩溃。根据我的理解,静态二进制文件崩溃的原因是基本函数(例如 pthread_mutex_lock
)没有以强符号结束。因此,它们在最终二进制文件中丢失,导致运行时错误。
除了 Ubuntu 18.04,我还可以使用 gcc 10.0.0 在 Arch Linux 上重现相同的行为。
但是,在 Ubuntu 14.04 和 16.04 上,可以毫无错误地创建和执行静态二进制文件。
问题:
- 构建工具链发生了什么变化(在 gcc 5.4.0 和 gcc 7.4.0 之间)? (胡乱猜测:我当时看到一个 pthread cleanup for C11 掉下来了。也许这就是原因?)
- 这是回归,还是旧的解决方法不再正确。
- 如果没有回归,应该怎么做才能允许静态链接到 pthread?
新解决方法:-Wl,--whole-archive -lrt -lpthread -Wl,--no-whole-archive
正如 Federico 所指出的,添加 -lrt
可以防止崩溃。整个问题几乎肯定与实时扩展库 librt 有关。它的计时函数(例如,clock_gettime
、clock_nanosleep
)用于实现线程。
在 Ubuntu 16.04 和 18.04 之间,与这些功能相关的 glibc 也发生了变化。想不通细节,代码里有注释:
/* clock_nanosleep moved to libc in version 2.17;
old binaries may expect the symbol version it had in librt. */
还有更新的提交消息:
commit 79a547b162657b3fa34d31917cc29f0e7af19e4c
Author: Adhemerval Zanella
Date: Tue Nov 5 19:59:36 2019 +0000
nptl: Move nanosleep implementation to libc
Checked on x86_64-linux-gnu and powerpc64le-linux-gnu. I also checked
the libpthread.so .gnu.version_d entries for every ABI affected and
all of them contains the required versions (including for architectures
which exports __nanosleep with a different version).
总而言之,解决方法是添加 -lrt
。请注意,在某些示例中(不在此处),顺序是相关的。从 gcc 中的测试和其他一些讨论中,我得到的印象是首先链接 librt 比在 pthread 之后链接导致的问题更少。 (在一个示例中,似乎只有 -lpthread -lrt -lpthread
有效,但不清楚原因。)
针对 pthread 的静态链接是 Linux 上的一个难题。它曾经用于将 -lpthread
包装为 -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
(详细信息可以在 answer 中找到)。
结果是符号(对于 pthread)是 strong, not weak。从大约 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
)很强(被 nm
标记为 T
),但现在它们很弱(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
总结一下:
- Ubuntu 14.04 和 16.04:
T pthread_mutex_lock
(强符号) - Ubuntu 18.04:
W pthread_mutex_lock
(弱符号)
在更复杂的示例中,这可能会导致分段错误。比如这段代码中(未修改的文件可以在here中找到):
#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)
我尝试删除 -O3
、切换到 clang++
、切换到 Gold 链接器等。但它总是崩溃。根据我的理解,静态二进制文件崩溃的原因是基本函数(例如 pthread_mutex_lock
)没有以强符号结束。因此,它们在最终二进制文件中丢失,导致运行时错误。
除了 Ubuntu 18.04,我还可以使用 gcc 10.0.0 在 Arch Linux 上重现相同的行为。 但是,在 Ubuntu 14.04 和 16.04 上,可以毫无错误地创建和执行静态二进制文件。
问题:
- 构建工具链发生了什么变化(在 gcc 5.4.0 和 gcc 7.4.0 之间)? (胡乱猜测:我当时看到一个 pthread cleanup for C11 掉下来了。也许这就是原因?)
- 这是回归,还是旧的解决方法不再正确。
- 如果没有回归,应该怎么做才能允许静态链接到 pthread?
新解决方法:-Wl,--whole-archive -lrt -lpthread -Wl,--no-whole-archive
正如 Federico 所指出的,添加 -lrt
可以防止崩溃。整个问题几乎肯定与实时扩展库 librt 有关。它的计时函数(例如,clock_gettime
、clock_nanosleep
)用于实现线程。
在 Ubuntu 16.04 和 18.04 之间,与这些功能相关的 glibc 也发生了变化。想不通细节,代码里有注释:
/* clock_nanosleep moved to libc in version 2.17; old binaries may expect the symbol version it had in librt. */
还有更新的提交消息:
commit 79a547b162657b3fa34d31917cc29f0e7af19e4c
Author: Adhemerval Zanella
Date: Tue Nov 5 19:59:36 2019 +0000nptl: Move nanosleep implementation to libc
Checked on x86_64-linux-gnu and powerpc64le-linux-gnu. I also checked the libpthread.so .gnu.version_d entries for every ABI affected and all of them contains the required versions (including for architectures which exports __nanosleep with a different version).
总而言之,解决方法是添加 -lrt
。请注意,在某些示例中(不在此处),顺序是相关的。从 gcc 中的测试和其他一些讨论中,我得到的印象是首先链接 librt 比在 pthread 之后链接导致的问题更少。 (在一个示例中,似乎只有 -lpthread -lrt -lpthread
有效,但不清楚原因。)