スタックオーバーフロー
http://q.hatena.ne.jp/1186284498
int LimitStackCall(DWORD stackSize, void (*StartFunc)()) { SYSTEM_INFO sysInfo; DWORD pageSize; DWORD alinedStackSize; void* pPage = NULL; void* pGuard = NULL; void* pStack = NULL; int ret = -1; // ページサイズを取得 GetSystemInfo(&sysInfo); pageSize = sysInfo.dwPageSize; // stackSize をページサイズで align alignedStackSize = (stackSize + pageSize - 1) / pageSize * pageSize; // スタックサイズを確保するために必要なページとガード用のページを予約 pPage = ::VirtualAlloc(NULL, alignedStackSize + pageSize, MEM_RESERVE, PAGE_READWRITE); if (pPage == NULL) { goto release; } // ガード用のページをコミット pGuard = ::VirtualAlloc(pPage, pageSize, MEM_COMMIT, PAGE_READONLY | PAGE_GUARD); if (pGuard == NULL) { goto release; } // スタック用のページをコミット pStack = ::VirtualAlloc((char*)pPage + pageSize, alignedStackSize, MEM_COMMIT, PAGE_READWRITE); if (pStack == NULL) { goto release; } // ページフォールトが起こってしまうと困るのでロックしておく if (!::VirtualLock(pPage, alignedStackSize + pageSize)) { if (!::VirtualLock(pPage, alignedStackSize + pageSize)) { goto release; } } // スタックの初期位置を設定 pStack = (char*)pStack + stackSize - 4; // レジスタを保存しておいてスタックを切り替える __asm { // 保存 push eax push ebx push ebp push esi push edi // スタック切り替え xchg pStack, esp // 呼び出し call [StartFunc] // C++ で書く場合、この中で例外が発生すると // pStack とかがリークしてしまうけど、 // だからといってここを try, catch で囲んで例外が発生した場合、 // スタックポインタがどうなるのかよく分からない。 // なので、StartFunc の中で例外をハンドルしてもらうことにする。 xchg pStack, esp // 復元 pop edi pop esi pop ebp pop ebx pop eax } ret = 0; release: // 解放 if (pStack != NULL) { ::VirtualFree(pStack, pageSize, MEM_DECOMMIT); } if (pGuard != NULL) { ::VirtualFree(pGuard, pageSize, MEM_DECOMMIT); } if (pPage != NULL) { ::VirtualFree(pPage, alignedStackSize + pageSize, MEM_RELEASE); } return ret; }
void Start() { // ここにメインルーチンを書く // C++ で書く場合、ここで発生した例外は // 全てハンドルし、外に漏らさないこと。 } void main() { // Start を 8KB のスタック量にして呼び出す LimitStackCall(4096 * 2, &Start); }
Windows でのスタックオーバーフローというのは、スタックの最上位にガードページを入れてやることによって検出しているようです。
なので、スタック領域とガードページを VirtualAlloc() で確保して、esp をそのスタック領域と入れ替えてやってから関数を呼び出せばいけるのではないか、という考えです。
ただ、デバッグモードでコンパイルすると、関数毎にスタックのチェックのために数百バイトのスタックを使っているようです。
リリースモードでコンパイルすると、最適化が走って複数の関数のスタックが一括で確保されたりすることもあるようで、思ったようなスタックの消費が出来ませんでした。
もしかしたらリリースモードで最適化を外してやれば意図した通りにスタックが消費されるのかもしれませんが、プログラム上でこれ以上のケアは無理そうなのでこの辺で止めておきます。