如何在ac / c ++程序中检测可能/潜在的堆栈溢出问题?
有没有一个标准的方法来看看你的应用程序有多less堆栈空间,以及堆栈使用的最高水印是在运行期间?
另外在实际溢出的可怕情况下会发生什么?
它是否会崩溃,触发exception或信号? 在所有系统和编译器中是否有标准或不同?
我正在寻找Windows,Linux和Macintosh。
在Windows上,会产生堆栈溢出exception 。
以下窗口代码说明了这一点:
#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 ******
在Linux上,如果代码尝试写入堆栈,则会出现分段错误。
堆栈的大小是进程之间inheritance的属性。 如果你可以使用像ulimit -s
(在sh
, ksh
, zsh
)或limit stacksize
( tcsh
, zsh
)这样的命令在shell中读取或修改它。
从程序中可以读取堆栈的大小
#include <sys/resource.h> #include <stdio.h> struct rlimit l; getrlimit(RLIMIT_STACK, &l); printf("stack_size = %d\n", l.rlim_cur);
我不知道一个标准的方法来获得可用堆栈的大小。
堆栈以argc
开头,接着是argv
的内容和环境的副本,然后是你的variables。 但是,由于内核可以随机化堆栈的开始位置,并且argc
上方可能有一些虚拟值,因此假定在&argc
下面有可用的l.rlim_cur
字节是错误的。
检索堆栈确切位置的一种方法是查看文件/proc/1234/maps
(其中1234
是程序的进程ID)。 一旦你知道了这些界限,你可以通过查看最新的局部variables的地址来计算你的栈的使用量。
gcc在“unsafe”函数调用中在返回地址和正常variables之间放置了额外的内存块(在这个例子中,函数是void test(){char a [10]; b [20]):
call stack: ----------- return address dummy char b[10] char a[20]
如果函数在指针“a”中写入36个字节,则溢出将“破坏”返回地址(可能的安全漏洞)。 但是它也会改变'dummy'的值,也就是指针和返回地址之间的值,所以程序会发生一个警告(你可以用-fno-stack-protector来禁止这个)
在Linux上,Gnu libsigsegv库包含函数stackoverflow_install_handler
,它可以检测堆栈溢出(有时可以帮助您恢复)。
堆栈溢出可能是最难处理的exceptiontypes – 因为你的exception处理器必须处理最小量的堆栈(通常只有一个页面是为此目的而保留的)。
关于处理这种types的exception的一些有趣的讨论,请看这些博客文章:Chris Brumme的1和2 ,主要关注.NET的问题,尤其是托pipeCLR。
在窗口上,堆栈(对于特定线程)按需增长,直到达到此线程创build之前指定的堆栈大小。
按需增长是通过使用防护页来推动的,因为最初只有一个堆栈可用,随后是一个防护页,当触发时会触发一个exception – 这个exception是特殊的,由系统处理你 – 处理增加了可用的堆栈空间(也检查是否已达到限制!),并重新读取操作。
一旦达到限制,就不会有越来越多的堆栈溢出exception。 当前的堆栈基础和限制存储在一个名为_NT_TIB
(线程信息块)的结构中的线程环境块中。 如果你有一个debugging器方便,这是你看到的:
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
并检查旁边的页面显示警卫属性:
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
希望能帮助到你。
如果你在linux上,我build议你使用备用信号栈。
- 在这种情况下,所有的信号将通过备用堆栈进行处理。
- 在发生堆栈溢出的情况下,系统产生一个SEGV信号,这可以通过备用堆栈进行处理。
- 如果你不使用它…那么你可能无法处理信号,并且你的程序可能会崩溃,没有任何处理/错误报告。
可以在Visual Studio中使用editbin来更改堆栈大小。 信息可以在msdn.microsoft.com/en-us/library/35yc2tc3.aspxfind。
一些编译器支持stackavail()函数,该函数返回堆栈剩余可用空间量。 在调用程序中需要大量堆栈空间的函数之前,可以使用此函数来确定调用函数是否安全