【发布时间】:2010-09-10 00:28:58
【问题描述】:
为什么 linux 内核会在堆栈溢出时产生段错误?当 c 中的 alloca 或 fortran 创建的临时数组溢出时,这会使调试变得非常尴尬。当然,运行时可能会产生更有用的错误。
【问题讨论】:
为什么 linux 内核会在堆栈溢出时产生段错误?当 c 中的 alloca 或 fortran 创建的临时数组溢出时,这会使调试变得非常尴尬。当然,运行时可能会产生更有用的错误。
【问题讨论】:
“内核”(实际上不是运行您的代码的内核,而是 CPU)不知道您的代码如何引用它不应该接触的内存。它只知道你试图这样做。
代码:
char *x = alloca(100);
char y = x[150];
当您尝试访问超出 x 的边界时,CPU 无法真正评估。
您可以通过以下方式访问完全相同的地址:
char y = *((char*)(0xdeadbeef));
顺便说一句,我不鼓励使用 alloca,因为堆栈往往比堆更受限制(改用 malloc)。
【讨论】:
堆栈溢出是分段错误。就像您打破了最初分配的给定内存界限一样。有限大小的堆栈,而你已经超过了它。您可以在wikipedia了解更多信息
此外,我过去为项目所做的一件事是将我自己的信号处理程序编写到段错误中(查看手册页信号 (2))。我通常会捕捉到信号并向控制台写出“发生致命错误”。我用检查点标志和调试做了一些进一步的事情。
为了调试段错误,您可以在 GDB 中运行程序。例如,以下 C 程序将出现段错误: #segfault.c #包括 #包括
int main()
{
printf("Starting\n");
void *foo=malloc(1000);
memcpy(foo, 0, 100); //this line will segfault
exit(0);
}
如果我这样编译:
gcc -g -o segfault segfault.c
然后像这样运行它:
$ gdb ./segfault
GNU gdb 6.7.1
Copyright (C) 2007 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 "i686-pc-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) run
Starting program: /tmp/segfault
Starting
Program received signal SIGSEGV, Segmentation fault.
0x4ea43cbc in memcpy () from /lib/libc.so.6
(gdb) bt
#0 0x4ea43cbc in memcpy () from /lib/libc.so.6
#1 0x080484cb in main () at segfault.c:8
(gdb)
我从 GDB 发现第 8 行出现了分段错误。当然还有更复杂的方法来处理堆栈溢出和其他内存错误,但这已经足够了。
【讨论】:
只需使用Valgrind。它会以极其精确的方式指出你所有的内存分配错误。
【讨论】:
您实际上可以使用信号处理程序捕获堆栈溢出的条件。
要做到这一点,你必须做两件事:
使用 sigaction 为 SIGSEGV(segfault)设置信号处理程序,为此设置 SO_ONSTACK 标志。这指示内核在传递信号时使用替代堆栈。
调用 sigaltstack() 来设置 SIGSEGV 的处理程序将使用的备用堆栈。
然后,当您溢出堆栈时,内核将在传递信号之前切换到您的备用堆栈。在您的信号处理程序中,您可以检查导致错误的地址并确定它是堆栈溢出还是常规错误。
【讨论】:
堆栈溢出不一定会导致崩溃。它可能会默默地丢弃您程序的数据,但会继续执行。
我不会使用 SIGSEGV 处理程序 kludges,而是解决原始问题。
如果您需要自动帮助,可以使用 gcc 的 -Wstack-protector 选项,它会在运行时发现一些溢出并中止程序。
valgrind 适用于动态内存分配错误,但不适用于堆栈错误。
【讨论】:
一些 cmets 很有帮助,但问题不在于内存分配错误。那就是代码没有错误。在 fortran 中,运行时在堆栈上分配临时值是一件很麻烦的事情。因此,诸如 写(fp)x,y,z 可以触发是没有警告的段错误。英特尔 Fortran 编译器的技术支持人员表示,运行时库无法打印出更有帮助的消息。但是,如果 Miguel 是正确的,那么这应该是可能的,正如他所建议的那样。非常感谢。那么剩下的问题是我如何首先找到段错误的地址,并确定它是来自堆栈溢出还是其他问题。
对于发现此问题的其他人,有一个编译器标志可将超过一定大小的临时变量放在堆上。
【讨论】: