スタックオーバーフロー

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 をそのスタック領域と入れ替えてやってから関数を呼び出せばいけるのではないか、という考えです。


ただ、デバッグモードでコンパイルすると、関数毎にスタックのチェックのために数百バイトのスタックを使っているようです。
リリースモードでコンパイルすると、最適化が走って複数の関数のスタックが一括で確保されたりすることもあるようで、思ったようなスタックの消費が出来ませんでした。
もしかしたらリリースモードで最適化を外してやれば意図した通りにスタックが消費されるのかもしれませんが、プログラム上でこれ以上のケアは無理そうなのでこの辺で止めておきます。