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


151

如何确定导致分段错误的代码中的错误

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


5
没有gcc / gdb不能。你可以找出其中的段错误发生,但实际的错误可能是在一个完全不同的位置。

Answers:


218

GCC无法做到这一点,但是GDB(调试器)肯定可以做到。使用-g开关编译程序,如下所示:

gcc program.c -g

然后使用gdb:

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

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

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


28
请注意,发生段错误的地方通常仅是代码中“导致错误的地方”的线索。一个重要的线索,但不一定是问题所在。
mpez0 2010年

9
您也可以使用(bt full)获取更多详细信息。
ant2009年


2
使用bt作为一个速记backtrace
rustyx

43

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

valgrind --leak-check=full <program>

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


2
+1,Valgrind更快/更容易发现内存错误。在带有调试符号的非优化构建中,它可以准确告诉您发生段错误的原因以及原因。
蒂姆·波斯特

1
可悲的是,使用-g -O0编译并与valgrind结合使用时,我的段错误消失了。
JohnMudd

2
--leak-check=full将无助于调​​试段错误。仅对调试内存泄漏有用。
ks1322'9

@JohnMudd我有一个段错误仅出现在测试的输入文件的1%左右,如果您重复失败的输入,它将不会失败。我的问题是由多线程引起的。到目前为止,我还没有弄清楚导致此问题的代码行。我现在使用重试掩盖此问题。如果使用-g选项,故障将消失!
周克敏

18

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

每当您收到消息时:

 Segmentation fault (core dumped)

一个核心文件被写入当前目录。您可以使用以下命令进行检查

 gdb your_program core_file

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

确保系统未将核心转储文件大小设置为零。您可以使用以下方法将其设置为无限制:

ulimit -c unlimited

小心点!核心转储会变得巨大。


我最近切换到arch-linux。我当前的目录不包含核心转储文件。我该如何生成?
Abhinav

您不会生成它;Linux确实如此。核心转储存储在不同的Linuces上的不同位置-围绕Google。对于Arch Linux的,请阅读本wiki.archlinux.org/index.php/Core_dump
Mawg说起用莫妮卡

7

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

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

根据文档,这些检查默认情况下包括捕获分段错误。这样做的好处是,您可以获得类似于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 ++二进制文件完成)。稍后的步骤将由于多线程而仅随机触发分段错误。很难调试。此选项至少触发了调试信息输出,这至少为我提供了进行代码审查以查找错误位置的起点。
Kemin Zhou

2

卢卡斯关于核心转储的答案是好的。在我的.cshrc中,我有:

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

通过输入“ core”来显示回溯。和日期戳,以确保我在寻找正确的文件:(。

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


4
并且不要忘记ulimit -c unlimited首先启用核心转储。
詹姆斯·莫里斯

@詹姆斯:对。卢卡斯已经提到了这一点。对于仍然停留在csh中的我们,请使用'limit'。而且我从来没有能够阅读CYGWIN stackdumps(但是我已经有2到3年没有尝试过了)。
约瑟夫·昆西

2

以上所有答案均正确无误,值得推荐;如果上述方法均不能使用,则此答案仅作为最后解决方法。

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

这很繁琐,但是它具有可以在任何地方工作的优势-唯一的可能不是,如果您由于某种原因无法访问stdout或stderr,或者如果您要解决的错误是一场比赛,条件,其行为随程序时间的变化而改变(因为调试打印会降低程序的速度并更改其时间)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.