如何在c / c ++程序中检测可能/潜在的堆栈溢出问题?

时间:2008-10-14 01:48:53

标签: c++ c memory stack overflow

是否有一种标准方法可以查看您的应用有多少堆栈空间以及在运行期间堆栈使用的最高水印是多少?

另外在可怕的实际溢出情况下会发生什么?

是否崩溃,触发异常或发出信号?所有系统和编译器都有标准或不同吗?

我正在寻找专门针对Windows,Linux和Macintosh的产品。

9 个答案:

答案 0 :(得分:15)

Windows 上,将生成堆栈溢出异常

以下Windows代码说明了这一点:

#include <stdio.h>
#include <windows.h>

void StackOverFlow()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  // this will eventually overflow the stack
  StackOverFlow();
}

DWORD ExceptionFilter(EXCEPTION_POINTERS *pointers, DWORD dwException)
{
  return EXCEPTION_EXECUTE_HANDLER;
}

void main()
{
  CONTEXT context;

  // we are interested control registers
  context.ContextFlags = CONTEXT_CONTROL;

  // get the details
  GetThreadContext(GetCurrentThread(), &context);

  // print the stack pointer
  printf("Esp: %X\n", context.Esp);

  __try
  {
    // cause a stack overflow
    StackOverFlow();
  }
  __except(ExceptionFilter(GetExceptionInformation(), GetExceptionCode()))
  {
    printf("\n****** ExceptionFilter fired ******\n");
  }
}

运行此exe时,将生成以下输出:

Esp: 12FC4C
Esp: 12F96C
Esp: 12F68C
.....
Esp: 33D8C
Esp: 33AAC
Esp: 337CC

****** ExceptionFilter fired ******

答案 1 :(得分:12)

在Linux上,如果您的代码尝试写入堆栈,则会出现分段错误。

堆栈的大小是进程之间继承的属性。如果您可以使用ulimit -sshkshzsh)或limit stacksizetcsh等命令在shell中阅读或修改它},zsh)。

从程序中,可以使用

读取堆栈的大小
#include <sys/resource.h>
#include <stdio.h>

struct rlimit l;
getrlimit(RLIMIT_STACK, &l);
printf("stack_size = %d\n", l.rlim_cur);

我不知道获得可用堆栈大小的标准方法。

堆栈以argc开头,后跟argv的内容和环境的副本,然后是变量。但是因为内核可以随机化堆栈开始的位置,并且可能存在argc以上的一些虚拟值,所以假设在{{1}以下有l.rlim_cur字节可能是错误的。 }。

检索堆栈确切位置的一种方法是查看文件&argc(其中/proc/1234/maps是程序的进程ID)。一旦了解了这些边界,就可以通过查看最新局部变量的地址来计算堆栈的使用量。

答案 2 :(得分:10)

gcc在“不安全”函数调用中的返回地址和正常变量之间放置了一个额外的内存块,就像(在这个例子中函数是void test(){char a [10]; b [20]}:

call stack:
-----------
return address
dummy
char b[10]
char a[20]

如果函数在指针'a'中写入36个字节,则溢出将“损坏”返回地址(可能的安全漏洞)。但是它也会改变'dummy'的值,即指针和返回地址之间的值,所以程序会崩溃并发出警告(你可以用-fno-stack-protector禁用它)

答案 3 :(得分:7)

在Linux上,Gnu libsigsegv library包含函数stackoverflow_install_handler,它可以检测(并在某些情况下帮助您从中恢复)堆栈溢出。

答案 4 :(得分:6)

在Windows上,堆栈(针对特定线程)按需增长,直到达到为此线程创建之前指定的堆栈大小。

按需增长是使用保护页面实现的,因为最初只有一个堆栈片段可用,然后是一个保护页面,当被点击时会触发一个异常 - 这个异常是特殊的,并由系统为您处理 - 处理增加了可用的堆栈空间(如果已达到限制,也会检查!)并重试读取操作。

一旦达到限制,就不会再增加,这会导致堆栈溢出异常。 当前堆栈基数和限制存储在线程环境块中,称为_NT_TIB(线程信息块)的结构中。 如果您有一个方便的调试器,这就是您所看到的:

0:000> dt ntdll!_teb @$teb nttib.
   +0x000 NtTib  : 
      +0x000 ExceptionList : 0x0012e030 _EXCEPTION_REGISTRATION_RECORD
      +0x004 StackBase : 0x00130000 
      +0x008 StackLimit : 0x0011e000 
      +0x00c SubSystemTib : (null) 
      +0x010 FiberData : 0x00001e00 
      +0x010 Version : 0x1e00
      +0x014 ArbitraryUserPointer : (null) 
      +0x018 Self   : 0x7ffdf000 _NT_TIB

StackLimit属性将按需更新。 如果检查此内存块上的属性,您将看到与此类似的内容:

0:000> !address 0x0011e000 
    00030000 : 0011e000 - 00012000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

检查旁边的页面会显示guard属性:

0:000> !address 0x0011e000-1000
    00030000 : 0011d000 - 00001000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000104 PAGE_READWRITE | PAGE_GUARD
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageStack
                    Pid.Tid  abc.560

希望它有所帮助。

答案 5 :(得分:4)

堆栈溢出可能是最难处理的异常类型 - 因为您的异常处理程序必须处理最少量的堆栈(通常只为此目的保留一个页面)。

有关处理此类异常的困难的有趣讨论,请参阅以下博客文章:来自Chris Brumme的12,它们从.NET角度关注问题,特别是托管CLR。

答案 6 :(得分:1)

如果你在linux上,我建议你使用alternate-signal-stack。

  1. 在这种情况下,所有信号都将通过备用堆栈处理。
  2. 如果发生堆栈溢出,系统会生成一个SEGV信号,这可以通过备用堆栈处理。
  3. 如果您不使用它...那么您可能无法处理信号,并且您的程序可能会在没有任何处理/错误报告的情况下崩溃。

答案 7 :(得分:1)

可以在Visual Studio中使用editbin来更改堆栈大小。该信息可在msdn.microsoft.com/en-us/library/35yc2tc3.aspx找到。

答案 8 :(得分:0)

某些编译器支持stackavail()函数,它返回堆栈的剩余可用空间量。您可以在程序中调用需要大量堆栈空间的函数之前使用此函数,以确定是否可以安全地调用它们