【问题标题】:Why doesn't SIGSEGV crash the process?为什么 SIGSEGV 不会使进程崩溃?
【发布时间】:2019-04-05 10:00:12
【问题描述】:

我正在尝试实现 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() 时,进程正确崩溃,并创建转储。

那么有什么区别呢?我怎样才能跨平台拥有相同的行为?

【问题讨论】:

  • 优化器可能会用任何内容替换该函数的主体。
  • 就像我说的,当使用 gdb 运行相同的可执行文件时。它捕获 SIGSEGV。当我在没有调试器的情况下运行它时,什么也没有发生。即使在调试器中捕获 SIGSEGV 也不会导致进程崩溃。为什么?

标签: c++ exception mingw


【解决方案1】:

这里是一个最小控制台程序的源代码,它尝试 取消引用空指针

ma​​in.c

#include <stdio.h>

int shoot_my_foot() {
    int* x = 0;
    return *x;
}

int main()
{
    int i = shoot_my_foot();
    printf("%d\n",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 虚拟机,并使用 微软 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-W64 gcc 或女士cl。没有什么可以归咎于 Windows 的事实 与 Linux 不同。

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

事实上,该程序确实在我们的三个 编译场景,我们可以通过给程序一个处理程序来观察 信号:

ma​​in_1.c

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <assert.h>

static void handler(int sig)
{
    assert(sig == SIGSEGV);
    fputs("Caught SIGSEGV\n", 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("%d\n",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 上,使用 MS cl

>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:\develop\so\scrap\prog.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.

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

我怎样才能在不同平台上拥有相同的行为?

在实践中是最好的:-

您可以在程序中处理信号,并将信号处理程序限制在 在您的首选范围内,跨平台行为相同的代码 含义相同

而且这个答案在实践中只是尽可能好,因为在原则中, 根据语言标准,您不能依赖导致 undefined 的操作 行为 发出任何特定信号,或产生任何特定甚至一致的结果。 如果您的目标是实现一致的跨平台处理 致命信号,然后调用适当的函数来触发信号sig 进行测试 目的由标准头 &lt;signal.h&gt; 提供(在 C++ 中, &lt;csignal&gt;):

int raise( int sig )

向程序发送信号sig

【讨论】:

    【解决方案2】:

    您的代码在

    中有未定义的行为
    *x = 1;
    

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

    如果您想发出SIGSEGV 的信号,请执行此操作,但不要使用可能导致您的代码执行任何操作的未定义行为。你不应该期望你的代码有任何输出,而是修复它;)。

    【讨论】:

    • 问题是为什么 SIGSEGV、SIGFPE 会在 Linux 上使进程崩溃,但在 Windows 上却不会。我怎么能有同样的行为?实现崩溃转储报告器的目标是从诸如此类的错误中获取崩溃报告,以便我可以修复它们,而不是忽略它们。
    • @AdrianSuciu 好吧,是的,我试图向您解释,没有可靠的方法可以从未定义的行为中获取崩溃报告,如果您想防止取消引用空指针,您需要先检查一下之后
    猜你喜欢
    • 1970-01-01
    • 2011-06-11
    • 2012-04-20
    • 1970-01-01
    • 2012-01-29
    • 1970-01-01
    • 1970-01-01
    • 2017-03-26
    • 2017-04-01
    相关资源
    最近更新 更多