堆栈溢出时出现段错误

时间:2020-03-05 18:58:18  来源:igfitidea点击:

为什么Linux内核在堆栈溢出时生成段错误?当c或者alltran中的临时数组创建溢出时,这会使调试变得很尴尬。当然,运行时可能会产生更有用的错误。

解决方案

回答

"内核"(实际上不是运行代码的内核,是CPU)不知道代码如何引用不应该接触的内存。它只知道我们尝试这样做。

代码:

char *x = alloca(100);
char y = x[150];

当我们尝试访问x之外时,CPU不能真正对其进行评估。

我们可以通过以下方式输入完全相同的地址:

char y = *((char*)(0xdeadbeef));

顺便说一句,我不鼓励使用alloca,因为堆栈比堆更受限制(请使用malloc代替)。

回答

堆栈溢出是分段错误。就像我们打破了最初分配给给定内存范围一样。有限大小的堆栈,我们已经超过了它。我们可以在Wikipedia上了解更多有关它的信息。

另外,我过去为项目所做的一件事是将自己的信号处理程序编写为segfault(请参见手册页信号(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处理程序错误,而是要解决原始问题。

如果需要自动帮助,可以使用gcc的-Wstack-protector选项,该选项将在运行时发现一些溢出并中止程序。

valgrind适用于动态内存分配错误,但不适用于堆栈错误。

回答

一些评论是有帮助的,但是问题不在于内存分配错误。那是没有错误的代码。在fortran中,运行时会在堆栈上分配临时值,这很麻烦。因此,例如
写(fp)x,y,z
可以触发没有错误的段错误。对Intel Fortran编译器的技术支持说,运行时库无法打印出更有用的消息。但是,如果米格尔(Miguel)是对的,那么他将建议这样做。非常感谢。剩下的问题是我该如何首先找到段错误的地址,并找出该错误是否来自堆栈溢出或者其他问题。

对于其他发现此问题的人,有一个编译器标志,该标志将临时变量放在堆上一定大小以上的位置。