コンパイル時にスタック使用量を確認する
-
02-07-2019 - |
質問
Cのコンパイル時に関数に必要なスタックサイズを知り、出力する方法はありますか?私が知りたいことは次のとおりです。
いくつかの関数を考えてみましょう:
void foo(int a) {
char c[5];
char * s;
//do something
return;
}
この関数をコンパイルするとき、呼び出されたときに消費されるスタック領域の量を知りたいです。これは、大きなバッファを隠している構造体のオンスタック宣言を検出するのに役立つ可能性があります。
次のようなものを印刷するものを探しています:
ファイル foo.c :関数 foo のスタック使用量は n
バイト
それを知るために生成されたアセンブリを見ない方法はありますか?それともコンパイラに設定できる制限でしょうか?
アップデート :特定のプロセスのランタイムスタックオーバーフローを回避しようとしているのではなく、コンパイラによって決定された関数スタックの使用状況がコンパイルプロセスの出力として利用可能かどうかをランタイム前に見つける方法を探しています。
別の言い方をしてみましょう:関数に対してローカルなすべてのオブジェクトのサイズを知ることは可能ですか?コンパイラの最適化は私の友人ではないと思います。いくつかの変数は失われますが、上限が優れているのは問題ないためです。
解決
Linux カーネル コードは、x86 上の 4K スタック上で実行されます。したがって、彼らは気にします。それをチェックするために彼らが使用しているのは、彼らが書いた Perl スクリプトです。これは、最近のカーネル tarball (2.6.25 ではそれが含まれています) 内に scripts/checkstack.pl として見つかる可能性があります。これは objdump の出力で実行され、使用方法のドキュメントは最初のコメントにあります。
何年も前にユーザー空間のバイナリにすでに使用していたと思います。Perl プログラミングを少しでも知っていれば、壊れた場合でも簡単に修正できます。
とにかく、基本的に行うことは、GCC の出力を自動的に調べることです。そして、カーネル ハッカーがそのようなツールを作成したという事実は、GCC でそれを行う静的な方法がないことを意味します (あるいは、ごく最近追加されたのかもしれませんが、私はそうは思いません)。
ところで、mingw プロジェクトの objdump と ActivePerl、または Cygwin を使用すると、Windows 上でも、他のコンパイラで取得したバイナリ上でもそれを行うことができるはずです。
他のヒント
StackAnlyser は、実行可能コード自体といくつかのデバッグ情報を検査するようです。で説明されている内容 この返事, 、私が探しているものですが、スタックアナライザーは私にはやりすぎのように思えます。
ADA 用に存在するものと同様のもので問題ありません。gnat マニュアルのこのマニュアル ページを参照してください。
22.2 静的スタック使用量の分析
-fstack-usage でコンパイルされたユニットは、関数ごとに使用されるスタックの最大量を指定する追加ファイルを生成します。ファイルのベース名はターゲット オブジェクト ファイルと同じで、拡張子は .su です。このファイルの各行は、次の 3 つのフィールドで構成されています。
* The name of the function.
* A number of bytes.
* One or more qualifiers: static, dynamic, bounded.
2 番目のフィールドは、関数フレームの既知の部分のサイズに対応します。
修飾子 static は、関数のフレーム サイズが純粋に静的であることを意味します。これは通常、すべてのローカル変数が静的なサイズを持つことを意味します。この場合、2 番目のフィールドは関数スタックの使用率の信頼できる尺度になります。
修飾子「dynamic」は、関数のフレーム サイズが静的ではないことを意味します。これは主に、一部のローカル変数が動的なサイズを持つ場合に発生します。この修飾子が単独で出現する場合、2 番目のフィールドは関数スタック分析の信頼できる尺度ではありません。これがboundedで修飾されている場合、2番目のフィールドが関数スタック使用率の信頼できる最大値であることを意味します。
静的コード分析でこれについて十分な数値が得られなかった理由がわかりません。
任意の関数内のすべてのローカル変数を見つけるのは簡単で、各変数のサイズは、C 標準 (組み込み型の場合) または計算 (構造体や共用体のような複合型の場合) によって見つけることができます。
確かに、コンパイラーはパディング、レジスターへの変数の配置、または不要な変数の完全な削除など、さまざまな種類の最適化を実行できるため、答えが 100% 正確であることを保証することはできません。しかし、それが与える答えは少なくとも適切な推定値であるはずです。
グーグルで簡単に検索して見つけました スタックアナライザー しかし、私の推測では、他の静的コード分析ツールにも同様の機能があると思います。
100% 正確な数値が必要な場合は、コンパイラーからの出力を確認するか、実行時にそれをチェックする必要があります (Ralph が提案したように) 彼の返事)
すべての内容をまとめているのはコンパイラだけなので、実際に知っているのはコンパイラだけです。生成されたアセンブリを見て、プリアンブルにどのくらいのスペースが予約されているかを確認する必要がありますが、これでは実際には次のようなことが考慮されていません。 alloca
実行時に動作します。
組み込みプラットフォームを使用していると仮定すると、ツールチェーンがこれに取り組んでいることに気づくかもしれません。優れた商用組み込みコンパイラ (Arm/Keil コンパイラなど) は、スタック使用量のレポートを生成することがよくあります。
もちろん、通常、割り込みと再帰はそれらを少し超えていますが、誰かがスタックのどこかに数メガバイトのバッファを使ってひどい失敗を犯したかどうか、大まかな見当がつきます。
正確には「コンパイル時」ではありませんが、ビルド後のステップとしてこれを実行します。
- リンカーにマップ ファイルを作成させます
- マップ ファイル内の各関数について、実行可能ファイルの対応する部分を読み取り、関数のプロローグを分析します。
これは StackAnalyzer が行うことと似ていますが、はるかに単純です。実行可能ファイルまたは逆アセンブリを分析することが、コンパイラの出力を取得する最も簡単な方法だと思います。コンパイラは内部的にこれらのことを知っていますが、残念ながらコンパイラからそれを取得することはできません (コンパイラ ベンダーに機能の実装を依頼することもできます。また、オープン ソース コンパイラを使用している場合は、自分で実行するか、誰かに実行させることもできます)あなたのために)。
これを実装するには、次のことを行う必要があります。
- マップファイルを解析できるようにする
- 実行可能ファイルの形式を理解する
- 関数プロローグがどのようなものかを知っており、それを「デコード」できる
これがどれだけ簡単か難しいかは、ターゲット プラットフォームによって異なります。(埋め込み?CPU アーキテクチャはどれですか?何のコンパイラ?)
これらはすべて x86/Win32 で確実に実行できますが、このようなことを一度も行ったことがなく、すべてを最初から作成する必要がある場合、作業が完了して何かが動作するようになるまでに数日かかることがあります。
一般的にはそうではありません。理論的コンピューターサイエンスにおける停止問題は、一般的なプログラムが特定の入力で停止するかどうかさえ予測できないことを示唆しています。一般に、プログラムの実行に使用されるスタックを計算することはさらに複雑になります。それで:いいえ。もしかしたら特殊な場合かもしれない。
再帰レベルが任意の長さの入力に依存する再帰関数があり、すでに運が悪いとします。