关于linux:堆栈上的segfault溢出

关于linux:堆栈上的segfault溢出

Segfault on stack overflow

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


您实际上可以使用信号处理程序来捕获堆栈溢出的条件。

为此,您必须做两件事:

  • 使用sigaction为SIGSEGV(segfault)设置信号处理程序,为此设置SO_ONSTACK标志。这指示内核在传递信号时使用备用堆栈。

  • 调用sigaltstack()设置SIGSEGV的处理程序将使用的备用堆栈。

然后,当您使堆栈溢出时,内核将在传递信号之前切换到备用堆栈。进入信号处理程序后,您可以检查导致错误的地址,并确定是堆栈溢出还是常规错误。


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

代码:

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

当您尝试访问x的边界之外时,

不能真正被CPU评估。

您可能会使用以下相同的地址:

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

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


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

上阅读有关此内容的更多信息。

此外,我过去对项目所做的一件事是将自己的信号处理程序编写到segfault(请参见手册页信号(2))。我通常会捕获信号,然后在控制台上写出"发生致命错误"。我做了其他一些检查点标记和调试的工作。

为了调试段错误,您可以在GDB中运行一个程序。例如,以下C程序将出现段错误:
#segfault.c
#包括
#include

1
2
3
4
5
6
7
8
int main()
{
        printf("Starting\
");
        void *foo=malloc(1000);
        memcpy(foo, 0, 100); //this line will segfault
        exit(0);
}

如果我这样编译它:

1
gcc -g -o segfault segfault.c

,然后像这样运行它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ 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。它会以极高的精确度指出您所有的内存分配错误。


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

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


堆栈溢出不一定会导致崩溃。它可能会悄无声息地破坏程序的数据,但会继续执行。

我不会使用SIGSEGV处理程序错误,而是要解决原始问题。

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

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


推荐阅读