确定导致分段错误的代码行?

Posted

技术标签:

【中文标题】确定导致分段错误的代码行?【英文标题】:Determine the line of code that causes a segmentation fault? 【发布时间】:2011-02-22 00:17:09 【问题描述】:

如何确定导致segmentation fault 的代码中的错误在哪里?

我的编译器 (gcc) 可以显示程序中故障的位置吗?

【问题讨论】:

没有 gcc/gdb 不能。您可以找出 哪里 发生段错误,但实际错误可能在完全不同的位置。 【参考方案1】:

GCC 不能这样做,但 GDB(debugger)肯定可以。使用-g 开关编译您的程序,如下所示:

gcc program.c -g

然后使用gdb:

$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>

Here 是一个很好的教程,可以帮助您开始使用 GDB。

段错误发生的位置通常只是关于“导致错误”在代码中的位置的线索。给定位置不一定是问题所在。

【讨论】:

请注意,段错误发生的位置通常只是关于“导致错误”在代码中的位置的线索。一个重要的线索,但不一定是问题所在。 您也可以使用 ( bt full ) 获取更多详细信息。 我觉得这很有用:gnu.org/software/gcc/bugs/segfault.html 使用bt 作为backtrace 的简写。【参考方案2】:

您也可以使用核心转储,然后使用 gdb 对其进行检查。要获得有用的信息,您还需要使用-g 标志进行编译。

每当您收到消息时:

 Segmentation fault (core dumped)

一个核心文件被写入您的当前目录。你可以用命令检查它

 gdb your_program core_file

该文件包含程序崩溃时的内存状态。在部署软件期间,核心转储可能很有用。

确保您的系统没有将核心转储文件大小设置为零。您可以将其设置为无限制:

ulimit -c unlimited

不过要小心!核心转储可能会变得巨大。

【讨论】:

我最近切换到了arch-linux。我的当前目录不包含核心转储文件。如何生成它? 你不生成它; Linux 可以。核心转储存储在不同的 Linux 上的不同位置 - 谷歌周围。对于 Arch Linux,请阅读wiki.archlinux.org/index.php/Core_dump 我不得不使用gdb --core=core 您可以使用ulimit -c查看当前状态,查看更多使用ulimit -a【参考方案3】:

Lucas 关于核心转储的回答很好。在我的 .cshrc 我有:

alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'

通过输入'core'来显示回溯。还有日期戳,以确保我正在查看正确的文件:(。

添加:如果存在堆栈损坏错误,则应用于核心转储的回溯通常是垃圾。在这种情况下,根据接受的答案,在 gdb 中运行程序可以获得更好的结果(假设故障很容易重现)。还要注意多个进程同时转储核心;某些操作系统会将 PID 添加到核心文件的名称中。

【讨论】:

别忘了ulimit -c unlimited首先启用核心转储。 @James:正确。卢卡斯已经提到了这一点。对于我们这些仍然停留在 csh 中的人,请使用“limit”。而且我从来没有能够阅读 CYGWIN 堆栈转储(但我已经有 2 或 3 年没有尝试过了)。【参考方案4】:

另外,你可以试试valgrind:如果你安装valgrind并运行

valgrind --leak-check=full <program>

然后它将运行您的程序并显示任何段错误的堆栈跟踪,以及任何无效的内存读取或写入和内存泄漏。真的很好用。

【讨论】:

+1 ,Valgrind 更快/更容易用于发现内存错误。在带有调试符号的非优化构建中,它会准确地告诉您发生段错误的位置以及原因。 遗憾的是,当使用 -g -O0 编译并与 valgrind 结合时,我的段错误消失了。 --leak-check=full 将无助于调​​试段错误。它仅对调试内存泄漏有用。 @JohnMudd 我有一个段错误只出现在大约 1% 的输入文件测试中,如果你重复失败的输入它不会失败。我的问题是由多线程引起的。到目前为止,我还没有弄清楚导致这个问题的代码行。我现在正在使用重试来掩盖这个问题。如果使用 -g 选项,故障就会消失!【参考方案5】:

有许多工具可以帮助调试分段错误,我想将我最喜欢的工具添加到列表中:Address Sanitizers(通常缩写为 ASAN)

现代¹ 编译器带有方便的 -fsanitize=address 标志,增加了一些编译时间和运行时间开销,从而进行更多的错误检查。

根据the documentation,这些检查包括默认捕获分段错误。此处的优点是您获得了类似于 gdb 输出的堆栈跟踪,但无需在调试器中运行程序。一个例子:

int main() 
  volatile int *ptr = (int*)0;
  *ptr = 0;

$ gcc -g -fsanitize=address main.c
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4848==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5654348db1a0 bp 0x7ffc05e39240 sp 0x7ffc05e39230 T0)
==4848==The signal is caused by a WRITE memory access.
==4848==Hint: address points to the zero page.
    #0 0x5654348db19f in main /tmp/tmp.s3gwjqb8zT/main.c:3
    #1 0x7f0e5a052b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)
    #2 0x5654348db099 in _start (/tmp/tmp.s3gwjqb8zT/a.out+0x1099)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/tmp.s3gwjqb8zT/main.c:3 in main
==4848==ABORTING

输出比 gdb 的输出稍微复杂一些,但也有好处:

无需重现问题即可接收堆栈跟踪。只需在开发过程中启用标志就足够了。

ASAN 捕获的不仅仅是分段错误。即使进程可以访问该内存区域,也会捕获许多越界访问。


¹ 那是Clang 3.1+ 和GCC 4.8+。

【讨论】:

这对我最有帮助。我有一个非常微妙的错误,它以大约 1% 的频率随机发生。我处理大量输入文件(16 个主要步骤;每个步骤由不同的 C 或 C++ 二进制文件完成)。由于多线程,稍后的步骤将仅随机触发分段错误。很难调试。这个选项触发了调试信息输出,至少它给了我一个代码审查的起点,以找到错误的位置。【参考方案6】:

以上所有答案均正确且值得推荐;如果上述方法均不可用,则此答案仅作为最后的手段。

如果所有其他方法都失败了,您始终可以使用各种临时调试打印语句(例如fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);)重新编译您的程序,这些语句散布在您认为是代码的相关部分的各处。然后运行程序,并观察在崩溃发生之前最后打印的调试打印内容——你知道你的程序已经走到了那一步,所以崩溃一定是在那之后发生的。添加或删除调试打印,重新编译并再次运行测试,直到您将其缩小到一行代码。此时您可以修复错误并删除所有临时调试打印。

这很乏味,但它的优势在于几乎可以在任何地方工作——唯一的可能是如果您由于某种原因无法访问 stdout 或 stderr,或者您正在尝试修复的错误是一种竞争条件,其行为会随着程序时间的变化而变化(因为调试打印会减慢程序并改变其时间)

【讨论】:

以上是关于确定导致分段错误的代码行?的主要内容,如果未能解决你的问题,请参考以下文章

这段代码一次执行良好,另一次出现分段错误

带有 ifort 的 tracebackqq() 导致分段错误

为啥释放内存会导致分段错误?

为啥重新声明 std::cout 会导致分段错误?

启动线程导致指针初始化时出现分段错误

为啥我的字符串分配会导致分段错误?