スタック コラプション
これまでにスタックの主な役割について説明してきましたが、スタックが壊れてしまう、ということはよくあるバグのひとつです。それをスタックコラプションといいます。スタックコラプションが発生する主な原因とそれが発生したときの状況についてご説明します。
簡単に壊れるスタック
それぞれのスレッドには関連付けされたスタック領域があり、関数の呼び出し時のパラメータの引渡しやローカル変数の保持に使用されます。
さて、C言語のソースコードには大きな間違いがあります。ひと目でお分かりになりますでしょうか。
void Foo(char** pBuff) { char* buff[256]; *pBuff = (char*) buff; } int main( int argc, char* argv[] ) { char* lpBuff = NULL; Foo(&lpBuff); sprintf(lpBuff, "Hello, World\n"); printf("%s", lpBuff); return 0; }
関数 Foo でバッファを十分なサイズ用意し、そこに短い文字列を書き込んでいる、だから問題がないのではないかと思われた方は間違いです。 バッファ buff[256] の有効範囲 (スコープ) はあくまで関数 Foo 内だけですから、それを main で使用することは禁じられています。
ところが、実行してみると分かるのですが筆者の環境ではこのプログラムは何の問題もなく動作します (何の問題もないかのように実行してしまう、と表現するのが適切でしょう)。
実行結果 (確かに “Hello, World!” という文字列が表示されている)
ではデバッガを使ってこのプログラムの内部動作を見てみましょう。
0:000> bp stack!Foo ← 関数 Foo にブレークポイントを設定 0:000> g ← プログラムの実行 Breakpoint 0 hit eax=0012fee0 ebx=7ffdf000 ecx=0012feac edx=7ffe0304 esi=00000a28 edi=00000000 eip=00401000 esp=0012fed8 ebp=0012fee4 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 stack!Foo: 00401000 55 push ebp 0:000> kv ChildEBP RetAddr Args to Child 0012fed4 0040102c 0012fee0 00000000 0012ffc0 stack!Foo (CONV: cdecl) 0012fee4 0040134e 00000001 00372c18 00372c60 stack!main+0x14 (CONV: cdecl) 0012ffc0 77e3eb69 0006f4d4 00080000 7ffdf000 stack!mainCRTStartup+0x169 (CONV: cdecl) 0012fff0 00000000 004011e5 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo]) 0:000> dv pBuff = 0012fee0 buff = Array [256] 0:000> .frame 01 01 0012fee4 0040134e stack!main+0x14 0:000> dv lpBuff = 00000000 "" 0:000> g 0040102c eax=0012fee0 ebx=7ffdf000 ecx=0012fad4 edx=7ffe0304 esi=00000a28 edi=00000000 eip=0040102c esp=0012fedc ebp=0012fee4 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000206 stack!main+14: 0040102c 83c404 add esp,0x4 0:000> kv ChildEBP RetAddr Args to Child 0012fee4 0040134e 00000001 00372c18 00372c60 stack!main+0x14 (CONV: cdecl) 0012ffc0 77e3eb69 0006f4d4 00080000 7ffdf000 stack!mainCRTStartup+0x169 (CONV: cdecl) 0012fff0 00000000 004011e5 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo]) 0:000> dv lpBuff = 0012fad4 "" ← lpBuff は esp より小さい値を指している 0:000> dc lpBuff l8 0012fee0 0012fad4 0012ffc0 0040134e 00000001 ........N.@..... 0012fef0 00372c18 00372c60 00000094 00000005 .,7.`,7......... 0:000> dc esp l8 0012fedc 0012fee0 0012fad4 0012ffc0 0040134e ............N.@. 0012feec 00000001 00372c18 00372c60 00000094 .....,7.`,7..... 0:000> g Hello, World
このようにローカル変数のアドレスを呼び出し元へ返すことで、スタック内の不正な位置へ書き込みを行うことになります。 これは直ちにアクセス違反などを発生させる場合もあれば、今回のように見かけ上、正常終了したように見える場合があります。 特にこのようなバグは、お客様サイトにて実行されたときに姿を現します。