アプリのスタック スペースの量と、実行中のスタック使用量の最高水準点を確認する標準的な方法はありますか?
また、実際のオーバーフローという恐ろしいケースでは何が起こるでしょうか?
クラッシュしたり、例外やシグナルをトリガーしたりしますか? 標準はありますか、それともすべてのシステムとコンパイラで異なりますか?
特に 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
, ) などのコマンドを使用して、シェルで読み取りまたは変更できる場合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
スタックの正確な場所を取得する 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 libsigsegvライブラリに関数が含まれています。この関数はstackoverflow_install_handler
、スタックオーバーフローを検出できます(場合によっては、回復に役立ちます)。
Windowsでは、スタック(特定のスレッド用)は、作成前にこのスレッドに指定されたスタックサイズに達するまで、オンデマンドで拡張されます。
オンデマンドの成長は、最初に利用可能なスタックのフラグメントのみがあり、その後にガードページが続くという点で、ガードページを使用して推進されます。ガードページは、ヒットすると例外をトリガーします。この例外は特別であり、システムによって処理されます。 you-処理により、使用可能なスタックスペースが増加し(制限に達しているかどうかもチェックされます!)、読み取り操作が再試行されます。
制限に達すると、それ以上成長することはなく、スタックオーバーフローの例外が発生します。現在のスタックベースと制限は、スレッド環境ブロックの_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
そして、その隣のページをチェックすると、ガード属性が明らかになります。
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
それが役に立てば幸い。
Visual Studio で editbin を使用して、スタック サイズを変更することができます。この情報はmsdn.microsoft.com/en-us/library/35yc2tc3.aspxにあります。
Linux を使用している場合は、alternate-signal-stack を使用することをお勧めします。
一部のコンパイラは、スタックの残りの空き容量を返す stackavail() 関数をサポートしています。多くのスタック領域を必要とするプログラムで関数を呼び出す前に、この関数を使用して、それらを呼び出しても安全かどうかを判断できます。