数千行から数百万行のコードで構成される組み込みソフトウェアは、より高度なものへと進化を続けていますが、堅牢性が高く高速で動作する正しいソフトウェアを実現するという全体的な目標は変わりません。高速で動作するソフトウェアは、利用可能な CPU とメモリリソースを最適に管理する必要がありますが、メモリ容量の中でも特に RAM 容量に制限のある組み込みシステムでは、これが課題となります。このため、スタック解析とヒープ解析を実行して RAM 使用量を解析することが重要です。開発者が手作業でスタックとヒープの負荷を推定するのは、小さなプログラムであっても難しい作業になります。正しく推定できない場合、スタック オーバーフローや未定義の動作につながる可能性があります。不要なオーバーヘッドを避けるよう、メモリ割り当ての使用に関するベストプラクティスを定めたコーディング規約が存在するのはそのためです。しかし、スタックは依然として、必要な RAM コンポーネントであり、最適な方法で使用する必要があります。

組み込みシステムにスタック解析が必要な理由

スタック オーバーフローは、利用可能なスタックがコードの要求よりも小さい場合に生じます。しかし、必要以上に大きなスタックで環境を構成すると、メモリが浪費されます。セーフティ クリティカルなアプリケーションにおけるワーストケースのスタック使用量を継続的かつ一貫して推定し、ソフトウェアの RAM 不足を防ぐことは、開発者にとって不可欠な手順です。

スタックを正しく推定できない場合のリスク。

スタックを正しく推定できない場合のリスク。

スタック解析で推定を得る方法

手作業によるスタックの推定

手作業によるスタック解析の推定が役に立つ場合もありますが、より複雑なシステムではそれは難しいこともあります。関数呼び出しの深さ、すべてのローカル変数の詳細、実行中いつでも発生する割り込みフレームのサイズなどを十分に理解する必要があります。この手順には時間がかかり、ミスも発生しがちです。静的コード解析ツールですぐに計算できることを考えれば、手作業で計算する必要はありません。

静的コードアナライザーの使用

開発者は、静的コードアナライザーを使用してスタック使用量を予測できます。解析ツールは、関数呼び出しの深さ、ローカル変数と戻り値パラメーターのスタック推定、入れ子にされた割り込み、実行中に発生した割り込みのサイズを解析できます。静的コードアナライザーを使用する利点は、スタック解析の推定とともに、コーディングルール違反、ランタイムの欠陥、コーディングの複雑度に対応できることです。数分で完了するため、開発者はスタック消費量を手作業で計算する時間を削減できます。

ターゲットでのテストと計測

静的アナライザーを使用すると、開発中にスタックの消費量を推定できます。しかし、実際のハードウェア上でスタック消費量の結果を得るのが最善です。多くの開発環境には、ハードウェアのエミュレーション機能が備わっており、リアルタイムのスタック解析を実行する機能が用意されています。実際のハードウェア上でスタック解析を行い、オーバーフロー シナリオを作成してフェイルセーフ ルーチンをテストすることが重要です。ここで最大の問題は、静的解析ツールでスタック解析を行うタイミングと、実際のターゲット上でスタック解析を行うタイミングです。

スタック解析のタイミング

スタック解析の実行は、ソフトウェア開発のライフサイクルにおける継続的なプロセスです。ソフトウェア開発のライフサイクルの最後になって別の品質評価チームがスタック使用量を推定するという手順では、開発作業全体にリスクが生じることになりかねません。また、開発サイクルの後期に問題を解決する手順では、エラーや時間の浪費を引き起こすおそれがあり、ハードウェアとソフトウェアのどちらの設計変更に対処すべきかの判断に混乱が生じるおそれもあります。スタック解析を行ううえで最も重要なマイルストーンは次のとおりです。

  • 新機能の追加時

    ソフトウェアに追加された新機能はすべて、スタック使用量の増加の原因となります。開発者は、新機能のスタック使用量を常に注視する必要があります。
    1. スタック解析の実行、デバッグ、および複雑なコードの修正: 主要な機能を実装するたびに、開発者は特定のソフトウェア コンポーネントまたはソフトウェア モジュールの静的アナライザーをローカルで使用し、基盤ソフトウェアと実装したソフトウェア間のスタック消費量の増加を評価できます。
    2. 開発プロセス全体でのスタック解析の監視: QA チームとプロダクトオーナーは、静的アナライザーを使用して継続的インテグレーション (CI) パイプラインのスタック推定を行い、その結果をダッシュボードに表示できます。このプロセスは、ソフトウェア開発のライフサイクルにおけるスタック解析の追跡に役立ちます。
    3. スタック使用量を最低限に抑える適切な手法の適用: 品質ゲートによって、動的メモリ割り当ての条件付きの使用を定めた MISRA™ および AUTOSAR コーディング ガイドラインの違反を回避できます。
  • ソフトウェアリリースの前

    静的アナライザーによるスタック推定は、スタック消費量の制御を示す説得力のある根拠となります。毎回のソフトウェアリリースの前に、標準的な運用負荷、最小負荷、および最大負荷の下において実際のターゲット上でスタック解析を実行し、スタック使用量に関して包括的に理解します。また、スタックのオーバーフローやアンダーフローが発生した場合のフェイルセーフ手順の検証も、極めて重要です。

スタック推定における Polyspace の役割

Polyspace Code Prover™ は、各関数内ローカル変数のサイズの大小について保守的かつ楽観的な推定を行い、関数レベルとプログラムレベルの両方でスタック使用量の最大値と最小値を算出します。この解析では、関数の戻り値のサイズ、関数のパラメーターのサイズ、ローカル変数のサイズ、メモリ配置のために導入された追加のパディングを考慮します。

Polyspace デスクトップ上のスタック解析のコードメトリクス。

Polyspace デスクトップ上のスタック解析のコードメトリクス。

オーバーシュートしたスタック使用量を把握しデバッグするには、開発者は Polyspace® をローカルで実行して関数呼び出しの深さをたどることにより、スタック オーバーシュートの正確な原因を特定し、利用可能なリソースを最適に活用することでスタック使用量を削減できます。

関数 table_loop() の呼び出しツリーと高い値を示すスタック使用量の推定値。

関数 table_loop() の呼び出しツリーと高い値を示すスタック使用量の推定値。

開発プロセス全体でのスタック解析の監視

Polyspace Access™ は、Web ブラウザー上でグラフィカル ユーザー インターフェイスを表示する結果データベースサーバーです。CI プロセスでは、Polyspace Server™ のスタック解析をトリガーして、スタック使用量の推定を生成できます。この結果は、結果データベースにアップロードできます。QA チームとプロダクトオーナーは、グラフィカルなフロントエンドでスタック使用量を継続的に確認し、利用可能なスタックリソースを過度に使用した場合に必要なアクションを実行できます。

Polyspace Access のプロジェクトレベルのスタック推定。

Polyspace Access のプロジェクトレベルのスタック推定。

次のステップとして、スタック使用量の多い関数を確認し、さらなる調査とデバッグのために特定の関数を開発者に割り当てます。Polyspace では、Jira のようなバグ トラッキング ツールで開発者に関数を割り当てる前に、解析結果のステータス、重大度、コメントを指定できます。

Polyspace Access の関数レベルのスタック推定と結果レビュー ダッシュボード。

Polyspace Access の関数レベルのスタック推定と結果レビュー ダッシュボード。

スタック使用量を最低限に抑える適切な手法の適用

量産コードでは、MISRA C™、MISRA C++、AUTOSAR C++ などのコーディング規約に違反しないことが義務付けられています。これらのコーディング規約は、動的メモリ割り当ての禁止を定め、静的メモリ割り当てを最適化する特定のユースケースを推奨しています。Polyspace Bug Finder™ でベストプラクティスの違反を特定し、開発者はローカルで、プロダクトオーナーは Polyspace Access でそれらを監視できます。以下のコーディングルールは、Polyspace Bug Finder で解析できる静的メモリ割り当てのベストプラクティスを規定しています。

コーディング ガイドライン

ルール

説明

MISRA C: 2004

20.4

Dynamic heap memory allocation shall not be used.

MISRA C: 2012

21.3

The memory allocation and deallocation functions of <stdlib.h> shall not be used.

MISRA C++: 2008

18-4-1

Dynamic heap memory allocation shall not be used.

AUTOSAR C++14

A18-5-1

Functions malloc, calloc, realloc, and free shall not be used.

AUTOSAR C++14

A18-5-2

Non-placement new or delete expressions shall not be used.

AUTOSAR C++14

A18-5-3

The form of the delete expression shall match the form of the new expression used to allocate the memory.

AUTOSAR C++14

A18-5-4

If a project has sized or unsized version of operator “delete” globally defined, then both sized and unsized versions shall be defined.

AUTOSAR C++14

A18-5-5

Memory management functions shall ensure the following: (a) deterministic behavior resulting with the existence of worst-case execution time, (b) avoiding memory fragmentation, (c) avoid running out of memory, (d) avoiding mismatched allocations or deallocations, and (e) no dependence on non-deterministic calls to kernel.

AUTOSAR C++14

A18-5-7

If non-real-time implementation of dynamic memory management functions is used in the project, then memory shall only be allocated and deallocated during non-real-time program phases.

AUTOSAR C++14

A18-5-8

Objects that do not outlive a function shall have automatic storage duration.

AUTOSAR C++14

A18-5-9

Custom implementations of dynamic memory allocation and deallocation functions shall meet the semantic requirements specified in the corresponding “Required behavior” clause from the C++ Standard.

AUTOSAR C++14

A18-5-10

Placement new shall be used only with properly aligned pointers to sufficient storage capacity.

AUTOSAR C++14

A18-5-11

“operator new” and “operator delete” shall be defined together.

スタック使用量は、コードの循環的複雑度、入れ子関数呼び出しの数、関数内変数の数などが増えるにつれて増大します。Polyspace では、スタック使用量に影響する多くの変数を制御し、コードの複雑度のしきい値を設定できます。

コードの複雑度に対するしきい値の設定。

コードの複雑度に対するしきい値の設定。

Polyspace Bug Finder には、静的および動的メモリ割り当てに対する多くの実行時チェック機能が備わっています。高、中、低優先度の欠陥をすべて解決することで、メモリ割り当てで問題が発生するリスクを軽減できます。

静的および動的メモリの実行時チェック。

静的および動的メモリの実行時チェック。

スタック使用量を計算する方法にかかわらず、スタックのサイズは大きめに取ることを推奨します。このアプローチによって、テスト中に検出されなかったスタック オーバーフローによるシステムの脆弱性を回避できます。

スタック オーバーフローの脆弱性は、多くの組み込みアプリケーションが現場で説明できない動作を示す重大な原因となります。適切なタイミングで適切なツールを使用し、ベストプラクティスに従うことで、スタック オーバーフローに対するソフトウェアの信頼性を向上させることができます。