如何找出在 Ubuntu 22.04 上使用 GCC 11 构建时 gcc 无法链接我的库(仅使用 lib 构造函数)的原因?
我在这里设置了一个小例子来尝试复制问题:https://godbolt.org/z/PT4jETToj,问题是我不能 - 在 godbolt 上它可以工作。 该问题仅发生在本地。
库构造函数应该打印
Library constructor called!
,注意它在容器中构建时的表现,但在我的主机上构建时则不然 (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
)
❯ rm -rf main.gcc; docker run -it --rm -v $(pwd):/workspace gcc:11.4 bash -c "cd /workspace; ./build.sh" && ./main.gcc
Building GCC variant
Library constructor called!
Main program started!
❯ rm -rf main.gcc; ./build.sh && ./main.gcc
Building GCC variant
Main program started!
build.sh
运行的地方:
g++ -fPIC -shared -o libexample-gcc.so example.cpp \
&& g++ -std=c++17 -o main.gcc main.cpp -L. -lexample-gcc -Wl,-rpath,.
如果我在 clang 中本地构建它,这也有效。 我开始认为我的系统只是坏了,但我同事的系统(20.04,gcc 9.4)也没有运行库构造函数。
问题似乎出在链接和运行时之间。 当我添加
-Wl,--verbose
标志时,我可以看到链接器对于 gcc 和 clang都看到
libexample
# verbose linker log
attempt to open ./libexample-gcc.so succeeded
./libexample-gcc.so
但是我的主机上生成的 gcc 二进制文件在
libexample
输出中没有显示 ldd
,而当使用 clang 构建时却显示了。 由于它不在链接器列表中,因此在运行时不存在以运行。
该符号在库中定义良好
❯ nm -C libexample-gcc.so | rg library_constructor
0000000000001179 T library_constructor()
但是库在
ldd
中完全缺失:
❯ ( LD_LIBRARY_PATH=$(pwd) ldd main.gcc | rg example ) || echo "not found"
not found
❯ LD_LIBRARY_PATH=$(pwd) ldd main.clang | rg example
libexample-clang.so => /home/matt/workspace/lib_constructor_example/libexample-clang.so (0x00007d0188775000)
即使我明确添加
--no-as-needed
,i.e.,这也是如此
g++ -std=c++17 -o main.gcc main.cpp -L. -lexample-gcc -Wl,-rpath,. -Wl,--no-as-needed
我的环境或系统设置中是否有某些东西正在控制这一点?
链接器不需要您的共享库,因为当链接器在输入序列中考虑它时,
它已累积 0 个对您的库定义的一个符号 library_constructor()
的未解析引用(自
main.o
当然,没有引用它)。您的库在输入时不会解析任何内容,因此不需要。库构造函数通常是一个 static
函数,以免被其他模块引用。我是 确保您计划向库中添加一些函数或数据对象的外部定义以使其有用。 如果您这样做,并且这些符号中的任何一个都在
main.o
或任何其他文件中引用
在您的库之前输入到链接,然后将需要您的库并将其链接
无论您在任何系统上使用什么前端。直到它提供链接所需的任何符号,无论您的库是否链接
取决于是否在幕后生成的样板链接选项
您调用的特定前端在命令行库之前包含 -Wl,-as-needed
是插值的。
当--as-needed
生效时,链接器将认为需要共享库当且仅当 它确实需要解决链接中先前累积的引用问题。当
--as-needed
无效时,链接器将考虑所需的库并链接它,即使它实际上不是
需要解决参考文献。
在调用 library_constructor()
且 ldd
报告动态链接库的情况下,前端 使用中配置不默认
--as-needed
。在不调用 library_constructor()
而调用 ldd
的场景中
不报告您的库,正在使用的前端确实默认
--as-needed
你努力强迫 --no-as-needed
:
g++ -std=c++17 -o main.gcc main.cpp -L. -lexample-gcc -Wl,-rpath,. -Wl,--no-as-needed
--as-needed
/都是 对共享库进行操作随后
输入,直到出现相反的选项。所以 你的
-Wl,--no-as-needed
来得太晚了,因为 -lexample-gcc
已经被输入了。这将是
仅适用于在 g++
的样板添加到命令行中输入的任何共享库,除非
该样板文件会在之后附加 --as-needed
,但在附加任何标准共享库之前(实际上它无法管理)。您成功地将您的图书馆与 clang++
链接到本地是因为您本地的 clang(++)
与 您本地的
gcc\g++
,不默认--as-needed
。我也是这样:$ g++ --version
g++ (Ubuntu 13.2.0-23ubuntu4) 13.2.0
...
$ clang++ --version
Ubuntu clang version 18.1.6 (++20240518023429+1118c2e05e67-1~exp1~20240518143527.144)
$ tail -n +1 main.cpp example.cpp CMakeLists.txt
==> main.cpp <==
#include <iostream>
extern int foo;
auto main() -> int {
std::cout << "Main program started!" << std::endl;
return 0;
}
==> example.cpp <==
#include <iostream>
// Constructor function
static __attribute__((constructor)) void library_constructor() {
std::cout << "Library constructor called!" << std::endl;
}
==> CMakeLists.txt <==
cmake_minimum_required(VERSION 3.21)
project(main-test VERSION 1.0 LANGUAGES CXX)
set(CMAKE_VERBOSE_MAKEFILE TRUE)
set(CXX_STANDARD 17)
add_library(example SHARED)
target_sources(example PRIVATE example.cpp)
add_executable(main)
target_sources(main PRIVATE main.cpp)
target_link_libraries(main PRIVATE example)
$ mkdir build
$ cd build/
$ cmake ..
...
$ make VERBOSE=1 | grep '\-o main'
/usr/bin/c++ CMakeFiles/main.dir/main.cpp.o -o main -Wl,-rpath,/home/imk/develop/so/scrap2/build libexample.so
最后一件事是 g++
链接命令行。让我们重做一下
--verbose
看看
--as-needed
是否在样板文件中:$ /usr/bin/c++ CMakeFiles/main.dir/main.cpp.o -o main --verbose -Wl,-rpath,/home/imk/develop/so/scrap2/build libexample.so 2>&1 | grep -o as-needed; echo Done
as-needed
Done
是的。还有:
$ readelf --dynamic --wide main | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
libexample.so
不是
NEEDED
:
$ ./main
Main program started!
使用 clang++
重做:
$ /usr/bin/clang++ CMakeFiles/main.dir/main.cpp.o -o main --verbose -Wl,-rpath,/home/imk/develop/so/scrap2/build libexample.so 2>&1 | grep -o as-needed; echo Done
Done
--as-needed
。还有:
$ readelf --dynamic --wide main | grep NEEDED
0x0000000000000001 (NEEDED) Shared library: [libexample.so]
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]
libexample.so
。
$ ./main
Library constructor called!
Main program started!