サニタイザは組込み開発でも使えるのか?― SILSで見つける車載ECUのメモリー破壊 ―

GCCやClangなどのコンパイラが提供する、不正なメモリーアクセスや未定義動作を実行時に検出する仕組みが「サニタイザ」です。組込み機器の実機では利用に制約がありますが、SILSと組み合わせることで、アプリケーションをシステムとして動作させながら検査できます。

本記事では、SILSツール「GSIL」上でサニタイザを有効にした車載ECUアプリケーションを実行し、メモリー破壊を検出する手順と結果を紹介します。

1. サニタイザとは ― メモリー破壊を発生箇所で検出する

サニタイザ(Sanitizer)は、プログラムの実行中に不正な操作を検出し、その発生箇所を特定する実行時検査の仕組みです。メモリ破壊は、誤動作の原因になるだけでなく、セキュリティ上の脆弱性にもつながります。そのためサニタイザは、品質向上だけでなく、ファジングと組み合わせた脆弱性の発見にも広く利用されています。

GCCやClangなどのコンパイラに搭載されており、コンパイル時にオプションを指定することで、プログラムに検査処理を組み込めます。

サニタイザには複数の種類があり、それぞれ検出対象が異なります。Clangの公式ドキュメントでは、次のサニタイザが紹介されています(利用できる機能は、Clangのバージョンやプラットフォームによって異なります)。

サニタイザ主な検出対象・用途
AddressSanitizer(ASan)配列の範囲外アクセス、解放済みメモリーへのアクセスなど
UndefinedBehaviorSanitizer(UBSan)整数のゼロ除算、符号付き整数オーバーフロー、範囲外の配列添字などの未定義動作
MemorySanitizer(MSan)未初期化メモリーの使用
ThreadSanitizer(TSan)スレッド間のデータ競合
LeakSanitizer(LSan)メモリーリーク
DataFlowSanitizer(DFSan)データの伝播・汚染状態の追跡(解析基盤)
Hardware-assisted AddressSanitizer(HWASan)AArch64のアドレス上位バイト無視機能(TBI)を利用したタグ付けによる、不正なメモリーアクセスの検出
TypeSanitizer(TySan)型に基づくエイリアシング規則違反の検出
RealtimeSanitizer(RTSan)リアルタイム処理中の不適切な操作(メモリー確保、ロック取得など)の検出

このように、サニタイザはメモリーアクセスの不正だけでなく、未定義動作やデータ競合など幅広い問題を対象とします。

配列の範囲外への書き込みを例に考えてみます。このような不正な書き込みは、発生した時点では必ずしも動作に影響を与えるとは限りません。メモリー配置によっては別の変数が書き換えられ、原因となった処理とは無関係な箇所で異常が顕在化することがあります。

ASanは、こうした不正なメモリーアクセスを発生した時点で検出し、アクセスが発生したソースコードの位置と呼び出し経路をレポートします。これにより、異常が現れた箇所から原因を遡るのではなく、問題を引き起こした処理を直接特定できます。

また、静的解析がソースコードを実行せずに不具合の可能性を指摘するのに対し、サニタイザは実際に発生した問題を検出します。特に、ASanは、実際に行われたメモリーアクセスを検査するため、誤検知がほとんどありません。ただし、オブジェクトの周囲に設けた検査領域(レッドゾーン)を飛び越えるアクセスや、構造体内部での溢れは検出できないなど、見逃しはあり得ます。また、検出できるのは実際に実行された処理に限られます。検査用のコードを組み込んでいない部分(ライブラリなど)の中で行われるメモリーアクセスも、memcpyなどASanが置き換える一部の標準関数を除いて対象外です。このため、静的解析とサニタイザは相互に補完する関係にあります。

サニタイザは、すでに多くのソフトウェア開発で活用されています。例えば、Chromiumプロジェクトでは、ASanを有効にしたビルドによるテストやファジングが継続的に行われています(https://chromium.googlesource.com/chromium/src/+/HEAD/docs/asan.md )。Androidのネイティブコード開発でも、HWASanなどが利用されています(https://developer.android.com/ndk/guides/hwasan )。

では、車載ECUをはじめとする組込みソフトウェアの開発でも、同様に活用できるのでしょうか。

2. 組込みソフトウェアでサニタイザを使う際の課題

サニタイザを利用するには、対応するコンパイラと実行環境の両方が必要です。組込み向けのコンパイラにもサニタイザの機能を備えたものはありますが、コンパイルできることと、実際に動作させられることは、別の問題です。

まず、組込み機器向けにビルドしたプログラムは、そのままでは開発用PCで実行できません。これは、組込みソフトウェアは一般にターゲットCPU向けにクロスコンパイルされ、生成されたコードはターゲットの命令セットや実行環境を前提としているためです。

では、ターゲット機器上でサニタイザを動作させる場合はどうでしょうか。Linuxを搭載した車載機などでは実機での利用例もありますが、マイコンとRTOSで構成されるECUには、次のような課題があります。

課題① ランタイムライブラリが必要

サニタイザは、コンパイラが検査コードを埋め込むだけでは動作しません。シャドウメモリーの管理やエラーの報告を行う実行時ライブラリ(ランタイム)が必要です。コンパイラがサニタイザに対応していても、ターゲットCPUやOSに対応したランタイムが提供されていなければ、それに相当する処理を自前で実装しない限り利用できません。

なお、UBSanには、検出時にトラップ命令を実行するだけのモード( -fsanitize-trap=undefined )があり、ランタイムを用意できない環境でも利用できます。ただし、このモードでは発生箇所や原因を示すレポートは出力されません。また、ClangにはUBSan向けに、機能を必要最小限に絞った軽量なランタイム( -fsanitize-minimal-runtime )も用意されています。

課題② メモリー使用量と実行時間の増加

サニタイザを有効にすると、検査処理が追加され、メモリー使用量と実行時間が増加します。Clangのドキュメントによれば、ASanでは一般に実行時間が約2倍に増加し、スタックの使用量は最大で3倍程度に増えた例もあるとされています(https://clang.llvm.org/docs/AddressSanitizer.html )。

組込み機器ではRAMやROMの容量が限られていることが多く、リアルタイム性が求められるシステムでは実行時間にも厳しい制約があります。サニタイザによる負荷が、本来の動作に影響を与える可能性があります。

課題③ 検出結果を取得する仕組みが必要

不正なアクセスを検出しても、その結果を開発者が確認できなければ意味がありません。

PC上であれば、エラーの内容やスタックトレースを標準エラー出力やファイルに記録できます。一方、組込み機器にはファイルシステムがないことも多く、ログを保持するメモリーや外部へ出力する手段を別途用意する必要があります。JTAGなどのデバッグインターフェースを利用する方法もありますが、デバッグ環境の構築や設定が必要となり、PC上で利用する場合ほど手軽ではありません。

このように、組込み機器の実機でサニタイザを利用するには、実行環境やハードウェアに起因するさまざまな制約があります。

一方、PC上ではサニタイザを比較的容易に利用でき、関数やモジュール単位のテストにも活用できます。ただし、組込みアプリケーション全体を動作させるには、ターゲット固有のOSや周辺ハードウェアへの依存を解消する必要があります。 そこで着目したのが、アプリケーションをPC上で実行するための環境であるSILS(Software In the Loop Simulation)です。

3. SILSとサニタイザを組み合わせる利点

SILSは、組込みアプリケーションをPC上で実行し、制御対象を模擬したプラントモデルと組み合わせて、システムとしての動作を検証する仕組みです。

サニタイザとの組み合わせで重要なのは、組込みソフトウェアのコードをPC上で実行できるだけでなく、実際のアプリケーションを、制御対象の動きに近い条件で動作させられるという点です。

利点① アプリケーションをPC上で実行できる

組込みアプリケーションは、ターゲットCPUやOS、周辺ハードウェアに依存しているため、通常はそのままPC上で実行できません。

SILSでは、これらの依存部分をPC上で動作する仕組みに置き換えることで、アプリケーションをPC向けにビルドし、実行できるようにします。その結果、PC向けのGCCやClangが提供するサニタイザを利用できます。

検査処理を実機の限られたRAMやROMに収める必要はなく、検出結果もPC上で取得できます。また、サニタイザはコンパイラのオプションで有効化するため、アプリケーションのソースコードに検査用の処理を追加する必要は基本的にありません。

利点② アプリケーション全体をシステムとして検証できる

PC上でサニタイザを使うだけであれば、関数やモジュール単位の単体テストでも可能です。

SILSの大きな特長は、実際のアプリケーションをシステムとして動作させられることです。組込みソフトウェアの不具合は、個々の関数の中だけでなく、複数の機能が連携する中で発生することもあります。SILSでは、複数の周期タスクや機能間の連携を含めて、アプリケーション全体の動作を検証できます。

利点③ プラントモデルと連携し、実際に近い条件で動作させられる

さらにSILSでは、制御対象を模擬するプラントモデルと組み合わせた閉ループシミュレーションが可能です。

例えば、車載ECUのクルーズコントロールであれば、ECUが出力した制御指令に応じて車両モデルの速度が変化し、その車速が再びECUに入力されます。この考え方は車載分野に限らず、制御対象に応じたプラントモデルを用意することで、産業機器やロボットなど、さまざまな組込みシステムに応用できます。

利点④ システムレベルの検証で、原因の行まで特定できる

機能間の連携で発生する不具合は、結合テストやHILSでも再現できることがあります。しかし、例えばメモリー破壊による異常が発生した場合、メモリーの状態や実行ログを調べても、どの処理がメモリーを破壊したのかを特定するのは容易ではありません。JTAGなどを用いて特定アドレスへのアクセスを捕捉する方法もありますが、監視対象の絞り込みやデバッグ環境の設定が必要です。

SILSとサニタイザを組み合わせれば、プラントモデルと連携したアプリケーションの実行中に不正なメモリーアクセスが発生した時点で、サニタイザがそれを検出し、原因となったソースコードの行と呼び出し経路をレポートします。

つまり、複数の機能が連携するシステムレベルの検証でありながら、問題のあるソースコードの行まで直接たどることができます。 不具合を再現するだけでなく、原因の特定まで一貫して行える点が、この組み合わせの価値です。

もちろん、PCと実機では、CPUアーキテクチャ、データ型のサイズ、メモリー配置、実行タイミングなどが異なります。また、サニタイザが検出できるのは実際に実行された処理に限られます。さらに、SILSで置き換えたハードウェア依存部分(レジスタアクセス、DMA、リンカスクリプトで配置される領域など)は検査の対象になりません。そのため、実機での検証を置き換えるものではなく、あくまで、実機検証の前段階で不具合を効率よく洗い出す手段として位置付けるのが適切です。

4. GSILでのサニタイザ適用

4-1. 検証環境

今回は、GSILに付属するクルーズコントロールのサンプルプロジェクトを使用しました。このサンプルは、クルーズ制御を行う仮想ECU(vECU)、車両の動きを再現するプラントモデル、運転操作を行う操作パネルで構成されています(図1)。

vECUが出力したアクセル開度に応じて車両モデルの速度が変化し、その車速が再びvECUに入力される閉ループシミュレーションを実行できます。この環境で、ECUアプリケーションにサニタイザを適用しました。

図1 GSILクルーズコントロール サンプルの構成
図1 GSILクルーズコントロール サンプルの構成

項目内容
SILSツールGSIL(vECUgen ver4.2)
コンパイラllvm-mingw 20250402 UCRT x86_64(Clang 20.1.2)
有効にしたサニタイザAddressSanitizer、UndefinedBehaviorSanitizer
ビルド構成Debug(-O0 -g3)

4-2. コンパイラの選定 ― MinGW-w64 GCCからllvm-mingwへ

GSILは、Windows上で動作するSILSツールで、vECUのビルドにはMinGW-w64のGCCまたはVisual Studioを使用できます。

GCC自体はASanやUBSanに対応していますが、MinGW-w64(Windows)向けのGCCには、サニタイザのランタイムライブラリが移植されていません。そのため、レポートを出力する形でサニタイザを利用することはできません。

Visual Studioに含まれるMSVCコンパイラ(cl.exe)はASanに対応していますが、UBSanには対応していません。なお、Visual Studioで追加インストールできるclang-clではUBSanも利用できますが、今回は既存のMinGW-w64向けビルド構成を活かすため、MinGW互換のツールチェーンを選びました。

「llvm-mingw」は、ASanとUBSanの両方に対応したWindows向けLLVMツールチェーンです。MinGW-w64のヘッダーとランタイムを使用しており、C言語のレベルではMinGW-w64のGCCとABI互換です。そのため、Cで書かれたECUアプリケーションであれば、既存のMinGW-w64向けビルド構成をもとに移行できます。ただし、C++の標準ライブラリはlibc++を使うためGCCとは互換性がなく、また今回使用したllvm-mingwはCランタイムがUCRT版である点に注意が必要です。

コンパイラの変更に伴い、ビルド設定とツール間の互換性について一部調整が必要でしたが、ECUアプリケーションのソースコードを変更することなく、サニタイザを有効にしたvECUをビルドできました。

【補足】主な調整内容

  • vECUgenが生成するMakefileのツール指定(CC、LINKなど)を、環境変数 MAKEFLAGS で上書きし、llvm-mingwのClangとlldを使用
  • GSILの型解析がClangのデバッグ情報を読み取れるよう、-mllvm -dwarf-inlined-strings=Enable を指定
  • GNU ldが自動定義し、lldが定義しないシンボル( __data_start__ 、 __bss_start__ )を補うオブジェクトをリンク時に追加

4-3. サニタイザの選定と有効化

第1章で紹介したように、Clangには多くのサニタイザがありますが、すべてがWindows環境で利用できるわけではありません。今回使用したllvm-mingwで公式に対応が明記されているのは、ASanとUBSanの2つです。そこで、この2つを組み合わせて使用しました。

Clangでは、次のオプションで両方を有効にできます。

-fsanitize=address,undefined

このオプションをコンパイル時とリンク時に指定することで、ソースコードを変更せずに実行時検査を組み込めます。また、ASanで一般に推奨される -fno-omit-frame-pointer もあわせて指定しました(今回は -O0 でビルドしているため、実質的な影響はありません)。

なお、ASanは既定では最初のエラーを検出した時点でプロセスを終了します。vECUはGSILのプロセスから読み込まれて動作するため、このままでは検出と同時にシミュレーションが継続できなくなります。そこで、検出後も処理を継続できるよう、コンパイル時に -fsanitize-recover=address を指定し、実行時にはランタイムオプション halt_on_error=0 を設定しました(UBSanは既定で検出後も処理を継続します)。

なお、検出後に処理を継続した場合、最初のエラー以降はメモリーが壊れている可能性があるため、2件目以降のレポートは参考として扱う必要があります。今回は、シミュレーションを継続して複数の不具合を確認するため、この設定を使用しています。

コンパイル時: -fsanitize=address,undefined -fsanitize-recover=address -fno-omit-frame-pointer
実行時:      ASAN_OPTIONS=halt_on_error=0:log_path=<出力先>
             UBSAN_OPTIONS=halt_on_error=0:log_path=<出力先>

log_path を指定すると、レポートは <出力先>.<プロセスID> という名前のファイルに出力されます。ただし、ASanとUBSanを同時に有効にした場合、log_path は両者に共通の設定として扱われ、後から読み込まれる UBSAN_OPTIONS の指定が優先されます。今回の環境でも、ASanのレポートを含むすべてのレポートが vecu_ubsan.<プロセスID> に出力されました。両方を有効にする場合は、UBSAN_OPTIONS 側で出力先を指定するか、両方に同じ値を指定しておくと確実です。また、ASanを有効にしたvECUは実行時にASanのランタイムDLLを必要とするため、GSILの起動前に、llvm-mingwのbinフォルダをPATHに追加しておく必要があります。

4-4. 検証用の不具合

サニタイザによる検出の様子を確認するため、クルーズコントロールのサンプルに、意図的に2種類の不具合を追加しました。

① モード遷移ログのリングバッファ溢れ(範囲外書き込み)

クルーズコントロールのモード遷移を記録するため、直近16件の履歴を保持するリングバッファを追加しました。task_mode() の中で、モードが切り替わるたびに save_mode_log() を呼び出して履歴を書き込み、16件に達したら書き込み位置を先頭に戻します。この折り返し判定を、意図的に誤らせています。

/* src/ac_ecu.c(抜粋) */
 73: #define ACC_LOG_NUM 16
 ...
 83: S_ACC_LOG acc_mode_log[ACC_LOG_NUM];
 84: unsigned int acc_log_pos;
 85: static unsigned int acc_time_cnt;

 87: static void save_mode_log(E_ST_ACC prev, E_ST_ACC next)
 88: {
 89:     acc_mode_log[acc_log_pos].time_cnt = acc_time_cnt;
 90:     acc_mode_log[acc_log_pos].prev_state = (unsigned char)prev;
 91:     acc_mode_log[acc_log_pos].new_state = (unsigned char)next;
 92:     acc_mode_log[acc_log_pos].vehicle_spd = (unsigned short)VehicleV;

 94:     acc_log_pos++;
 95:     if (acc_log_pos > ACC_LOG_NUM)    /* 正しくは >= */
 96:     {
 97:         acc_log_pos = 0;
 98:     }
 99: }
/* src/ac_ecu.c(抜粋):呼び出し元 */
231: void task_mode(void)
232: {
233:     E_ST_ACC prev_state = acc_state;

235:     acc_time_cnt++;
236:     acc_state = mode_change();
237:     if (acc_state != prev_state)
238:     {
239:         save_mode_log(prev_state, acc_state);
240:     }
241: }

95行目の折り返し条件が > になっているため、16件を書き込んだ後も書き込み位置 acc_log_pos が16のまま残ります。その結果、17件目の履歴は配列の範囲外である acc_mode_log[16] に書き込まれます(89〜92行目)。

今回のサンプルでは、車速40km/h以上でクルーズコントロールのON/OFFを繰り返すと、9回目のON操作が17件目の遷移となり、この状態に到達します。

この種の履歴記録処理は、データロギングや故障診断など、ECUのさまざまな機能で用いられています。

問題は、配列の範囲外に書き込んでも、その場で異常が現れるとは限らない点です。書き込み先にどの変数が配置されているかはリンク結果によって決まり、別の変数や他の機能のデータを書き換えてしまう可能性があります(図2)。実際、今回の検証でも、サニタイザを使用しない場合は、範囲外への書き込みが発生してもクルーズ制御は正常に動作しているように見えました。

図2 リングバッファ溢れ:17件目の書き込み先(acc_mode_log[16])
図2 リングバッファ溢れ:17件目の書き込み先(acc_mode_log[16])

② アクセルペダル特性テーブルの範囲外読み出し

もう1つは、クルーズコントロールがOFFのときに、アクセルペダルの踏み込み量(0.0〜1.0)をアクセル開度に変換する処理です。0.1刻み・11点のテーブルを線形補間します。

/* src/ac_ecu.c(抜粋) */
102: #define ACCEL_TBL_NUM 11

104: const double accel_open_tbl[ACCEL_TBL_NUM] = {
105:     0.00, 0.06, 0.13, 0.21, 0.30, 0.40, 0.51, 0.62, 0.74, 0.87, 1.00
106: };

108: static double calc_accel_open(double pedal)
109: {
 ...
117:     pos = (int)(pedal * 10.0);
118:     if (pos > ACCEL_TBL_NUM - 1)
119:     {
120:         pos = ACCEL_TBL_NUM - 1;
121:     }
122:     rate = pedal * 10.0 - pos;

124:     return accel_open_tbl[pos] + (accel_open_tbl[pos + 1] - accel_open_tbl[pos]) * rate;
125: }
/* src/ac_ecu.c(抜粋):呼び出し元(task_control()) */
276:     case ST_NoAc:
277:         req_accel = calc_accel_open(AccelPedal);

118〜121行目で添字 pos を最終要素(10)に制限していますが、124行目の補間では pos + 1 の要素も参照します。そのため、ペダル踏み込み量が1.0(全開)になると accel_open_tbl[11] を読み出し、範囲外アクセスが発生します。

この不具合は1つの関数内で完結する境界値の問題であり、静的解析や境界値を確認する単体テストでも発見できるタイプです。実際に、llvm-mingw付属のclang-tidyとClang Static Analyzerで解析したところ、②は検出されましたが、①は検出されませんでした。①は周期タスクが繰り返し呼び出された結果、グローバル変数の値が特定の値に達したときに初めて発生するため、関数単位の解析では捉えにくい不具合です。

なお、②では全開時に補間係数 rate が0になるため、範囲外の値が通常の数値であれば計算結果は正しい値(1.0)になります。出力値だけを確認するテストでは、問題が見過ごされる可能性があります。

4-5. サニタイザによる検出結果

サニタイザを有効にしたvECUをGSIL上で実行し、操作パネルから上記の不具合を発生させました。前述の設定により、サニタイザが不正なアクセスを検出してもシミュレーションは継続し、検出結果はログファイルに出力されます。

なお、今回の環境では、実行中のログファイルはサニタイザのランタイムが開いたままになっていたため、内容はGSILの終了後に確認しました。

① リングバッファの範囲外書き込み

車速40km/h以上でクルーズコントロールのON/OFFを繰り返したところ、9回目のON操作でリングバッファの範囲外書き込みが検出されました。

実際に出力されたログの冒頭部分を示します。

src\ac_ecu.c:89:2: runtime error: index 16 out of bounds for type 'S_ACC_LOG[16]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src\ac_ecu.c:89:2
=================================================================
==17352==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7ffcb4a947e0 at pc 0x7ffcb49e18d8 bp 0x009c4fefe990 sp 0x009c4fefe9d8
WRITE of size 4 at 0x7ffcb4a947e0 thread T0
    #0 0x7ffcb49e18d7 in save_mode_log C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:89:37
    #1 0x7ffcb49e178d in task_mode C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:239:3
    #2 0x7ff75972503c  (C:\GSIL\bin\GSIL\GSILCORE.EXE+0x14000503c)
    (中略:GSIL本体およびOSのフレーム)

0x7ffcb4a947e0 is located 0 bytes after global variable 'acc_mode_log' defined in 'src\ac_ecu.c' (0x7ffcb4a94760) of size 128
0x7ffcb4a947e0 is located 32 bytes before global variable 'acc_state' defined in 'src\ac_ecu.c' (0x7ffcb4a94800) of size 4
SUMMARY: AddressSanitizer: global-buffer-overflow C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:89:37 in save_mode_log
(以下、シャドウメモリーの状態と、90〜92行目に対する同様のレポートが続く)

まずUBSanが、要素数16の配列 acc_mode_log に対して添字16でアクセスしたこと(89行目)を報告しています。続いてASanが、acc_mode_log(128バイト)の直後への4バイトの書き込み(global-buffer-overflow、WRITE)を検出しています。呼び出し経路からは、GSIL本体(GSILCORE.EXE)から周期タスク task_mode() が呼び出され、その239行目から save_mode_log() の89行目に至ったことが分かります。

構造体の各メンバーへの書き込み(89〜92行目)がそれぞれ範囲外となるため、1回の不具合に対して複数のレポートが出力されます。また、同じ箇所での2回目以降の検出は、サニタイザ側で報告が省略されます。

② アクセルペダル特性テーブルの範囲外読み出し

クルーズコントロールがOFFの状態でアクセルペダルを全開(1.0)にしたところ、テーブルの範囲外読み出しが検出されました。

実際に出力されたログを示します(シャドウメモリーの表示などは省略)。

src\ac_ecu.c:124:32: runtime error: index 11 out of bounds for type 'const double[11]'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior src\ac_ecu.c:124:32
=================================================================
==37216==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7ffcb4a693d8 at pc 0x7ffcb49e20bf bp 0x00afd654eb60 sp 0x00afd654eba8
READ of size 8 at 0x7ffcb4a693d8 thread T0
    #0 0x7ffcb49e20be in calc_accel_open C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:124:32
    #1 0x7ffcb49e1de7 in task_control C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:277:15
    #2 0x7ff75972503c  (C:\GSIL\bin\GSIL\GSILCORE.EXE+0x14000503c)
    (中略:GSIL本体およびOSのフレーム)

0x7ffcb4a693d8 is located 0 bytes after global variable 'accel_open_tbl' defined in 'src\ac_ecu.c' (0x7ffcb4a69380) of size 88
SUMMARY: AddressSanitizer: global-buffer-overflow C:/GSIL/examples/SampleProject/Cruise_llvmclang/vECU/Cruise/src/ac_ecu.c:124:32 in calc_accel_open

こちらは範囲外からの読み出し(READ)です。UBSanは添字11によるアクセスを、ASanは accel_open_tbl(88バイト=double×11)の直後からの8バイトの読み出しを、いずれも124行目として報告しています。呼び出し元が task_control() の277行目、すなわちクルーズOFF時の処理であることも確認できます。

このように、サニタイザを使用しない場合には制御結果に異常が現れなかった処理についても、不正なメモリーアクセスを検出できました。

SILSでECUアプリケーションをシステムとして動作させながら、ソースコードの行単位で不正なアクセスを検出できる。これが、GSILとサニタイザを組み合わせる大きな利点です。

5. 検出結果を修正につなげる

サニタイザのレポートには、不正なアクセスの種類、発生したソースコードの行、呼び出し経路などが記録されており、不具合の原因調査に直接活用できます。

生成AIを活用して、レポートと該当するソースコードから修正案を検討することも可能です。例えば今回のリングバッファの範囲外書き込みでは、サニタイザのレポートと save_mode_log() のコードをAIに入力することで、折り返し条件の誤りを指摘させ、修正案を得ることができます。

ただし、AIが提示した修正案をそのまま採用するのではなく、開発者によるレビューと再テストが不可欠です。修正後のECUアプリケーションを再びGSILで実行し、同じシナリオでサニタイザによる検査を行えば、修正の確認にもそのまま活用できます。

また、生成AIでコードを作成する機会が増えるなか、生成されたコードを実際に動かして検査できる仕組みの重要性は高まっています。今回のような境界条件の誤りは、AIが書いたコードにも紛れ込む可能性があります。SILSとサニタイザの組み合わせは、AIが書いたコードの検証手段としても有効です。

このように、SILSとサニタイザを組み合わせることで、ECUアプリケーションを動かしながら不具合を検出し、その結果を原因調査と修正確認につなげることができます。

6. まとめ

このコラムでは、SILSツール「GSIL」とClangのサニタイザを組み合わせ、車載ECUアプリケーションに対する実行時検査を行いました。

サニタイザは、不正なメモリーアクセスを発生した時点で検出し、原因となったソースコードの行と呼び出し経路を報告できる仕組みです。一方、組込み機器の実機では、ランタイムやメモリー容量などの制約により、そのまま利用することが難しい場合があります。

SILSを用いれば、組込みアプリケーションをPC上で実行し、プラントモデルと連携したシステムレベルの検証を行えます。そこにサニタイザを適用することで、制御対象の動きに近い条件でアプリケーションを動作させながら、不正なメモリーアクセスを原因の行まで特定するという、新たな検証の選択肢が得られます。

今回は車載ECUを題材としましたが、この考え方は車載分野に限らず、他の組込みソフトウェア開発にも応用できます。

SILSは、組込みアプリケーションをPC上で動かすだけでなく、サニタイザや生成AIといったPC上の開発・解析技術を、組込みソフトウェアの検証に取り入れる手段にもなります。

本コラムについて

本コラムは、技術検証(PoC)の内容を紹介するものです。
本コラムに記載したサニタイザ対応は、GSILの標準機能として提供されているものではありません。

このコラムの著者

株式会社ユビキタスAI

エンベデッド第3部 セールス&PM セクション

藤江 克彦​(ふじえ かつひこ)

より詳しく技術や​関連製品について​知りたい方へ

本コラムに関係する技術や関連する製品について知りたい方は、お気軽にご相談ください。


製品情報

車載ECUソフトウェア開発向けシミュレーションツール

GSIL

ハードウェア完成前にPC上でECUソフトウェアを検証
製品ページを見る

AIエージェントに車載ECUソフトウェアの回帰検証を任せる

コラムを読む

ECUソフトウェアのカバレッジを90%以上に向上させる方法 ─ GSILの自動網羅テスト─

コラムを読む

マルチコアRTOS設計における実装前検証 ― chronSUITEによるタスクタイミングの可視化 ―

コラムを読む

SDV・マルチコア時代のブレーキ制御ECUに求められるタイミング検証とは

コラムを読む

GSILの網羅的自動テストケース生成機能を使ったECUの信頼性向上

コラムを読む

ソフトウェアアプリケーションの実行速度高速化技術

コラムを読む

組込み制御ソフトウェアでのCIの課題と実現方法

コラムを読む

イベントチェーンを応用した車載ECUのタイミング検証

コラムを読む

車載制御ECUソフトウェア教育向けパッケージGTrainerとは

コラムを読む

システムテストのカバレッジ測定

コラムを読む

組込み制御ソフト開発へのシミュレーション応用による開発効率向上

コラムを読む

CANを使用したSILSテスト ―車両全体シミュレーション、テストケース再利用―

コラムを読む

GSILのMILS開発への適用

コラムを読む

GSILのCI(Continuous Integration)適用

コラムを読む

OEM⇔サプライヤでのECU制御ソフトウェア環境共有による手戻り工数削減方法

コラムを読む

SILSを使用した車載制御ECUソフトウェア開発競争力強化

コラムを読む

車載ECUシミュレーションでテスト資産を活かすための技術とは?

コラムを読む

早期の車載ECUタイミング検証の実現

コラムを読む
メニューを閉じる
一つ前に戻る