为什么 SIGSEGV 不会使进程崩溃?

Why doesn't SIGSEGV crash the process?

本文关键字:进程 崩溃 SIGSEGV 为什么      更新时间:2023-10-16

我正在尝试实现breakpad来获取我们的跨平台Qt应用程序的崩溃报告和堆栈跟踪。我想我实现了所有必要的代码,但我无法让应用程序在 Windows 上可靠地崩溃。

我使用MinGW gcc编译器和Qt。

我在 UI 中创建了一个按钮。

void crash() {
printf(NULL);
int* x = 0;
*x = 1;
int a = 1/0;
}
/* .... */
connect(ui->btnCrash, SIGNAL(clicked()),this,SLOT(crash()));

单击按钮时,实际上没有任何反应。但是,在调试模式下运行时,调试器 (gdb) 会在第一次函数调用时检测 SIGSEGV,然后放弃运行该方法的其余部分。我注意到在代码中的其他地方故意做非法事情时也有同样的行为。这会导致意外/未定义的行为。

现在,此行为与 Linux 不同,在 Linux 中,当调用此 crash() 时,进程会正确崩溃,并创建转储。

那么有什么区别呢?如何跨平台具有相同的行为?

这是一个尝试的最小控制台程序的来源 取消引用空指针

主.c

#include <stdio.h>
int shoot_my_foot() {
int* x = 0;
return *x;
}
int main()
{
int i = shoot_my_foot();
printf("%dn",i);
return 0;
}

我将在(Ubuntu 18.04)Linux上编译并运行它:

$ gcc -Wall -Wextra -o prog main.c
$ ./prog
Segmentation fault (core dumped)

系统返回代码是什么?

$ echo $?
139

当程序因致命信号而被终止时,Linux 会将 128 + 信号号码返回给调用方。所以 那是 128 + 11,即 128 +SIGSEGV.

这就是在 Linux 上,当程序试图取消引用空指针时发生的情况。 这就是Linux对行为不端的程序所做的:它杀死了它并返回了我们 128 +SIGSEGV.这不是程序所做的:它不处理任何信号。

现在,我将跳入Windows 10 VM,并使用 Microsoft C 编译器:

>cl /Feprog /W4 main.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
main.c
Microsoft (R) Incremental Linker Version 14.11.25547.0
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:prog.exe
main.obj
>prog
>

无。所以程序崩溃了,并且:

>echo %errorlevel%
-1073741819

系统返回码-1073741819,即有符号整数值0xc0000005,著名的Windows错误代码,表示访问冲突

仍然在Windows中,我现在将使用GCC编译并运行该程序:

>gcc --version
gcc (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 7.2.0
>gcc -Wall -Wextra -o prog.exe main.c
>prog
>echo %errorlevel%
-1073741819

和以前一样,程序崩溃了,系统代码0xc0000005.

从顶部再来一次:

>gcc -Wall -Wextra -o prog.exe main.c
>prog
>echo %errorlevel%
-1073741819

没有变化。

这就是在Windows上,当程序尝试取消引用空指针时发生的情况。 这就是Windows对行为不端的程序所做的:它会杀死它并返回我们0xc0000005.

对于行为不端的 C 程序,我们没有什么可以感谢的 Windows对它做同样的事情,无论我们是否使用它编译MinGW-W64gcc或 MScl.没有什么可以归咎于Windows的事实 它与Linux不做同样的事情。

事实上,我们甚至无法感谢同样的事情发生了 到行为不端的程序,使用 GCC 编译,两次都是在我们刚刚运行它的时候。因为 C (或C++)标准不承诺取消引用空指针会导致SIGSEGV(或者除以 0 将导致SIGFPE,依此类推)。它只是承诺 此操作会导致未定义的行为,包括可能导致SIGSEGV当程序在gdb下运行时,在星期二,否则不运行。

事实上,该程序确实在我们的所有三个方面都引起了SIGSEGV编译场景,正如我们可以通过为程序提供一个处理程序来观察的那样 信号:

main_1.c

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <assert.h>
static void handler(int sig)
{
assert(sig == SIGSEGV);
fputs("Caught SIGSEGVn", stderr);
exit(128 + SIGSEGV);
}
int shoot_my_foot(void) {
int* x = 0;
return *x;
}
int main(void)
{
int i;
signal(SIGSEGV, handler);
i = shoot_my_foot();
printf("%dn",i);
return 0;
}

在 Linux 上:

$ gcc -Wall -Wextra -o prog main_1.c
$ ./prog
Caught SIGSEGV
$ echo $?
139

在Windows上,使用MinGW-W64gcc':

>gcc -Wall -Wextra -o prog.exe main_1.c
>prog
Caught SIGSEGV
>echo %errorlevel%
139

在 Windows 上,使用 MScl

>cl /Feprog /W4 main_1.c
Microsoft (R) C/C++ Optimizing Compiler Version 19.11.25547 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.
main_1.c
Microsoft (R) Incremental Linker Version 14.11.25547.0
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:prog.exe
main_1.obj
>prog
Caught SIGSEGV
>echo %errorlevel%
139

这种一致的行为不同于我们在gdb下的原始程序:

>gcc -Wall -Wextra -g -o prog.exe main.c
>gdb -ex run prog.exe
GNU gdb (GDB) 8.0.1
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from prog.exe...done.
Starting program: C:developsoscrapprog.exe
[New Thread 6084.0x1e98]
[New Thread 6084.0x27b8]
Thread 1 received signal SIGSEGV, Segmentation fault.
0x0000000000401584 in shoot_my_foot () at main.c:5
5               return *x;
(gdb)

原因是默认情况下gdb会安装 所有致命信号及其SIGSEGV处理程序的行为是输出 像:

Thread 1 received signal SIGSEGV, Segmentation fault.
0x0000000000401584 in shoot_my_foot () at main.c:5
5               return *x;

并下降到gdb提示符,这与我们安装的SIGSEGV处理程序的行为不同main_1.c.

所以你有一个问题的答案:

如何跨平台具有相同的行为?

在实践中是最好的:-

您可以在程序中处理信号,并将信号处理程序限制为 其行为在你喜欢的跨平台内相同的代码意思相同

而这个答案在实践中是最好的,因为原则上, 根据语言标准,您不能依赖于导致未定义的操作 引发任何特定信号的行为,或产生任何特定甚至一致的结果。 如果实际上您的目标是实现一致的跨平台处理 致命信号,然后调用适当的函数以引发测试的信号sig目的由标准标头<signal.h>提供(C++,<csignal>):

int raise( int sig )

向程序发送信号sig

您的代码在

*x = 1;

因为您不得取消引用空指针。实际上,我不太确定除以零,但是一旦你脱离了轨道,无论如何所有的赌注都会消失。

如果你想发出SIGSEGV信号,那就这样做,但不要使用可能导致代码做任何事情的未定义行为。你不应该期望你的代码有任何输出,而应该;)修复它。