web-dev-qa-db-ja.com

スタックオーバーフローはセグメンテーションフォールト以外の何かをもたらすことができますか?

コンパイルされたプログラムでは(CやC++としましょうが、この質問は、コールスタックを使用したVMに似ていない言語にも拡張できると思います)-スタックをオーバーフローさせると非常に頻繁に発生します セグメンテーションエラーが発生します

スタックオーバーフローは[a]原因で、セグメンテーションフォールトが結果です。

しかし、これは常にそうですか?スタックオーバーフローにより、他の種類のプログラム/ OSの動作が発生する可能性はありますか?

Linux、Windows、OS、X86以外のハードウェアについても質問しています。 (もちろん、ハードウェアメモリ保護やOSサポート(MS-DOSなど)がない場合は、セグメンテーションフォールトなどはありません。私はあなたがの場合について尋ねていますcouldはセグメンテーション違反を取得しますが、何か他のことが起こります)。

注:スタックオーバーフロー以外では、プログラムは有効であり、境界を超えた配列へのアクセス、無効なポインターの逆参照などを試みないものとします。

36
einpoklum

はい、標準OS(Linux)および標準ハードウェア(x86)でも可能です。

void f(void) {
    char arr[BIG_NUMBER];
    arr[0] = 0; // stack overflow
}

X86では、スタックが大きくなるため、オーバーフローをトリガーするために配列の先頭に割り当てていることに注意してください。通常の免責事項が適用されます...正確な動作は、Cコンパイラの詳細など、この回答で説明されているよりも多くの要因に依存します。

BIG_NUMBERがオーバーフローするのに十分な大きさしかない場合、スタックガードに遭遇し、セグメンテーションフォールトが発生します。それがスタックガードの目的であり、単一の4 KiBページと同じくらい小さくても(ただし、小さくなく、この4 KiBサイズはLinux 4.12より前に使用されます)、または大きくすることもできます(Linux 4.12では1 MiBのデフォルト) 、 mm:大きなスタックガードギャップ )を参照してください。ただし、常に特定のサイズです。

BIG_NUMBERが十分に大きい場合、オーバーフローはスタックガードをスキップして、有効なメモリである可能性のある他のメモリに到達する可能性があります。これにより、プログラムは正しく動作しますが、クラッシュすることはありません。これは基本的に最悪のシナリオです。wewantプログラムが正しくない場合にクラッシュする意図しない何かをするよりも。

31
Dietrich Epp

一つのことは、スタックをオーバーフローさせたときに実行時に何が起こるかということです。含むがこれらに限定されません;セグメンテーションフォールト、オーバーフローしたものに続いて変数を上書きし、不正な命令を引き起こします。 「古い」古典的なペーパー 楽しさと利益のためにスタックを破壊する は、このようなもので「楽しい」ことができる多くの方法を説明しています。

別のことは、コンパイル時に何が起こるかということです。 CとC++の両方で、配列を超える書き込みやスタックのサイズを超える書き込みは未定義の動作であり、プログラムにUBanywhereが含まれる場合、コンパイラは基本的に無料です何でもしたいからプログラムのany部分へそして、最新のコンパイラーは、最適化の目的でUBを悪用することに非常に積極的になってきています-多くの場合、UBが発生しないと仮定して、単純にUBを含むコードを削除するか、代替がUBを引き起こすためにブランチを常にまたはまったく実行しないようにします。時々、コンパイラは タイムトラベル または ソースコードで呼び出されなかった関数を呼び出す を導入し、非常に紛らわしいランタイム動作を引き起こす可能性のある他の多くのことを導入します。

こちらもご覧ください:

すべてのCプログラマーが未定義の動作#1/3について知っておくべきこと

すべてのCプログラマが未定義の動作#2/3について知っておくべきこと

すべてのCプログラマーが未定義の動作#3/3について知っておくべきこと

CおよびC++の未定義動作のガイド、パート1

CおよびC++の未定義動作のガイド、パート2

CおよびC++の未定義動作のガイド、パート

7
Jesper Juhl

他の回答は、PC側をかなりよくカバーしています。組み込みの世界の問題のいくつかに触れます。

埋め込みコードには、セグメンテーション違反に似たものがあります。コードは、ある種の不揮発性ストレージに保存されます(通常は最近フラッシュしますが、ある種のROMまたはPROM)。これへの書き込み特別な操作を設定する必要があります;通常のメモリアクセスは読み取りができますが、書き込みはできません。さらに、組み込みプロセッサは通常、メモリマップに大きなギャップがあります。プロセッサが読み取り専用のメモリに対する書き込み要求を取得した場合または、物理的に存在しないアドレスに対する読み取りまたは書き込み要求を取得すると、プロセッサは通常ハードウェア例外をスローします。デバッガを接続している場合は、システムの状態をチェックして、問題を見つけます。コアダンプ。

ただし、これがスタックオーバーフローに対して発生するという保証はありません。スタックはRAMのどこにでも配置でき、通常これは他の変数と一緒になります。スタックオーバーフローの結果は、通常、これらの変数を破壊することになります。

アプリケーションがヒープ(動的割り当て)も使用している場合、メモリのセクションを割り当てるのが一般的です。スタックはそのセクションの一番下から始まり、上に展開し、ヒープはそのセクションの一番上から始まり、下に展開します。これは明らかに、動的に割り当てられたデータが最初の犠牲者になることを意味します。

運が悪ければ、それがいつ起こるか気付かないかもしれません。そして、コードが正しく動作しない理由を解明する必要があります。最も皮肉なケースでは、上書きされるデータがポインターである場合、ポインターが無効なメモリにアクセスしようとするとハードウェア例外が発生する可能性がありますが、これはスタックがオーバーフローした後の時間であり、通常は自然な仮定ですコードのバグ。

埋め込みコードには、これに対処する共通のパターンがあります。これは、すべてのバイトを既知の値に初期化することでスタックに「透かし」を付けることです。コンパイラがこれを実行できる場合があります。または、main()の前にスタートアップコードで自分で実装する必要がある場合があります。スタックの最後から振り返って、この値に設定されなくなった場所を見つけることができます。この時点で、スタック使用量の最高水準点がわかります。または、それがすべて正しくない場合は、オーバーフローが発生していることがわかります。組み込みアプリケーションでは、これをバックグラウンド操作として継続的にポーリングし、診断目的でレポートできるようにするのが一般的です(そして適切な実践です)。

スタックの使用状況を追跡できるようになったため、ほとんどの企業は、オーバーフローを回避するために許容できる最悪のマージンを設定します。これは通常、75%から90%の範囲ですが、常に予備があります。これにより、まだ見たことのない最悪の最悪のケースが発生する可能性があるだけでなく、より多くのスタックを使用する新しいコードを追加する必要がある場合、将来の開発が楽になります。

6
Graham

Stackoverflowは、プログラムの 多くの理由未定義の動作 の1つです。この場合、予想される結果やセグメンテーションフォールトを取得したり、ハードディスクを消去したりすることができます。未定義の動作なので、定義された動作を期待しないでください。

3
haccks