2016-02-05 7 views
12

私はかなり大きな再帰関数(Cで書いています)を持っていますが、スタックオーバーフローが発生するシナリオはほとんどありませんが、それでも可能です。私が疑問に思うのは、スタックがいくつかの反復でオーバーフローするかどうかを検出できるかどうかです。プログラムをクラッシュさせずに緊急停止を行うことができます。実行時にスタックオーバーフローを事前に検出する

+0

私は、ポータブルソリューションがあると思いませんが、移植性を気にしない場合は、インラインアセンブリを使用して、スタックポインタの値をチェックすることができますスタックポインタがある値よりも小さい場合は緊急出口を実行します。 –

+0

単純に再帰深度を最大深度に制限することもできます。 –

+2

重複はありませんが、関連しています:http://stackoverflow.com/questions/199747/how-to-detect-possible-potential-stack-overflow-problems-in-acc-program?rq=1 –

答えて

4

相続人は、単純な解決策それはwin-32のために働く。実際Wossnameが既に掲載が、あまり不快何に似ている:)

unsigned int get_stack_address(void) 
{ 
    unsigned int r = 0; 
    __asm mov dword ptr [r], esp; 
    return r; 
} 
void rec(int x, const unsigned int begin_address) 
{ 
    // here just put 100 000 bytes of memory 
    if (begin_address - get_stack_address() > 100000) 
    { 
     //std::cout << "Recursion level " << x << " stack too high" << std::endl; 
     return; 
    } 
    rec(x + 1, begin_address); 
} 
int main(void) 
{ 
    int x = 0; 
    rec(x,get_stack_address()); 
} 
+0

'std :: cout'?これはCコードではありません。 – kfx

+0

@kfxが付与されました:) – mainactual

+10

あの、暖かく、ファジーな、仕事のセキュリティ感覚のためのインラインASM:D Nice。 – Wossname

9

C言語では、それは不可能です。一般に、あなたはあなたが尽きる前にスタックを使い果たしたことを簡単に知ることができません。代わりに、実装の再帰深度に構成可能なハード制限を設定することをお勧めします。深度が超過した場合は、単に中止できます。再帰を介してスタックを使用するのではなく、補助データ構造を使用するようにアルゴリズムを書き直すこともできます。これにより、メモリ不足状態を検出する柔軟性が向上します。 malloc()は、いつ失敗するかを知らせます。両方のためのシグナルハンドラを確立し、ハードスタックリミット

  • よりも低いソフトスタックの上限を設定する

    1. 使用setrlimit

      ただし、UNIX系のシステムでこのような手順で同様のものを得ることができますSIGSEGVおよびSIGBUSを使用してスタックオーバーフローを通知します。一部のオペレーティングシステムではSIGSEGV、それ以外の場合はSIGBUSが生成されます。

    2. このような信号が得られた場合にスタックオーバーフローが発生したと判断した場合は、ソフトスタックの制限をsetrlimitにして、これが発生したことを識別するためのグローバル変数を設定します。変数volatileを作成して、オプティマイザが平野を覆わないようにします。
    3. コード内で、各再帰ステップで、この変数が設定されているかどうかを確認します。そうであれば、中断する。

    これはどこでも動作するわけではなく、プラットフォーム固有のコードが必要なため、シグナルがスタックオーバーフローから発生していることがわかります。 SIGSEGVまたはSIGBUSを取得した後で、すべてのシステム(特に、68000システムの初期のシステム)が正常な処理を継続できるわけではありません。

    Bourneシェルはメモリ割り当てに同様の手法を使用しました。

  • +0

    似たようなハッキングはWindowsでは可能ですが、例外をサポートする言語を使用してWindows構造のスタックオーバーフロー例外をキャッチできれば、ずっと簡単です。 –

    +0

    @MartinJamesとにかくこのソリューションは移植性がないので、Windowsでは問題を解決するWindows APIを使用してください。 – fuz

    3

    はここで素朴な方法だが、それは少し不快です...

    初めて関数を入力するときは、その関数内で宣言あなたの変数のいずれかのアドレスを格納することができます。その値を関数外に(たとえば、グローバルに)格納します。後続の呼び出しでは、その変数の現在のアドレスとキャッシュされたコピーを比較します。これらの2つの値が離れれば離れるほど、より深いところまで再帰します。

    これはコンパイラの警告(一時変数のアドレスを格納する)を引き起こす可能性がありますが、使用しているスタックの正確な量を正確に知ることができます。

    本当にこれをお勧めしますが、うまくいくとは言えません。 (いくつかの安全マージン内、64キロバイトを言う)

    #include <stdio.h> 
    
    char* start = NULL; 
    
    void recurse() 
    { 
        char marker = '@'; 
    
        if(start == NULL) 
        start = &marker; 
    
        printf("depth: %d\n", abs(&marker - start)); 
    
        if(abs(&marker - start) < 1000) 
        recurse(); 
        else 
        start = NULL; 
    } 
    
    int main() 
    { 
        recurse(); 
    
        return 0; 
    } 
    
    +3

    スレッドセーフではありません:( –

    +0

    @MartinJamesスレッドローカル変数を使用するか、単純にポインタをワーカー関数の余分な引数として渡すことができますが、再帰の深さを単にカウントする方法よりも優れています。 – fuz

    +0

    @MartinJames、本当は、それは前提条件ではありませんでした:) – Wossname

    2

    別の方法は、プログラムの開始時にスタックリミットを学ぶことで、あなたの再帰関数の各時間は、この制限が近づいてきたかどうかをチェックします。もしそうなら、打ち切ります。そうでない場合は、続行します。

    システムコールのgetrlimitを使用すると、POSIXシステムのスタック制限を知ることができます。

    スレッドセーフである

    コード例:(注:!それのコードはスタックがx86のように、逆方向に成長することを前提として)

    #include <stdio.h> 
    #include <sys/time.h> 
    #include <sys/resource.h> 
    
    void *stack_limit; 
    #define SAFETY_MARGIN (64 * 1024) // 64 kb 
    
    void recurse(int level) 
    { 
        void *stack_top = &stack_top; 
        if (stack_top <= stack_limit) { 
         printf("stack limit reached at recursion level %d\n", level); 
         return; 
        } 
        recurse(level + 1); 
    } 
    
    int get_max_stack_size(void) 
    { 
        struct rlimit rl; 
        int ret = getrlimit(RLIMIT_STACK, &rl); 
        if (ret != 0) { 
         return 1024 * 1024 * 8; // 8 MB is the default on many platforms 
        } 
        printf("max stack size: %d\n", (int)rl.rlim_cur); 
        return rl.rlim_cur; 
    } 
    
    int main (int argc, char *argv[]) 
    { 
        int x; 
        stack_limit = (char *)&x - get_max_stack_size() + SAFETY_MARGIN; 
        recurse(0); 
        return 0; 
    } 
    

    出力:

    max stack size: 8388608 
    stack limit reached at recursion level 174549 
    
    +0

    10 kBは、 'main()'の前にスタック上で見つけたくらいのくらいのものを考えれば十分ではありません。長い引数リストを使用して再試行し、10 kBで十分でないことを確認してください。私は疑問に思っています。何とかスタックの始まりを見つけ出すことは間違いありません。 – fuz

    +0

    10 kBは私のために働くヒューリスティックです。答えで64klbに変更しました。スタックメモリ領域のアドレスを知る方法の1つは、 '/ proc//maps'ファイルを読むことですが、それは非常にプラットフォーム固有です。 – kfx

    関連する問題