如何确定代码中导致 段错误 的错误位置?
我的编译器(gcc
)是否能显示程序中的故障位置?
如何确定代码中导致 段错误 的错误位置?
我的编译器(gcc
)是否能显示程序中的故障位置?
此外,您可以尝试使用valgrind
:如果您安装了valgrind
并运行
valgrind --leak-check=full <program>
然后它将运行您的程序,并显示任何段错误、无效内存读取或写入以及内存泄漏的堆栈跟踪。这非常有用。
--leak-check=full
无法帮助调试段错误。它只有在调试内存泄漏时才有用。 - ks1322有许多工具可用于帮助调试分段错误,我想将我的最爱工具添加到列表中:Address Sanitizers(通常缩写为ASAN)。
现代¹编译器配备了方便的-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+提供的功能。
-g
标志进行编译。 Segmentation fault (core dumped)
一个核心文件会被写入到你当前的目录中。你可以使用命令检查它:
gdb your_program core_file
当程序崩溃时,文件包含内存状态。核心转储在软件部署期间可能很有用。
确保系统不将核心转储文件大小设置为零。您可以使用以下命令将其设置为无限:
ulimit -c unlimited
需要注意的是!核心转储可能会变得非常大。
gdb --core=core
。 - Juliaulimit -c
命令来检查当前状态,要查看更多信息,请使用 ulimit -a
命令。 - Alexis Wilkefprintf(stderr, "CHECKPOINT REACHED @ %s:%i\n", __FILE__, __LINE__);
),并重新编译程序。然后运行该程序,并观察在崩溃发生之前最后一个打印的调试信息 - 您知道程序已经执行到那里,因此崩溃肯定发生在该点之后。添加或删除调试打印语句,重新编译并再次运行测试,直到缩小到仅有一行代码。此时,您可以修复该错误并删除所有临时调试打印语句。Lucas关于核心转储的回答很好。在我的.cshrc文件中,我有以下内容:
alias core 'ls -lt core; echo where | gdb -core=core -silent; echo "\n"'
通过输入“core”来显示回溯信息。同时,为了确保我正在查看正确的文件,需要查看日期戳 :(。
补充:如果存在堆栈损坏漏洞,则应用于核心转储的回溯信息通常是无用的。在这种情况下,在gdb中运行程序可以得到更好的结果,如接受的答案所述(假设故障很容易重现)。还要注意多个进程同时转储核心的情况;一些操作系统将PID添加到核心文件名中。
ulimit -c unlimited
来启用核心转储。 - James Morris-O0 -g -Wall -fcheck=all -fbacktrace
这是一种粗略的方法,用于找到发生分段错误后的确切行。
#include \<iostream>
void log(int line) {
std::cout << line << std::endl;
}
查找并替换所有log函数后面的分号为"; log(_LINE_);"
确保在for (;;)循环中替换为函数的分号已被删除
如果您有一个可重现的异常,比如分段错误,您可以使用调试器等工具来重现错误。
我曾经为了找到甚至是不可重现的错误的源代码位置而苦恼。这是基于微软编译器工具链的想法。
步骤6和7很有益处,因为您只需要代码行。但我建议您要意识到这一点。
希望您能在您的平台上获得与GCC编译器类似的环境。如果您没有可用的MAP文件,请使用工具链工具获取函数的地址。我相信ELF文件格式支持此功能。