アプリのスタックスペースの量を確認する標準的な方法はありますか?また、実行中のスタック使用量の最高水準点は何ですか?
また、実際のオーバーフローの恐ろしいケースではどうなりますか?
クラッシュしますか、例外またはシグナルをトリガーしますか?標準はありますか、それともすべてのシステムとコンパイラで異なりますか?
私は特にWindows、Linux、Macintoshを探しています。
Windowsでスタックオーバーフローexceptionが生成されます。
次の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
_(sh
、ksh
、zsh
内)または_limit stacksize
_(tcsh
、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
の上にいくつかのダミー値が存在する可能性があるため、_l.rlim_cur
_バイトが_&argc
_の下にあると想定するのは誤りです。
スタックの正確な場所を取得する1つの方法は、ファイル_/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バイトを書き込むと、オーバーフローにより戻りアドレスが「破損」します(セキュリティー違反の可能性があります)。ただし、ポインタと戻りアドレスの間の「ダミー」の値も変更されるため、プログラムは警告付きでクラッシュします(-fno-stack-protectorでこれを無効にできます)。
Linuxでは、Gnu libsigsegvlibrary には、検出できる(場合によっては役立つ)関数stackoverflow_install_handler
が含まれていますスタックオーバーフローから回復します。
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
それが役に立てば幸い。
Linuxを使用している場合は、alternate-signal-stackを使用することをお勧めします。
Visual Studioでeditbinを使用してスタックサイズを変更することができます。情報は msdn.Microsoft.com/en-us/library/35yc2tc3.aspx にあります。
一部のコンパイラは、スタックの残りの空き容量を返すstackavail()関数をサポートしています。多くのスタックスペースを必要とするプログラムで関数を呼び出す前にこの関数を使用して、それらを呼び出しても安全かどうかを判断できます。