我们如何确定代码中导致segmentation fault?
的错误在编写了一些代码后,为了确定我的分段错误,我的编译器(gcc
)能否显示我的程序中的错误位置?
答案 0 :(得分:173)
-g
开关编译您的程序,如下所示:
gcc program.c -g
然后使用gdb:
$ gdb ./a.out
(gdb) run
<segfault happens here>
(gdb) backtrace
<offending code is shown here>
Here是一个很好的教程,可以帮助您开始使用GDB。
答案 1 :(得分:36)
此外,您可以尝试Valgrind:如果您安装Valgrind并运行valgrind --leak-check = full,那么它将运行您的程序并显示任何段错误的堆栈跟踪,以及任何无效的内存读取或写入和内存泄漏。这非常有用。
答案 2 :(得分:15)
您还可以使用核心转储,然后使用gdb进行检查。要获得有用的信息,还需要使用-g
标志进行编译。
每当收到消息时:
Segmentation fault (core dumped)
将核心文件写入当前目录。您可以使用命令
检查它 gdb your_program core_file
该文件包含程序崩溃时的内存状态。在部署软件期间,核心转储可能很有用。
确保您的系统未将核心转储文件大小设置为零。您可以使用以下内容将其设置为无限制:
ulimit -c unlimited
虽然小心!核心转储可能变得巨大。
答案 3 :(得分:2)
卢卡斯关于核心转储的答案很好。在我的.cshrc中我有:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
通过输入'core'来显示回溯。和日期戳,以确保我正在查看正确的文件:(。
已添加:如果存在堆栈损坏错误,则应用于核心转储的回溯通常是垃圾。在这种情况下,根据接受的答案(假设故障很容易重现),在 gdb 中运行程序可以提供更好的结果。并且要注意同时倾倒核心的多个进程;某些操作系统将PID添加到核心文件的名称中。
答案 4 :(得分:2)
有许多可用的工具可以帮助调试分段错误,我想将我最喜欢的工具添加到列表中:地址消毒器(通常缩写为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不仅捕获分段错误,还捕获了更多内容。即使该进程可以访问该内存区域,也会捕获许多超出范围的访问。
答案 5 :(得分:2)
以上所有答案均正确无误,建议使用;如果上述方法均不能使用,则此答案仅作为最后解决方法。
如果所有其他方法均失败,则始终可以使用各种临时调试打印语句(例如fprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
)重新编译程序,这些语句遍布您认为与代码相关的部分。然后运行该程序,观察崩溃发生前最后的调试打印内容-您知道程序已经走了那么远,因此崩溃肯定在那之后发生了。添加或删除调试打印,重新编译,然后再次运行测试,直到将测试范围缩小到一行代码为止。此时,您可以修复该错误并删除所有临时调试打印。
这很繁琐,但是它具有可以在任何地方工作的优势-唯一的可能是,如果您由于某种原因无法访问stdout或stderr,或者您正尝试修复该错误,是一种竞争条件,其行为随程序时间的变化而改变(因为调试打印会降低程序速度并更改其时间)