是否有一种标准方法可以查看您的应用程序有多少堆栈空间以及在运行期间堆栈使用的最高水印是多少?
同样在实际溢出的可怕情况下会发生什么?
它会崩溃、触发异常或信号吗?所有系统和编译器都有标准还是不同?
我正在专门寻找 Windows、Linux 和 Macintosh。
在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 ******
在 Linux 上,如果您的代码尝试写入堆栈,则会出现分段错误。
堆栈的大小是进程之间继承的属性。ulimit -s
如果您可以使用(in sh
, ksh
, zsh
) 或limit stacksize
( tcsh
, )等命令在 shell 中读取或修改它zsh
。
从程序中,可以使用以下命令读取堆栈的大小
#include <sys/resource.h>
#include <stdio.h>
int main() {
struct rlimit l;
getrlimit(RLIMIT_STACK, &l);
printf("stack_size = %ld\n", l.rlim_cur);
return 0;
}
我不知道获取可用堆栈大小的标准方法。
堆栈以环境argc
的内容argv
和副本开头,然后是您的变量。但是,由于内核可以随机化堆栈开始的位置,并且上面可能有一些虚拟值argc
,因此假设您在l.rlim_cur
下面有可用的字节是错误的&argc
。
检索堆栈的确切位置的一种方法是查看文件/proc/1234/maps
(1234
程序的进程 ID 在哪里)。一旦你知道了这些界限,你就可以通过查看最新的局部变量的地址来计算你的堆栈使用了多少。
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 禁用它)
在 Linux 上,Gnu libsigsegv库包含函数stackoverflow_install_handler
,它可以检测(并且在某些情况下帮助您从中恢复)堆栈溢出。
On windows, the stack (for specific thread) grows on-demand until the stack size specified for this thread prior to its creation has been reached.
On-demand growing is impelmented using guard pages, in that there's a only a fragment of stack available initially, followed by a guard page, which, when hit, will trigger an exception - this exception is special, and is handled by the system for you - the handling increases the available stack space (also checked if a limit has been reached!) and the read operation is retried.
Once the limit is reached, there's no more growing which results in stack overflow exception.
The current stack base and limit are stored in thread environment block, in a struct called _NT_TIB
(thread information block).
If you have a debugger handy, this is what you see:
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
The StackLimit attribute will get updated on-demand. If you check the attributes on this memory block, you'll see something similar to that:
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
And checking a page next to it reveals the guard attribute:
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
Hope it helps.
如果您在 linux 上,我建议您使用备用信号堆栈。
一些编译器支持 stackavail() 函数,它返回堆栈的剩余可用空间量。您可以在调用程序中需要大量堆栈空间的函数之前使用此函数,以确定调用它们是否安全
可以在 Visual Studio 中使用 editbin 来更改堆栈大小。该信息可在msdn.microsoft.com/en-us/library/35yc2tc3.aspx中找到。