在 Linux GCC 下,抛出任何 C++ 异常都会终止静态构建中的程序
Throwing any C++ exception terminates program in static build under Linux GCC
我需要在 Linux 下修复一个巨大的商业 C++ 项目。它是使用 GCC(特别是 g++
)构建的,在公司中定制编写的构建系统下。它是在 Raspberry Pi 4B.运行 下建造的。
当它是动态构建的(没有 -static
标志)时,一切正常并且程序 运行s 正确并给出正确的结果。
但是当它是静态构建时(使用 -static
标志)然后程序会在任何第一个异常时崩溃。为了检查程序是静态的,我 运行 ldd ./prog
并且它显示 not a dynamic executable
。如果对动态构建执行相同的 ldd 命令,那么它会显示一堆动态库。
我决定让整个构建配置完全相同,但在主文件中只是用以下简单代码替换了 main() 函数:
int main() {
std::cout << "Before exc" << std::endl;
try {
throw std::runtime_error("Hello, World!");
} catch (std::exception const & ex) {
std::cout << "Thrown '" << ex.what() << "'." << std::endl;
}
std::cout << "After exc" << std::endl;
}
如果上面带有 main() 的整个项目是动态构建的,那么它会正确显示以下内容,如预期的那样:
Before exc
Thrown 'Hello, World!'.
After exc
如果我静态构建完全相同的东西,那么输出是这样的:
Before exc
terminate called after throwing an instance of 'std::runtime_error'
terminate called recursively
Aborted
换句话说,消息 Before exc
被显示,然后异常被抛出,但不是捕获它而是调用 terminate 并且程序中止。
如果使用相同的构建系统,我构建的不是整个项目,而是使用上面的代码静态构建单个文件 main.cpp
,那么它会输出预期的内容。所以这里的问题是所有项目文件和 linked 库,而不是构建系统(可能)。
可能项目的某些全局变量在初始化时修改了程序,抛出异常导致终止。但是所有全局变量都被正确初始化,因为 main() 运行s 正确地直到抛出异常的点。全局变量仅使静态构建异常不友好。
有谁知道发生了什么以及 C++ 异常调用何时在抛出点终止?是否存在任何程序配置可以设置异常抛出调用std::terminate()
?
其他一切正常,只有例外情况。换句话说,静态程序 运行 可以正确运行一段时间并执行正确的操作,但会在第一个异常时崩溃。
使用的构建命令在下面,所有的名字都是故意修改的,也只剩下2-3个目标文件和库文件,在真实的例子中有几十个相同的.a
库和几十个.o
对象。首先是将 .cpp
编译为 .o
的编译命令,第二个是将 .o
聚合为 .a
的归档命令,最后是 link 命令 link将所有 .o
和 .a
文件放入最终的单文件程序中:
"ccache" "g++" "-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-std=c++2a" "-fpic" "-c" "-static" "-static-libgcc" "-static-libstdc++" "-Iinc_dir/" "-I." "-g" "-march=armv6zk+fp" "-ffast-math" "-xc++" "-O0" "-DSOME_DEFINE" "-omisc.o" "misc.cpp"
"ar" "rs" "lib1.a" "lib1.o"
"ccache" "g++" "-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-std=c++2a" "-fpic" "-static" "-static-libgcc" "-static-libstdc++" "-oprog" "main.o" "misc.o" "lib1.a" "lib2.a" "-L./lib_dir/" "-lboost_program_options" "-lboost_system" "-lpthread" "-lc" "-lgcc" "-ldl" "-lstdc++" "-lstdc++fs"
通过二进制搜索删除部分项目,才发现原因在 misc.cpp
中。不知何故,如果我将它包含在构建中,那么异常会崩溃,如果我不包含,那么异常不会崩溃。
据我了解,misc.cpp
可以使异常崩溃的唯一原因仅是由于 main.cpp
中的某些全局变量对程序进行了此类设置,使异常可崩溃。提醒一下,我将 throw.cpp
包含在抛出玩具异常的项目中,如上面的代码所示。从 throw.cpp
开始,misc.cpp
根本没有被使用。此外 main.cpp
不包含在构建中。基本上我只留下了项目中的 misc.cpp
文件,只有它导致了错误。项目构建配置的所有其余部分只是不会导致崩溃的外部库。
现在我做了一个最小的可复制示例。现在我的项目由 throw.cpp
和 misc.cpp
加上一堆外部 linked 库组成。这个 throw.cpp
只包含 main() 和我在问题开头提到的抛出玩具代码,它没有别的,除了 iostream
和 stdexcept
之外不包含任何东西。 misc.cpp
没有main(),根本没有使用misc.cpp
,只是编译和linked。所以现在,如果我从 linking 中删除 misc.cpp
,那么异常将在 throw.cpp
的 main() 中正确抛出,没有错误。如果我 link misc.cpp
(我根本不使用,只是 link)然后在 throw.cpp
的 main() 中抛出异常会使程序崩溃,仅在静态 link.
经过 6 个小时的问题搜索,我设法找出原因不在项目文件/库/构建系统中。
看来问题出在将任何 C++ 代码与异常和 C 代码与 sscanf()(甚至可能是任何 C 标准库函数)的使用相结合。
这可能是由于 Raspberry Pi 4B 在默认安装中的 GCC/G++ 版本太旧,g++ -v
产生:
Target: arm-linux-gnueabihf
...
Thread model: posix
gcc version 8.3.0 (Raspbian 8.3.0-6+rpi1)
所以版本是 8.3.0,而 GCC 已经有 11.2 版了。
我创建了最小的崩溃示例:
文件z.cpp
:
#include <stdexcept>
#include <iostream>
int z2_f();
int main() {
std::cout << "Before exc" << std::endl;
try {
throw std::runtime_error("Hello, World!");
} catch (std::exception const & ex) {
std::cout << "Thrown '" << ex.what() << "'." << std::endl;
} catch (...) {
std::cout << "Thrown 'Unknown'." << std::endl;
}
std::cout << "After exc" << std::endl;
std::cout << z2_f() << std::endl;
}
文件z2.cpp
:
#include <cstdio>
int z2_f() {
char s[] = "123";
int x = 0;
sscanf(s, "%d", &x);
return x;
}
编译命令:
"g++" "-std=c++2a" "-c" "-static" "-O0" "-oz.o" "z.cpp"
"g++" "-std=c++2a" "-c" "-static" "-O0" "-oz2.o" "z2.cpp"
"g++" "-std=c++2a" "-static" "-oprog.exe" "z.o" "z2.o"
编译后 运行 prog.exe
它在控制台中显示:
Before exc
terminate called after throwing an instance of 'std::runtime_error'
terminate called recursively
Aborted
如果只编译并运行第一个文件 z.cpp
(删除了对 z2_f()
的使用),那么一切都会按预期运行而不会崩溃:
Before exc
Thrown 'Hello, World!'.
After exc
我根据页面的 this tutorial (WayBack mirror 安装 GCC/G++ 版本 10.1.0
设法解决了这个问题。基本上可以通过 git clone https://bitbucket.org/sol_prog/raspberry-pi-gcc-binary.git --depth=1
.
获得 GCC 二进制作者发布的版本
上面这个版本的玩具例子运行没有崩溃,原来的大项目也运行没有崩溃。
提醒一下,在我使用 8.3.0
版本的 GCC 之前,它因异常而崩溃。
我的 uname -a
显示:
Linux raspberrypi 5.4.51-v7l+ #1333 SMP Mon Aug 10 16:51:40 BST 2020 armv7l GNU/Linux
而 lsb_release -d
显示:
Raspbian GNU/Linux 10 (buster)
我需要在 Linux 下修复一个巨大的商业 C++ 项目。它是使用 GCC(特别是 g++
)构建的,在公司中定制编写的构建系统下。它是在 Raspberry Pi 4B.运行 下建造的。
当它是动态构建的(没有 -static
标志)时,一切正常并且程序 运行s 正确并给出正确的结果。
但是当它是静态构建时(使用 -static
标志)然后程序会在任何第一个异常时崩溃。为了检查程序是静态的,我 运行 ldd ./prog
并且它显示 not a dynamic executable
。如果对动态构建执行相同的 ldd 命令,那么它会显示一堆动态库。
我决定让整个构建配置完全相同,但在主文件中只是用以下简单代码替换了 main() 函数:
int main() {
std::cout << "Before exc" << std::endl;
try {
throw std::runtime_error("Hello, World!");
} catch (std::exception const & ex) {
std::cout << "Thrown '" << ex.what() << "'." << std::endl;
}
std::cout << "After exc" << std::endl;
}
如果上面带有 main() 的整个项目是动态构建的,那么它会正确显示以下内容,如预期的那样:
Before exc
Thrown 'Hello, World!'.
After exc
如果我静态构建完全相同的东西,那么输出是这样的:
Before exc
terminate called after throwing an instance of 'std::runtime_error'
terminate called recursively
Aborted
换句话说,消息 Before exc
被显示,然后异常被抛出,但不是捕获它而是调用 terminate 并且程序中止。
如果使用相同的构建系统,我构建的不是整个项目,而是使用上面的代码静态构建单个文件 main.cpp
,那么它会输出预期的内容。所以这里的问题是所有项目文件和 linked 库,而不是构建系统(可能)。
可能项目的某些全局变量在初始化时修改了程序,抛出异常导致终止。但是所有全局变量都被正确初始化,因为 main() 运行s 正确地直到抛出异常的点。全局变量仅使静态构建异常不友好。
有谁知道发生了什么以及 C++ 异常调用何时在抛出点终止?是否存在任何程序配置可以设置异常抛出调用std::terminate()
?
其他一切正常,只有例外情况。换句话说,静态程序 运行 可以正确运行一段时间并执行正确的操作,但会在第一个异常时崩溃。
使用的构建命令在下面,所有的名字都是故意修改的,也只剩下2-3个目标文件和库文件,在真实的例子中有几十个相同的.a
库和几十个.o
对象。首先是将 .cpp
编译为 .o
的编译命令,第二个是将 .o
聚合为 .a
的归档命令,最后是 link 命令 link将所有 .o
和 .a
文件放入最终的单文件程序中:
"ccache" "g++" "-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-std=c++2a" "-fpic" "-c" "-static" "-static-libgcc" "-static-libstdc++" "-Iinc_dir/" "-I." "-g" "-march=armv6zk+fp" "-ffast-math" "-xc++" "-O0" "-DSOME_DEFINE" "-omisc.o" "misc.cpp"
"ar" "rs" "lib1.a" "lib1.o"
"ccache" "g++" "-finput-charset=UTF-8" "-fexec-charset=UTF-8" "-std=c++2a" "-fpic" "-static" "-static-libgcc" "-static-libstdc++" "-oprog" "main.o" "misc.o" "lib1.a" "lib2.a" "-L./lib_dir/" "-lboost_program_options" "-lboost_system" "-lpthread" "-lc" "-lgcc" "-ldl" "-lstdc++" "-lstdc++fs"
通过二进制搜索删除部分项目,才发现原因在 misc.cpp
中。不知何故,如果我将它包含在构建中,那么异常会崩溃,如果我不包含,那么异常不会崩溃。
据我了解,misc.cpp
可以使异常崩溃的唯一原因仅是由于 main.cpp
中的某些全局变量对程序进行了此类设置,使异常可崩溃。提醒一下,我将 throw.cpp
包含在抛出玩具异常的项目中,如上面的代码所示。从 throw.cpp
开始,misc.cpp
根本没有被使用。此外 main.cpp
不包含在构建中。基本上我只留下了项目中的 misc.cpp
文件,只有它导致了错误。项目构建配置的所有其余部分只是不会导致崩溃的外部库。
现在我做了一个最小的可复制示例。现在我的项目由 throw.cpp
和 misc.cpp
加上一堆外部 linked 库组成。这个 throw.cpp
只包含 main() 和我在问题开头提到的抛出玩具代码,它没有别的,除了 iostream
和 stdexcept
之外不包含任何东西。 misc.cpp
没有main(),根本没有使用misc.cpp
,只是编译和linked。所以现在,如果我从 linking 中删除 misc.cpp
,那么异常将在 throw.cpp
的 main() 中正确抛出,没有错误。如果我 link misc.cpp
(我根本不使用,只是 link)然后在 throw.cpp
的 main() 中抛出异常会使程序崩溃,仅在静态 link.
经过 6 个小时的问题搜索,我设法找出原因不在项目文件/库/构建系统中。
看来问题出在将任何 C++ 代码与异常和 C 代码与 sscanf()(甚至可能是任何 C 标准库函数)的使用相结合。
这可能是由于 Raspberry Pi 4B 在默认安装中的 GCC/G++ 版本太旧,g++ -v
产生:
Target: arm-linux-gnueabihf
...
Thread model: posix
gcc version 8.3.0 (Raspbian 8.3.0-6+rpi1)
所以版本是 8.3.0,而 GCC 已经有 11.2 版了。
我创建了最小的崩溃示例:
文件z.cpp
:
#include <stdexcept>
#include <iostream>
int z2_f();
int main() {
std::cout << "Before exc" << std::endl;
try {
throw std::runtime_error("Hello, World!");
} catch (std::exception const & ex) {
std::cout << "Thrown '" << ex.what() << "'." << std::endl;
} catch (...) {
std::cout << "Thrown 'Unknown'." << std::endl;
}
std::cout << "After exc" << std::endl;
std::cout << z2_f() << std::endl;
}
文件z2.cpp
:
#include <cstdio>
int z2_f() {
char s[] = "123";
int x = 0;
sscanf(s, "%d", &x);
return x;
}
编译命令:
"g++" "-std=c++2a" "-c" "-static" "-O0" "-oz.o" "z.cpp"
"g++" "-std=c++2a" "-c" "-static" "-O0" "-oz2.o" "z2.cpp"
"g++" "-std=c++2a" "-static" "-oprog.exe" "z.o" "z2.o"
编译后 运行 prog.exe
它在控制台中显示:
Before exc
terminate called after throwing an instance of 'std::runtime_error'
terminate called recursively
Aborted
如果只编译并运行第一个文件 z.cpp
(删除了对 z2_f()
的使用),那么一切都会按预期运行而不会崩溃:
Before exc
Thrown 'Hello, World!'.
After exc
我根据页面的 this tutorial (WayBack mirror 安装 GCC/G++ 版本 10.1.0
设法解决了这个问题。基本上可以通过 git clone https://bitbucket.org/sol_prog/raspberry-pi-gcc-binary.git --depth=1
.
上面这个版本的玩具例子运行没有崩溃,原来的大项目也运行没有崩溃。
提醒一下,在我使用 8.3.0
版本的 GCC 之前,它因异常而崩溃。
我的 uname -a
显示:
Linux raspberrypi 5.4.51-v7l+ #1333 SMP Mon Aug 10 16:51:40 BST 2020 armv7l GNU/Linux
而 lsb_release -d
显示:
Raspbian GNU/Linux 10 (buster)