50

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

gccコンパイラに-fstack-usageオプションと-fcallgraph-infoオプションが追加され、正確な「最大スタック使用量」を静的に計算するために使用できると言われました。(Botcazou、Comar、およびHainqueによる「GCCを使用したコンパイル時スタック要件分析」 )。

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

また、割り込みハンドラーのいずれも、最後の割り込みからの復帰命令まで割り込みを再度有効にしないようにしているので、割り込みハンドラーの再入を心配する必要はありません。

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

LM3S1968ARMCortex-M3のコードをコンパイルするためにgcc4.6.0でYAGARTOを使用しています。

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

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

4

7 に答える 7

26

GCCドキュメント:

-fstack-usage

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

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

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

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

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

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

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

于 2011-06-17T19:44:49.770 に答える
13

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

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

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

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

于 2011-06-17T21:44:46.297 に答える
8

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

main        1024     dynamic,bounded
functionA    512     static
functionB     16     static

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

main
    functionA
    functionB

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

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

つまり、このアプローチの使用には細心の注意を払う必要があります。

于 2014-09-27T21:10:33.047 に答える
6

結局、τεκの答えを実装するためのPythonスクリプトを書くことになりました。ここに投稿するにはコードが多すぎますが、githubで見つけることができます

于 2016-05-20T17:07:20.917 に答える
4

私は-fstack-usage-fcallgraph-infoオプションに精通していません。ただし、実際のスタック使用量は、次の方法で常に把握できます。

  1. (この実験では)適切なスタックスペースを割り当て、簡単に識別できるものに初期化します。私は好き0xeeです。
  2. アプリケーションを実行し、そのすべての内部パスをテストします(入力とパラメーターのすべての組み合わせによって)。「十分な長さ」以上実行させてください。
  3. スタック領域を調べて、スタックのどれだけが使用されたかを確認します。
  4. スタックサイズに加えて、ソフトウェアの更新やまれな条件に耐えられるように10%または20%にします。
于 2011-06-17T18:38:05.243 に答える
2

一般に、静的と実行時の2つのアプローチがあります。

静的:スクリプトを使用してプロジェクトをコンパイル-fdump-rtl-expand -fstack-usageし、*.expandスクリプトからプロジェクトをコンパイルして、各関数の呼び出しツリーとスタックの使用法を取得します。次に、呼び出しツリーのすべてのリーフを反復処理し、各リーフのスタック使用量を計算して、最大のスタック使用量を取得します。次に、その値をターゲットで使用可能なメモリと比較します。これは静的に機能し、プログラムを実行する必要はありません。これは再帰関数では機能しません。VLAアレイでは機能しません。静的に事前に割り当てられたバッファではなくリンカセクションで動作する場合sbrk()、動的な割り当ては考慮されません。動的な割り当ては、反対側からそれ自体で大きくなる可能性があります。ツリーにstacklyze.shというスクリプトがあり、このオプションを使用して調べました。

ランタイム:各関数呼び出しの前後に、現在のスタックの使用状況を確認します。を使用してコードをコンパイルします-finstrument-functions。次に、現在のスタック使用量を大まかに取得して操作する2つの関数をコードで定義します。

static unsigned long long max_stack_usage = 0;

void __cyg_profile_func_enter(void * this, void * call) __attribute__((no_instrument_function)) {
      // get current stack usage using some hardware facility or intrisic function
      // like __get_SP() on ARM with CMSIS
      unsigned cur_stack_usage = __GET_CURRENT_STACK_POINTER() - __GET_BEGINNING_OF_STACK();
      // use debugger to output current stack pointer
      // for example semihosting on ARM
      __DEBUGGER_TRANSFER_STACK_POINTER(cur_stack_usage);
      // or you could store the max somewhere
      // then just run the program
      if (max_stack_usage < cur_stack_usage) {
            max_stack_usage = max_stack_usage;
      }
      // you could also manually inspect with debugger
      unsigned long long somelimit = 0x2000;
      if (cur_stack_usage > somelimit) {
           __BREAKPOINT();
      }
}
void __cyg_profile_func_exit(void * this, void * call) __attribute__((no_instrument_function)) {
      // well, nothing
}

各関数の作成前と作成後-現在のスタックの使用状況を確認できます。関数は、関数内でスタックが使用される前に呼び出されるため、このメソッドは現在の関数スタックの使用状況を取得しません。これは1つの関数であり、あまり機能しません。どの関数であるかを取得してからスタックを取得することで、なんとか軽減できます。で使用し-fstack-usage、結果に追加します。

于 2021-05-05T09:08:45.323 に答える
2

一般に、コールグラフ情報をによって生成された.suファイルと組み合わせて-fstack-usage、特定の関数から開始する最も深いスタック使用量を見つける必要があります。またはスレッドエントリポイントから開始するとmain()、そのスレッドの最悪の場合の使用法がわかります。

ここで説明したように、ここからPerlスクリプトを使用して、このようなツールを作成する作業が完了しました。

于 2021-05-05T22:31:37.410 に答える