Stack Overflowさせたいです。


VC6を使用しています。新規でプロジェクトを作ってスタックアロケーションの予約サイズを100byteにしました。下記のテストコードを動かした場合1024byteのスタック領域が確保出来ずにStack Overflowになると思っていたのですが、Stack Overflowにはなりませんでした。

テストコード:
int main( int argc, char *argv[])
{
 char hoge[1024];
 return 0;
}

ちなみにchar hoge[1024];を下記のように変更してみました。
 char hoge[1024*1024]; → Stack Overflow
 char hoge[512*1024];  → Stack Overflow
 char hoge[128*1024];  → 問題なし

スタックサイズが100byteと決めて、それ以上のスタック領域を使おうとした場合、Stack Overflowで落ちるようにしたいのですが、どうすれば良いでしょうか?有識者の皆様ご教示ください。

回答の条件
  • 1人5回まで
  • 登録:2007/08/05 12:28:19
  • 終了:2007/08/12 12:30:03

回答(3件)

id:noocyte No.1

noocyte回答回数21ベストアンサー獲得回数32007/08/05 15:38:17

ポイント27pt

VC6 は持っていないので,VC2003 で調べてみました.

大きいスタックサイズを指定して Stack Overflow を発生させ,

デバッガを起動するとアセンブラで書かれた _chkstk ルーチンの

中で例外が発生していることがわかります.

_chkstk をC言語風に書いてみると,次のような感じです.

#define _PAGESIZE_ 0x1000 // ページサイズは 4KB

// x86 のレジスタ
register DWORD *ESP;   // スタックポインタ
register DWORD EAX;

void ckstk(void)
{
 // この時点で,EAX=スタックフレームのサイズ (バイト数).

 if(EAX < _PAGESIZE_) {
  // EAX ← 新しいスタックフレームの先頭アドレス+4
  // (リターンアドレス格納先の直後)
  EAX = (BYTE*)ESP - EAX + 4;

  // *(DWORD*)EAX を読んでみる.スタック用メモリが
  // 割り当てられていなければ,ここで例外が発生する.
  *(volatile DWORD*)EAX;

  EAX ⇔ ESP;   // 両者を交換

  // EAX ← 交換前の *(DWORD*)ESP,つまりリターンアドレス.
  EAX = *(DWORD*)EAX;

  // 新しいスタックフレームの先頭にリターンアドレスを push.
  *--ESP = EAX;
 } else {
  (調べてないので略)
 }
}


↑Low Address
┌───────┬┬← new ESP
│Return Address│↑
├───────┤│
│       ││
│       ││
│       ││New Stack Frame
│       ││
│       ││
│       ││
│       │↓
├───────┼┴← old ESP
│Return Address│
└───────┘
↓High Address

これは新しいスタックトップを試しに読んでみて,割り当てられていなければ

メモリアクセス違反が発生するというやり方になっています.

つまり「スタックサイズを1バイト単位で」チェックしているのではなく,

「スタックアドレスを1ページ=4096バイト単位で」しかチェックしていないのです.

(スタックは4バイト単位なので) 4バイト単位でチェックしたければ,

自力でチェックするしかなさそうです.その方法をちょっと考えているので,

うまくいけば後でまた投稿します.

id:noocyte No.2

noocyte回答回数21ベストアンサー獲得回数32007/08/05 21:47:32

ポイント27pt

最初に回答 #1 をちょっと訂正します.

  • 2行目:「大きいスタックサイズ」→「大きい配列」
  • 未確認ですが,リターンアドレスはたぶん間違いで,

「呼び出し元のスタックフレームの先頭アドレス」がたぶん正しい.


一般のC言語で自力スタックチェックを行う方法

VC という条件を一旦横へ置いといて,まず一般のC言語を使って自力でスタック

チェックを行う方法を考えます.

(移植性が保証されているわけではありませんが,たぶんたいていの処理系で OK.

ただしスタックが下位アドレスから上位アドレスに向かって伸びる CPU では,

アドレスの大小を反転させる必要あり.)


関数を呼び出す際は,引数を (左または右から順に) スタックに積み,

関数を CALL し,次に auto 変数の領域を確保するので,スタック上の

配置は次の順番になるはず.

↑Low Address
┌───────┐
│ auto変数領域 │
├───────┤
│Return Address│
├───────┤
│   引数領域   │
└───────┘
↓High Address

参考:コールスタック (Wikipedia)

http://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%AB%E3%82%B...


関数の引数を arg1,…,argN とすると,引数を左から順にスタックに積む

処理系では &arg1+1 が関数呼び出し直前の stack top になるはず.

(同様に,右からの場合は &argN+1.)


したがって,C のプログラムから stack bottom のアドレスを得ようとすれば,

main() の &argc+1 (左からの場合) または &argv+1 (右からの場合) が

妥当な値になるはず.(このアドレスより後にもスタック領域があるかもしれないが,

C のプログラムからは知り得ない.)


一方,stack top を求めるのはちょっと面倒.auto 変数をどういう順番で

スタック上に配置するかは規定されていないので,次の2つの方法以外は

今のところ思いつかない.


(1) すべての auto 変数のアドレスを調べ,その最小値を stack top とみなす.

(2) alloca(1) が返したアドレスを stack top とみなす.

alloca() は auto 変数を確保したあとで実行するので,スタック配置は

次のようになるはず.(この方法は (1) に比べて簡単だが,スタックを

少し無駄遣いするのが欠点.)

↑Low Address
┌───────┐
│  alloca領域  │
├───────┤
│ auto変数領域 │
├───────┤
│Return Address│
├───────┤
│   引数領域   │
└───────┘
↓High Address

以上の方法を行う場合に注意しなければならないのは,当然ながら

まだスタックにアクセスしてはならないという点である.


  • チェックが終わるまで,auto 変数を初期化してはならない.

また,チェックの途中結果を auto 変数に書いてもいけない.

  • 変数アドレスの最大値・最小値を求める際,式を使ってはならない.

(式の計算にスタックを使う可能性があるので.)

単純な代入文ならたぶん大丈夫.

  • スタックオーバーフローを検出しても,それを報告するために関数を

呼び出したり,auto 変数に書き込んだりしてはならない.


長くなったので,VC2003 で自力スタックチェックを行う方法は次回に.

id:noocyte No.3

noocyte回答回数21ベストアンサー獲得回数32007/08/06 04:05:18

ポイント26pt

VC2003 用自力スタックチェックプログラム (参考用)

#2 で書いた方法を VC2003 で使えるかどうか調べたところ,そのままだと

ちょっと問題があることがわかりましたが,説明するとまた長くなるので,

その対策を行ったプログラムをとりあえず投稿します.

#include <stdio.h>
#include <stdlib.h>

// 最大スタックサイズの設定
// VC のスタック使用量は debug と release で異なる.
#ifdef _DEBUG
#define MAXSTACKSIZE    159 // debug 用
#else /* _DEBUG */
#define MAXSTACKSIZE    132 // release 用
#endif /* _DEBUG */

char *StackTopLimit;    // 許容する StackTop の下限
char *StackTop;         // 現在のスタックトップ
char *StackBottom;      // スタック開始位置

// スタック上の変数のアドレスを一時的に記憶する変数.
// (auto 変数にしてはならない.)
char *StackVarAddress;

/*--------------------------------------------------------------------------
機能  :関数の引数または auto 変数から StackTop を求める.(main() 以外の関数用)
入力  :var:関数の引数名または auto 変数名.
使用法:main() 以外の関数のすべての引数および auto 変数についてこれを実行する.
--------------------------------------------------------------------------*/
#define FindStackTop(var) \
  StackVarAddress = (char*)&(var); \
  if(StackVarAddress < StackTop) StackTop = StackVarAddress;

/*--------------------------------------------------------------------------
機能  :関数の引数または auto 変数から,StackTop および StackBottom を求める.
        (main() 専用)
入力  :var:関数の引数名または auto 変数名.
使用法:main() のすべての引数および auto 変数についてこれを実行する.
--------------------------------------------------------------------------*/
#define FindStackRange(var) \
  FindStackTop(var); \
  StackVarAddress += sizeof(var); \
  if(StackVarAddress > StackBottom) StackBottom = StackVarAddress;

/*--------------------------------------------------------------------------
--------------------------------------------------------------------------*/
int main(int argc, char *argv[])
{
  // 注意:auto 変数は,スタックチェックが成功するまで初期化してはならない.
  char   hoge[100];
  short  hige;
  int    hage;
  double hogo;

  // 引数領域および auto 変数領域のアドレス範囲を求め,
  // その最大値を StackBottom,最小値を StackTop とする.
  // すべての引数および auto 変数について FindStackRange() を実行する.
  // (main() 以外では FindStackRange() ではなく FindStackTop() を使用する.)
  StackTop    = (char*)~(size_t)0; // 最小値を求めるための初期値
  StackBottom = (char*)0;          // 最大値を求めるための初期値 (main() のみ)
  FindStackRange(argc);
  FindStackRange(argv);
  FindStackRange(hoge);
  FindStackRange(hige);
  FindStackRange(hage);
  FindStackRange(hogo);

  StackTopLimit = StackBottom - MAXSTACKSIZE;

#if 1 /* DEBUG */
  // 本来ここで関数を呼んではいけない.あくまでもデバッグ用.
  printf("MAXSTACKSIZE : %08lX\n", (unsigned long)MAXSTACKSIZE);
  printf("StackTop     : %p\n", StackTop);
  printf("StackTopLimit: %p\n", StackTopLimit);
  printf("StackBottom  : %p\n", StackBottom);
#endif /* DEBUG */

  // スタックオーバーフローをチェックする.
  if(StackTop < StackTopLimit) {
    // スタックオーバーフローの場合:
    // エラーを報告するために関数を呼び出すことはできないので,
    // わざと大きなスタック領域を確保して強制的に例外を発生させる.
    _alloca(0x1000000);
    //*(char*)_alloca(0x1000000) = 0;
    //fprintf(stderr, "Stack overflow\n");    // 本来は使用不可だがテスト用
  }
  return EXIT_SUCCESS;
}

ご希望があれば説明しますが,次に書き込めるのはたぶん週末になると思います.


検出精度や速度については改善の余地があると思いますが,

sakuneko さんがどういう目的でスタックサイズをたった100バイトに制限し,

実際にはメモリがあるのになぜわざわざエラーを発生させたいのかわからないので,

とりあえずこの辺で.

id:sakuneko

まだ全て読みきれておりませんが、noocyteさんの丁寧な回答に感謝しております。

>どういう目的で

評価ボード用のソフトウェアを開発をしているのですが、単価が高い上に1台しかないので、必要な機能をwrapしてwindowsで開発可能な環境を作っています。

評価ボード=組込みですのでスタックサイズがものすごく貧弱です。実際に作業している方々にスタックを意識した開発をして頂くために、windows用のシミュレータ(と言えるほど大層なものではないですが)を出来る限り評価ボードの環境に近づけたいと考えています。

>スタックサイズをたった100バイトに制限し

本当に制限したいサイズは4096byte(4KB)です。スタックアロケーションの予約サイズは、byte単位で指定可能と書かれていたので、キリのよさそうな100byteとして説明させて頂きました。

2007/08/11 10:00:54

コメントはまだありません

この質問への反応(ブックマークコメント)

トラックバック

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

絞り込み :
はてなココの「ともだち」を表示します。
回答リクエストを送信したユーザーはいません