LinuxのCでスタックオーバーフローを予測することは可能ですか?
質問
x86 Linuxシステムでスタックオーバーフローを引き起こす可能性がある特定の条件があります。
-
スタック上の
-
struct my_big_object [HUGE_NUMBER]
。それを歩くと、最終的にSIGSEGV
が発生します。 -
alloca()
ルーチン(malloc()
と同様ですが、スタックを使用し、自動的にそれ自体を解放し、SIGSEGV
で爆破します)大きすぎる場合)。 更新:alloca()は、最初に述べたように正式には廃止されていません。単に推奨されていません。
ローカルスタックが特定のオブジェクトに対して十分に大きいかどうかをプログラムで検出する方法はありますか?スタックサイズは ulimit
で調整できることは知っているので、方法があることを願っています(ただし、移植性がない場合もあります)。理想的には、次のようなことができるようにしたいと思います。
int min_stack_space_available = /* ??? */;
if (object_size < min_stack_space_available)
{
char *foo = alloca(object_size);
do_stuff(foo);
}
else
{
char *foo = malloc(object_size);
do_stuff(foo);
free(foo);
}
解決
プロセスのスタックスペースのサイズを検索し、使用量を減算することにより、プロセスが使用できるスタックスペースを判別できます。
ulimit -s
は、Linuxシステムでのスタックサイズを示しています。プログラムによるアプローチについては、 getrlimit()。次に、現在のスタックの深さを判断するには、スタックの一番上から一番下へのポインターを減算します。例(テストされていないコード):
unsigned char *bottom_of_stack_ptr;
void call_function(int argc, char *argv) {
unsigned char top_of_stack;
unsigned int depth = (&top_of_stack > bottom_of_stack_ptr) ?
&top_of_stack-bottom_of_stack_ptr :
bottom_of_stack_ptr-&top_of_stack;
if( depth+100 < PROGRAMMATICALLY_DETERMINED_STACK_SIZE ) {
...
}
}
int main(int argc, char *argv) {
unsigned char bottom_of_stack;
bottom_of_stack_ptr = &bottom_of_stack;
my_function();
return 0;
}
他のヒント
非推奨のalloca()ルーチン(malloc()と同様ですが、スタックを使用しますが、 自動的に解放され、大きすぎる場合はSIGSEGVで爆破されます)。
なぜallocaは非推奨になりましたか
とにかく、あなたの場合alloca対mallocはどれくらい速いですか? (それだけの価値はありますか?)
十分なスペースが残っていない場合、allocaからnullを取得しませんか? (mallocと同じ方法?)
コードがクラッシュした場合、どこでクラッシュしますか? allocaにあるのですか、それともdoStuff()にあるのですか?
/ヨハン
これがLinuxに当てはまるかどうかはわかりませんが、Windowsでは、成功した場合でも大きなスタック割り当てでアクセス違反が発生する可能性があります!
これは、デフォルトでは、WindowsのVMMが実際にスタックRAMの4096バイトページ(ページファイルによってバッキングされている)の4096バイトページを実際にマークするのは、スタックアクセスが一般に信じているためです上から下に行進します。アクセスが現在の「境界」に近づくにつれて、下位ページと下位ページがページング可能としてマークされます。しかし、これは、メモリがまだ実際に割り当てられていないため、スタックの最上部よりはるか下の初期のメモリ読み取り/書き込みがアクセス違反を引き起こすことを意味します!
alloca()は失敗するとNULLを返します。alloca(0)の動作は未定義であり、プラットフォームによって異なります。 do_something()の前にそれをチェックする場合、SEGVでヒットすることはありません。
質問がいくつかあります:
- なぜ、なぜそんなに大きなスタックが必要なのですか?ほとんどのシステムのデフォルトサイズは8Mですが、それでもまだ小さすぎますか?
- alloca()を呼び出す関数がブロックする場合、mlock()/ mlockall()を介して同じ量のヒープが保護され、同じアクセスパフォーマンスに近いことが保証されます(つまり、&quot;私を交換しないでください!&quot;)時間?より積極的な「rt」スケジューラを使用している場合は、とにかくそれらを呼び出すことをお勧めします。
この質問は興味深いが、眉をひそめる。四角いペグ丸穴メーターの針を上げます。
スタックに割り当てる理由についてはあまり説明しませんが、魅力的なのがスタックメモリモデルである場合は、ヒープにもスタック割り当てを実装できます。プログラムの先頭に大きなメモリチャンクを割り当て、これへのポインタのスタックを保持します。これは、通常のスタック上のフレームに対応します。関数が戻ったときにプライベートスタックポインターをポップすることを忘れないでください。
複数のコンパイラ、たとえば Watcom C / C ++を開くは、stackavail()関数をサポートします。まさにそれができます
GNU libsigsegv
を使用して (ウェブサイトから)スタックオーバーフローが発生した場合を含むページフォールトを処理します:
一部のアプリケーションでは、スタックオーバーフローハンドラーが何らかのクリーンアップを実行するか、ユーザーに通知してからすぐにアプリケーションを終了します。他のアプリケーションでは、スタックオーバーフローハンドラーlongjmpはアプリケーションの中心点に戻ります。このライブラリは両方の使用をサポートしています。 2番目の場合、ハンドラーは通常のシグナルマスクを確実に復元する必要があり(ハンドラーの実行中に多くのシグナルがブロックされるため)、制御を転送するためにsigsegv_leave_handler()を呼び出す必要もあります。その後、それだけがlongjmpできます。
alloca関数は非推奨ではありませんではありません。ただし、POSIXにはなく、マシンとコンパイラに依存しています。 allocaのLinuxマンページには、「特定のアプリケーションでは、その使用によりmallocの使用と比較して効率が向上することがあり、特定の場合には、longjmp()またはsiglongjmp()を使用するアプリケーションのメモリ割り当て解除も簡素化できる」と書かれています。それ以外の場合、その使用は推奨されません。&quot;
また、マンページには、スタックフレームを拡張できない場合はエラー表示がないと記載されています。ただし、割り当てに失敗した後、プログラムはSIGSEGVを受け取る可能性があります。&quot;
mallocのパフォーマンスは、実際には Stackoverflow Podcast#36 で言及されています。
(これはあなたの質問に対する適切な答えではないことは知っていますが、とにかく役立つと思いました。)
これがあなたの質問に対する直接的な答えではない場合でも、 valgrind < a>-Linuxで実行時にこのような問題を検出するための素晴らしいツール。
スタックの問題に関しては、これらのオーバーフローを検出する固定プールからオブジェクトを動的に割り当てようとすることができます。シンプルなマクロウィザードを使用すると、デバッグ時に実行し、リリース時に実際のコードを実行することができます。したがって、(少なくとも実行しているシナリオでは)必要以上に取っていないことがわかります。 詳細情報とリンクサンプル実装へ。
考えられる良い方法はありません。たぶん、getrlimit()(前に推奨)といくつかのポインター演算を使用することで可能ですか?ただし、最初にこれが本当に必要かどうか自問してください。
void *closeToBase; main () { int closeToBase; stackTop = &closeToBase; } int stackHasRoomFor(int bytes) { int currentTop; return getrlimit(...) - (¤tTop - closeToBase) > bytes + SomeExtra; }
個人的に、私はこれをしません。ヒープに大きなものを割り当てます。スタックはそのためのものではありませんでした。
スタック領域の終わりは、OSによって動的に決定されます。 「静的」を見つけることができますが高度にOSに依存する方法で仮想メモリ領域(VMA)を見ることによるスタックの境界( libsigsegv / src / )、さらに考慮する必要があります
- getrlimit値、
- スレッドごとのスタックサイズ( pthread_getstacksize を参照)
これが明白なことを述べている場合は申し訳ありませんが、(そのサイズの)allocaを試し、スタックオーバーフロー例外をキャッチするだけで、特定のスタック割り当てサイズをテストする関数を簡単に作成できます。必要に応じて、関数スタックオーバーヘッドのいくつかの事前に定義された数学を使用して、関数に配置できます。例:
bool CanFitOnStack( size_t num_bytes )
{
int stack_offset_for_function = 4; // <- Determine this
try
{
alloca( num_bytes - stack_offset_for_function );
}
catch ( ... )
{
return false;
}
return true;
}