原点に戻る - 効率を高める方法としてのトレースとデバッグ

組込みソフトウェアのデバッグは、特定のバグを追跡するという点でも、一般的なプロジェクト作業としても、多くの場合、時間のかかる作業です。 それはしばしば、必死さ、汗、そして呪術的思考が混在しています。

この記事では、デバッグの煩わしさを完全になくすことはできないかもしれませんが、少なくとも呪術的な部分は最小限に抑えることができるテクニックや戦術を説明します。
あなたが組み込みソフトウェアの世界に比較的初心者であるなら、有用な情報をいくつか入手できるかもしれません。
あなたが経験を積んだプロであれば、おそらくこれらのトピックを既にご存知でしょうが、すでに知っていて実践すべきテクニックを再発見することができるかもしれません。

ベースとなるコード品質

新しく書かれたソフトウェアが完全にバグフリーであることはほとんどありません。
ただし、コードで対処しなければならない問題の数を減らす、すなわちデバッグの手間を減らすために前もって実行できるアクションがあることも分かっています。
最初に行うべきことは、コードの健全性ためにいくつかの基本的なルールを定めることです。
いくつかのルールの概要は次のとおりです。

  • コーディング標準を使用してください。MISRAとCERT Cは、はじめに着手するのに適しています。MISRAへの準拠に努めることで、C および C++ に内在するいくつかの落とし穴を回避することができます。
    そしてCERT Cは回避すべき事柄のリストにセキュリティの観点を追加します。このアドバイス最初の結論はコンパイラの警告に細心の注意を払うことです。
    2番目の結論は、自動チェッカーを使用して規則順守をチェックすることです。
  • ハードウェア抽象化レイヤー(HAL)を使用してください。ハードウェアを操作するコードをアプリケーションコード内に直接書くことを避けてください。
    たとえば、タイマを開始する必要がある場合は、タイマレジスタを直接操作するのではなく、HAL関数を呼び出してタイマを設定および開始してください。
    このアドバイスに従うことには多くの利点があります、
    そのうちの1つは、コードベースの異なる箇所で、いくつかの異なるタイマ呼び出しを処理する場合等にコピー/ペースト エラーとタイプミスをほぼ完全に回避することができます。
    さらに、コンパイラは最適化により優れた処理を行うことができ、コードをインライン化することもできるため、実際にはパフォーマンスとコードサイズについて両方を得ることができます。このアドバイスの当然の結論は、個々のHAL機能を可能な限り小さく保つことです。「スイス軍のナイフ」(多くの責任を持つ大きな機能)の作成は避けてください。
    小さく単一目的の関数は理解しやすく、管理しやすいだけでなく、少し直感に反するかもしれませんがコンパイラが最適化しやすくなることがよくあります。
  • メモリをどのように使用するかをさらに考えてください。例えば、動的メモリ管理は本当に必要か、スタックは本当に複雑なデータ構造を格納するのに適した場所か、機能安全と高整合性ソフトウェアの標準規格は、動的メモリ管理およびスタックに複雑なデータ構造や大きなデータ構造を格納することに対して強く忠告することがあります。これには十分な理由があります。
  • ツールチェーンが最悪の場合のスタック深度分析をサポートしている場合、その機能を調べて使用するための投資はすぐに報われます。

printfするかどうかは問題ではありません

組み込みソフトウェアを開発およびデバッグする場合、最初に理解する(または覚えておく)ことの1つは、ターゲットでのコードの実行がデバッガを介して実行される環境で行われる可能性が高いことです。たとえば、IDEで作業している場合、プログラムを実行する最も簡単な方法はデバッガを起動することです。これは明らかなことですが、それを認識することなくデバッガのすべての機能をすぐに利用できることを意味します。

本題に入るために、ブレークポイントの威力を調べてみましょう。
しかし、その前に、デバッグツールとしての由緒ある printf に少し触れましょう。
printf を使わない最も重要な理由は、コードに printf ステートメントを追加すると、
コードがどのようにコンパイルされるかに劇的な影響を与える可能性があるからです。printf は関数呼び出しであるだけでなく、呼び出しの引数も考慮しなければなりません。これは、スタックとレジスタの使用法が全く異なるものになり、多くのコンパイラの最適化が行われないことを意味します。特にステートメントがタイトなループの中にある場合は特に注意が必要です。コードが複雑であるか、実装定義であり、C / C ++標準で未定義のC / C ++動作に依存している場合、これは予期しない結果をもたらす可能性があります。起こり得ることは、コードにprintfを追加したときに、コードが完全に適切に動作し、プリントアウトを削除すると壊れます、またはその逆です。ちなみに、これはMISRAへの準拠に努めるための非常に良い理由です。
2番目のの理由は、printfはデータを表示することしかできないので、かなり弱いツールであるということです。3番目の理由は、プリントアウトの動作を変更したり、プリントステートメントをさらに追加する場合は、アプリケーションを再ビルドしてターゲットにダウンロードしなおすことが必要です。最後に、ある時点で、コードベースを確認して、追加したすべてのプリントステートメントを削除する必要があります。それらがすべて#ifdefで保護されている場合でも同様です。

ブレークポイントの能力

説法から一休みして、利用可能なさまざまなタイプのブレークポイントを見てみましょう。ブレークポイントは、最も単純な形では、特定のソース文のストップサインとなり、適切な場所に到達すると無条件に実行が中断されます。
きちんとしたデバッガであれば、変数、レジスタ、コールスタック、一般的なメモリの内容を調べることができます。このようなコードブレークポイントはそれ自体非常に便利ですが、実行が停止するかどうかを真理値が決定する式に関連付けることもできます。

そうすることで、実行がブレークポイントの場所を通過するたびに関心のある変数を調べるのではなく、関心のあるケースに焦点を当てることができます。
例えば、ループインデックス変数の特定の値の範囲で何が起こっているのかを詳しく調べたい場合、そのコードをヒットするたびに停止するのではなく、インデックスがその範囲内にあるときだけ停止するように式を設定することができます。もちろん、スコープ内の任意の変数に基づいて、より複雑な停止式を構築することもできます。

場合によっては、1つ以上の式の値を確認する必要があります。これを行う簡単な方法は、ログブレークポイントを使用することです。ログブレークポイントは、実行を停止することなくデバッグログウィンドウにメッセージを表示することだけを目的としたブレークポイントです。基本的には、デバッガが提供するprintfであり、メッセージを生成するかどうかを判断するためにブール演算式と組み合わせることもできます。

AH breakpoint

ブレークポイントの非常に強力なタイプは、データ ブレークポイントです。データ・ブレークポイントは、特定の変数またはメモリ・ロケーションにアクセスされたときにトリガされます。これは、特定の場所のデータ値が期待したデータではない理由を調べる場合に非常に役立ちます。そのような状況に陥る理由はいくつかありますが、原因の一つはポインタです。ポインタを使用(または乱用)すると、ある時点でポインタの計算が間違ってしまう可能性がかなりあります。また、間違ったアドレスからの読み取りまたは書き込みを行っても、プログラムがフォールオーバーしない場合でも、非常に奇妙な結果が生じる可能性があります。
この種の問題は、実際のバグと影響が発生する場所が関係ないことが多いので、デバッグするには非常に難しい場合があります。

データブレークポイント(または、任意の種類のブレークポイント)をコールスタックウィンドウと組み合わせると、非常にわかりやすくなります。コールスタックウィンドウには、処理がどこから来たのかが表示されます。また、コールチェーンを上下に移動してパラメータの値を調べることもできます。

プログラムを実行しているデバイスあるいはデバッグプローブによっては、これらのタイプのブレークポイントが常に利用できるとは限らないことに注意してください。

一部のターゲットはメモリのライブ読み込みをサポートしているので、デバッガは標準のデバッグプローブで実行中に変数値やその他の情報を連続的に表示することができます。

啓発への道

少し余分なのバズワードや形容詞に耐えられるのであれば、本当に素晴らしいデバッグツールの話に数行を費やしましょう。トレースは、割り込み情報やその他のハードウェアイベントなど、デバイス上の実行やその他のタイプのデータフローを記録する方法です。例えば、イベントデータを組み合わせたものをタイムラインで表示すると、システムの動作が非常にわかりやすくなります。:必要なときに割り込みが発生していますか?それは他のアクティビティとどのように相関していますか?

AH trace

トレースを通常のデバッグよりも少し複雑にしているのは、トレース技術には多くの種類があり、トレースデータにアクセスする方法も様々だということです。さらに、トレース対応のプローブが必要な場合もあります。そのため、自分のニーズに最適な方法でトレースの力を活用するためには、プロジェクトの最初にトレースを使用するために何をする必要があるかを考えることが有益です。

  • 考慮すべきことの一つは、デバイスの選択です。それはトレース機能を持っているか、持っている場合はどのような種類のものか?
  • そのデバイスにはトレース機能があるバージョンとないバージョンがありますか?もしそうであれば、コストを抑えるために、ボードの開発バージョンをトレース付きでビルドして、トレース無しで生産することもできます。
  • トレースは、プロファイリングやコードカバレッジデータのイネーブラーにもなるので、この分野でのニーズを前もって考えておくことは有益です。

高品質のトレースツールは、トレースの複雑さに悩まされることなく、利用可能なすべてのトレース情報を使用できるように設計されています。ただし、ハードウェア側のニーズを把握する必要があります。しかしながら、デバッグとコード品質のツールとして最初にトレースに時間とリソースを費やすと、最初のトリッキーな問題が発生したときに報われます。

効率を高める方法

この記事のトピックの中には、些細なことのように思えるものもあるかもしれませんが、トリッキーな問題に対する最良の解決策は、多くの場合、そのようなものです。
ソフトウェアの問題の根本的な原因を見つけるのには、数日から数週間かかることもあれば、素早く簡単に解決できることもあります。 後者の可能性を高める1つの方法は、常に printf 文に手を伸ばすのではなく、コードベースの知識をデバッガやトレースツールの機能と組み合わせてどのように使うのがベストなのかを考えることです。
時間が経つにつれて、このような作業方法が、安心感は言うまでもなく、生産性と効率を大幅に向上させることがわかります。

 

Article written by Anders Holmberg, General Manager Embedded Development Tools at IAR Systems

申し訳ございませんが、弊社サイトではInternet Explorerをサポートしていません。サイトをより快適にご利用いただくために、Chrome、Edge、Firefoxなどの最新ブラウザをお使いいただきますようお願いいたします。