(2023/10/2)
[連載16] 関数トレース
今回は、関数トレースについてご紹介します。
「ここに至るまでの経緯」を知るための機能です。
デバッガの機能の一つに、トレースの情報を取得する機能があります。
一般的に思いつくのは、ICEにトレースプローブを付ける方法です。
マイコンチップのトレース用端子とトレースアダプタを接続し、そこから分岐情報やバス情報等拾ってトレースを行います。
この場合、トレースの情報を抽出し出力するのは、ハードウェアによって行われるため、どのようなファームウェアに対してもトレースが取れます。
ブート回り、ベアメタルファームウェア、等、どんな場合にでもトレース用端子を接続しさえすればOK。
思えば、筆者も大変にお世話になりました。
評価ボードを大きくしたくないという理由でトレース用端子がテストピンにまったく出ていない場合でも、その端子を直接極細プローブで引っ掛けて無理矢理トレースを取ったりしていましたね。。。
昔のマイコンチップは端子間がもう少し大きかったなぁ。。。(遠い目)
おっと、昔を懐かしんでしまいました。
話を戻します。
ちなみに、端子を使わなくてよいトレース機能もあります。
デバイスの内部にあるトレースバッファに、情報をためていく方式です。
しかしながら、トレースバッファはあまり大きくはなく、欲しいデータを取ることが難しかったりします。
ここまでは、ハードウェア(端子やデバイス内蔵バッファ)によるトレース機能について、です。
ここからは、関数トレースという機能について。
先のハードウェアによるトレース機能でも、デバッガでフィルタリングし関数トレースを行う事は可能です。
ですがSOLIDでは
特別なハードウェア不要の関数トレース機能
を実現しています。
トレース端子もトレースプローブも不要なので、今すぐここで動かせます。
早速見ていきましょう!
SOLID-IDEの関数トレース機能については、このURLに記載されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/function_trace.html#user-guide-function-trace
ここに記載されている通りに、試してみます。
今回も前回に引き続き、Cortex-A9搭載ボード×PARTNER Jet2の組み合わせを使用します。
動作させるプログラムについても、今まで使用してきたサンプル「KZM-A9GT-demo-sample1」を使用します。
準備として、以下4点を行います。
・関数トレースを有効化するためにプロパティシートを変更
・関数トレース情報を蓄積するためのメモリ領域割り当て
・インストルメンテーション(Instrumentation:計装。計測のためのコード追加)有効化
・関数へのパッチ適用
「関数へのパッチ適用」については後ほど触れますが、ここでは「トレース情報を取る対象とする」という事だと思っておいて頂ければOKです。
関数トレースを有効化するために、プロパティシートを変更します。
まず、プロパティシートを開きます。
開き方は以下に記載されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/reload-solution-propsheet.html#id4
ソリューションエクスプローラ上でソリューションを右クリックし、表示されるメニューから「ソリューションプロパティシートを開く」を選択すればOKです。
<PropertyGroup>内にを追加し、値をtrueにします。
そして、ソリューションを保存し再読み込みします。
次に、関数トレース情報を蓄積するためのメモリ領域を割り当てます。
メモリマップデザイナを使用します。
smmファイルをダブルクリックすれば開きます。
見てみます。
今回、既にFTRACEありましたので、このままにします。
FTRACEがない場合は、追加すればOKです。
サイズは、とりあえず10MBになっていますが、もっと小さくすることもできます。
次に、インストルメンテーション有効化です。
対象とするプロジェクトのプロパティから有効化できます。
[C/C++] → [XRay]を選択し、以下2点を設定します。
・XRayインストルメンテーションの有効化:はい
・命令のしきい値:とりあえずここでは10にしました。(設定しない場合は200になる)
「命令のしきい値」とは、関数が構成される命令数のことです。
指定された命令数以上の関数にのみ、関数トレースが適用される仕組みです。
「命令のしきい値」を小さくすれば、小さい関数にでも適用されます。
ですが、その分、オーバーヘッドにもなってしまうので、注意が必要です。
小さい関数は、頻繁に動くことが多いかと思います。
(レジスタに何やら設定するだけの関数、とか。。。)
最後、関数へのパッチを有効化します。
まず、再ビルドし実行し、適当なところでブレークします。
ここでは、ルートタスク内でタスクを生成した直後くらいでブレークしてみます。
[デバッグ]メニュー → [ウインドウ] → [関数トレース] を選択します。
[Patch all]をクリックすると、対象のプロジェクト内のすべての関数(10命令以上の)に対しパッチを適用します。
準備は整いました。
関数トレースを取ってみます。
ブレークを削除し、実行!
そして、強制ブレーク!
トレース情報が集まったはずです。
吸い上げてみましょう。
関数トレースウインドウから[Save trace data]をクリックします。
これでデータが収集されました。
収集日のところを、クリックします。
CPU0をダブルクリックして、展開してみます。
トレース結果が表示されました!
上段:タスク遷移
下段:呼び出された関数名
このウインドウの詳細はこちらに記載されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/function_trace.html#id8
先の例では、命令数が10命令以上の関数すべてをトレース取得の対象にしました。
ですが、
トレース取得の対象にする=トレース収集のオーバーヘッドが存在する
なので、できれば対象範囲は少ない方が良いです。
それにあまり沢山取れてしまっても、後から見るのが大変だったりもしますよね。。。
という事で、トレース取得したい対象を選択する方法についてご紹介します。
先に説明した、「トレース取得の対象とする関数の命令数を適切に設定する」以外にも、選択する方法があります。
先程の関数トレースウインドウで、[Patch all]を選択しましたが、そうではなく関数単位に選択することも可能です。
[Patch]を選択します。
このように、選択することが可能です。
例えばイベントフラグの推移をトレースしたい場合、ここでイベント操作系関数のみトレースする、という使い方もできますね。
先のご紹介では、「プロジェクト」のプロパティから[XRay]を有効にしましたが、
実はファイル単位でも可能です。
対象とするファイルのプロパティから、[XRay]を有効にすることができます。
こうすると、選択したファイルに書かれている関数をまるっと全部、トレース対象とすることができます。(命令数が、設定したしきい値以上である全関数です)
実現方法について少し触れます。
Clangコンパイラの” XRay instrumentation”という機能を使って実現しています。
以下に詳細記載されています。
https://llvm.org/docs/XRayExample.html
関数の中にトレースができるような命令を埋め込む機能です。
Xray を有効にしたときに、コンパイラは、命令数のしきい値を越える関数にだけ、先頭と最後尾に、「数個の連続するNOP命令」を出力します。
これが準備③に該当します。
デバッガは、RAM 上の 「数個のNOP命令」 の部分を、「関数トレースのランタイムを呼び出すコード」に書き換えます。準備④に該当します。
書き換え対象とする関数は、準備④で、パッチ適用を行った関数となります。
ここで使われている「パッチ」とは、「バイナリパッチを当てる」という意味です。
関数トレースのランタイムは、SOLID-OSが持っています。
さらに、プログラムが動作しブレークするとします。
この時、SOLID-IDEの方で、トレース情報を吸い上げ、GUIに表示します。
まさに三位一体、OSとデバッガが協力して、Clangコンパイラの機能を使いこなしています。
実行速度についても少し補足します。
命令を埋め込むという事は、実行速度が落ちるようにも思いますが、トレース対象関数ではない場合はNOP数個埋め込むだけですよね。
近年の高速なCPUであれば、NOP命令の実行には、ほとんどCPUのサイクル数を使わないので、メモリサイズの点で許されるのであれば「製品版ソフトも XRay有効でビルド」し、その上で、市場で問題が起きたときに関数トレースを取るといった使い方も考えられます。
今回は、関数トレースをご紹介しました。
トレース用ハードウェアを別途用意する必要がなく、トレース専用端子を占有することも必要ないので手軽だし、逆に必要部分のみトレースできるのも特にOS上で動いている場合に不要なコードまでトレースされているという事を防げるので嬉しい部分ですね。
また、実現方法のあたりで少し触れたとおりOSとデバッガを一緒に開発していなきゃできない機能なので、なかなかレアな機能だと思いました。
それに、なんだかいろいろ設定するところがあって面倒かな?と思いきや、実際にやってみると結構簡単に使えたのも良かったです。
次回は、ローダー機能についてご紹介する予定です。