web-dev-qa-db-ja.com

gccを使用した組み込みシステムでの最大スタック使用量を決定する方法は?

組み込みシステムのスタートアップコード(main()関数にジャンプする前に初期スタックポインターをロードするコード)を書いていて、アプリケーションが使用するスタックのバイト数(またはそれ以上)を通知する必要があります、保守的な見積もり)。

Gccコンパイラーに-fstack-usageオプションと-fcallgraph-infoオプションが追加され、正確な「最大スタック使用量」を静的に計算するために何らかの方法で使用できると聞いたことがあります。 ( "GCCを使用したコンパイル時スタック要件分析" Botcazou、Comar、およびHainqueによる)。

Nigel Jonesは、組み込みシステム(「Computing your stack size」2009)では再帰は非常に悪い考えだと言っているので、このコードで相互再帰関数を作成しないように注意してきました。

また、私の割り込みハンドラーが最後の割り込みからの復帰命令まで割り込みを再度有効にしないことを確認しているので、再入可能な割り込みハンドラーについて心配する必要はありません。

再帰または再入可能な割り込みハンドラーがなければ、最大スタック使用量を静的に決定できるはずです。 (そして へのほとんどの答えは、最大スタック使用量を決定する方法ですか? は適用されません)。私の理解は、私(またはできれば、実行可能ファイルを再構築するたびに自動的に実行される私のPCのコードのビット)が、優先順位の高い割り込みによって中断されていない場合の各割り込みハンドラーの最大スタック深度と、中断されていない場合のmain()関数のスタック深度。次に、それらをすべて追加して、合計(最悪の場合)の最大スタック深度を見つけます。これは(私の組み込みシステムでは)main()バックグラウンドタスクが最低優先度の割り込みによって中断されたときに最大深度にあり、次にその優先度が次に低い優先度によって中断されたときに最大深度にあるときに発生します割り込みなど。

YAGARTOとgcc 4.6.0を使用してLM3S1968のコードをコンパイルしていますARM Cortex-M3。

では、gccで-fstack-usageオプションと-fcallgraph-infoオプションを使用して最大スタック深度を計算するにはどうすればよいですか?または、スタックの最大使用量を決定するためのより良いアプローチはありますか?

(Keilコンパイラを対象としたほぼ同じ質問については、 組み込みシステムでの最大スタック使用量を決定する方法 を参照してください。)

41
David Cary

GCCドキュメント:

-fstack-usage

関数ごとに、コンパイラーにプログラムのスタック使用状況情報を出力させます。ダンプのファイル名は、auxnameに.suを追加することによって作成されます。 auxnameは、明示的に指定されていて実行可能ファイルでない場合、出力ファイルの名前から生成されます。それ以外の場合は、ソースファイルのベース名になります。エントリは3つのフィールドで構成されています。

  • 関数の名前。
  • バイト数。
  • 1つ以上の修飾子:静的、動的、制限付き。

Static修飾子は、関数がスタックを静的に操作することを意味します。固定バイト数が関数の入り口でフレームに割り当てられ、関数の出口で解放されます。それ以外の場合、関数ではスタック調整は行われません。 2番目のフィールドは、この固定バイト数です。

動的修飾子とは、関数がスタックを動的に操作することを意味します。上記の静的割り当てに加えて、関数の本体でスタックの調整が行われます。たとえば、関数呼び出しの周りの引数をプッシュ/ポップします。制限付きの修飾子も存在する場合、これらの調整の量はコンパイル時に制限され、2番目のフィールドは、関数によって使用されるスタックの総量の上限です。存在しない場合、これらの調整の量はコンパイル時に制限されず、2番目のフィールドは制限された部分のみを表します。

-fcallgraph-infoへの参照が見つかりません

-fstack-usageおよび-fdump-tree-optimizedから必要な情報を作成できる可能性があります

-fdump-tree-optimizedの各リーフについて、その親を取得し、スタックサイズの数値を合計します(この数値は、「動的」で「制限付き」ではないすべての関数に当てはまることに注意してください)。これらの値のうち、これがスタックの最大使用量になります。

21
τεκ

誰もより良い答えを思い付かない場合に備えて、これらのオプションとツールを使用した経験はありませんが、コメントに書いた内容を他の質問に投稿します。

GCC 4.6は、関数ごとのスタック使用統計を提供する-fstack-usageオプションを追加します。

この情報を cflow または同様のツールによって生成されたコールグラフと組み合わせると、探している種類のスタック深度分析を取得できます(これを行うには、スクリプトをかなり簡単に作成できます)。 。スクリプトにスタック使用情報を読み取らせ、関数が使用するスタックを含む関数名のマップをロードします。次に、スクリプトでcflowグラフ(簡単に解析できるテキストツリーにすることができます)をウォークし、コールグラフの各ブランチの各行に関連付けられたスタック使用量を合計します。

したがって、これはGCCで実行できるように見えますが、適切なツールのセットを一緒にまとめる必要がある場合があります。

12
Michael Burr

かなり遅いですが、これを見ている人にとっては、fstack-usageからの出力とcflowなどの呼び出しグラフツールの組み合わせに関する回答は、動的スタックの場合、動的割り当ての場合は非常に不正確になる可能性があります。動的スタックのタイミングに関する情報がないためです。割り当てが発生します。したがって、どの関数に値を適用すべきかを知ることはできません。不自然な例として、(簡略化された)fstack-usage出力が次の場合:

main        1024     dynamic,bounded
functionA    512     static
functionB     16     static

非常に単純な呼び出しツリーは次のとおりです。

main
    functionA
    functionB

これらを組み合わせる素朴なアプローチでは、メイン-> functionAが最大スタック使用量のパスとして1536バイトで選択される可能性があります。しかし、main()での最大の動的スタック割り当てが、recordのような大きな引数をfunctionB()に直接プッシュすることで、functionBを呼び出す条件付きブロックのスタックに直接スタックする場合(私はすでにこれは不自然であると述べました)、実際にはmain-> functionBは最大スタック使用量のパスで、1040バイトです。既存のソフトウェア設計、およびスタック上のすべてを渡す他のより制限されたターゲットによっては、累積エラーにより、最大スタックサイズが大幅に誇張されていると主張する完全に間違ったパスをすぐに見つかる可能性があります。

また、割り込みについて話すときの「再入可能」の分類によっては、一部のスタック割り当てを完全に見逃してしまう可能性があります。たとえば、多くのColdfireプロセッサのレベル7割り込みはエッジセンシティブであるため、割り込み無効マスクを無視します。そのため、セマフォを使用して命令を早期に残すと、再入可能とは見なされない場合がありますが、初期スタック割り当てはまだ発生します。セマフォがチェックされます。

つまり、このアプローチを使用する場合は、非常に注意する必要があります。

6
Adam Palaniuk

結局、pythonτεκを実装するためのスクリプト answer を記述しました。ここに投稿するにはコードが多すぎますが、 github で見つけることができます。

6
PeterM

-fstack-usageおよび-fcallgraph-infoオプション。ただし、次の方法で実際のスタック使用量を把握することは常に可能です。

  1. (この実験のために)十分なスタックスペースを割り当て、それを簡単に識別できるものに初期化します。好き 0xee
  2. アプリケーションを実行し、そのすべての内部パスをテストします(入力とパラメーターのすべての組み合わせによる)。 「十分に長い」以上実行してみましょう。
  3. スタック領域を調べ、使用されたスタックの量を確認します。
  4. スタックサイズに加えて、ソフトウェアの更新とまれな条件を許容するために10%または20%を加えます。
3
wallyk