C ソースからの
組込み制御システムのモデル化

Jeremías Sauceda

Theodore Murdock

ENSOFT
Ames, Iowa, USA

概要— 過去 10 年間で、自動車業界は制御システムのモデルベース開発を取り入れてきた。こうした企業の多くは、これらのモデルの設計とシミュレーションに MathWorks の Simulink を選択している。しかし、多くの制御システムが当初 C で書かれており、現在も使われ続けているという問題が残っている。一部の企業はこれらの C のシステムを手作業で Simulink モデルに変換しようと試みたが、変換のコストが高くなりすぎ、誤りが生じやすく、時間がかかりすぎるため断念している。EnSoft は、弊社の Atlas for C ツールを用いた半自動変換を提供することで、この問題に取り組むことにした。Atlas はソフトウェアをマッピングし、プログラムのすべての部分について関係マップを作成する。そのうえで、このグラフをクエリし可視化するためのツールを開発者に提供する。我々は、C プロジェクトに対して必要なクエリを実行し、等価な Simulink モデルとサブシステムを作成する Eclipse プラグインを開発した。本論文では、条件ロジックやグローバル変数の扱いを含め、このような変換における困難のいくつかについて論じる。また、C のソースコードを可視化し、理解し、検証するためのカスタムツールを作成するために Atlas をどのように拡張できるかについても論じる。

キーワード—モデル化; 組込みソフトウェア; 制御システム; レガシー C; ソフトウェアグラフ; グラフクエリ; Atlas; XCSG; 自動コード生成; リバースエンジニアリング

目次

I. はじめに

過去 10 年間で、自動車業界は制御システムのモデルベース開発を取り入れてきた。こうした企業の多くは、これらのモデルの設計とシミュレーションに MathWorks の Simulink を選択している[1]。しかし、多くの制御システムが当初 C で書かれており、現在も使われ続けているという問題が残っている。一部の企業はこれらの C のシステムを手作業で Simulink モデルに変換しようと試みたが、変換のコストが高くなりすぎ、誤りが生じやすく、時間がかかりすぎるため断念している。

EnSoft は、弊社の Atlas for C ツールを用いて構築した半自動変換を提供することで、この問題に取り組むことにした。Atlas はソフトウェアをマッピングし、プログラムのすべての部分について関係マップを作成する。そのうえで、このグラフをクエリし可視化するためのツールを開発者に提供する。我々は、C プロジェクトに対して必要なクエリを実行し、等価な Simulink モデルとサブシステムを作成する Eclipse プラグインである Modelify を開発した。本論文では、条件ロジックやグローバル変数の扱いを含め、このような変換における困難のいくつかについて論じる。また、C のソースコードを可視化し、理解し、検証するためのカスタムツールを作成するために Atlas をどのように拡張できるかについても論じる。

II. 自動車制御システムのソフトウェア開発の変遷

自動車や航空機のような複雑な機械にソフトウェアが組み込まれる場合、その開発には分野横断的なチームが必要となる。この分野横断的なチームには、車両の制御の背後にある物理と数学を理解している制御エンジニアと、そのドメイン知識を非常に効率的なコードに変換するソフトウェアエンジニアが含まれることがある。

Simulink のようなモデルベース設計ツールを使えば、制御エンジニアはコードを一切書かずに、仮想環境で制御戦略を設計、解析、テストできる。Simulink モデルはグラフィカルであるため、制御エンジニアは問題についての自分の考え方に適した環境でアイデアを探ることができる。

入力 U と W、ばねとダンパーのブロック、積分器、位置と微分の出力を持つ、ばね・質量・ダンパー系の Simulink サブシステム。
1. Simulink モデル
fogbugz-timesheet プロジェクトの Atlas コールグラフで、DailyTimesheet.main と WeeklyTimesheet.main が Main.main と generateReport を呼び出し、ServerUtil が person、interval、case のヘルパーを呼び出し、各パーサーが rt.jar 内の java.net を呼び出している。
2. クエリを用いて生成した Atlas のグラフ

Simulink モデルは、制御戦略の設計プロセスを支援するだけでなく、実行可能な仕様としても機能し、ソフトウェアチームはそれを電子制御ユニット(ECU)上で動作する効率的なコードに変換できる。モデルは精密で実行可能であるが、最終的に量産 ECU に搭載されるコードとは異なることがある。例えば次のとおりである。

  • コードでは精度の低い算術演算、多くの場合は固定小数点演算が使われることがある。
  • 高速なワークステーション上で動作するモデルは、ECU 上のコードよりもはるかに多くのステップを 1 秒間に計算できる。
  • 開発の後期にコードに調整が加えられ、それがモデルに反映されないことがある。

制御ソフトウェアに変更が必要になると、制御チームがモデルを変更し、その後ソフトウェアチームがソフトウェアを更新する。この更新戦略により、ソフトウェアチームが効率を達成するために行った作業を繰り返すことなく、ソフトウェアを進化させることができる。

2 つの要因によって、ハンドコード(モデルを手作業でコードに変換したコード)の必要性は低下してきた。第一に、自動コード生成ツールが大きく改善され、多くの場合、ハンドコードと生成コードの性能差は許容できる程度になっている[2]。第二に、より高速なプロセッサのコストが下がった。

そのため現在では、制御エンジニアが制御ソフトウェアを完全に Simulink で設計し、統合用にそれなりに効率的な C コードを提供できる状況にある。ソフトウェアチームには、アプリケーションを RTOS と統合するといった他の作業の責任が依然としてある。しかし、モデルを手作業で C コードに変換するという手間のかかる作業を行う必要はない。

では、なぜ今でもこれほど多くのハンドコードを使っているのだろうか。端的に言えば、何百万行ものレガシーコードがあるからである。そのコードの一部には対応するモデルがあるが、多くにはない。対応するモデルがあるハンドコードであっても、重要な変更の一部がハンドコードにのみ加えられ、モデルには反映されていないのではないかという懸念がある。

そのため、多くの企業には次の 3 つの選択肢しか残されていない。

  • 既存のプロジェクトやモジュールについてはモデルを手作業でコードに変換し続け、Simulink と自動コード生成は新しいプロジェクトやモジュールにのみ使う。
  • 自社製品をリバースエンジニアリングし、Simulink で再実装する。
  • コードを手作業で Simulink に変換する。

1 つ目の選択肢では、モデルの自動コード生成による効率の向上を得られない。2 つ目と 3 つ目には、莫大な初期コストがかかるか、あるいは非常に長い時間がかかる。

モデルを自動的にコードに変換できるのであれば、コードを自動的にモデルに変換することもできるのではないか。従来のコンパイラでは、正しさと性能が関心事である[3]。しかし、C を機械語ではなくモデルに変換するのであれば、関心事は正しさと保守性である。ここで保守性とは、生成されたモデルが制御エンジニアにとって読みやすく、良いシステムアーキテクチャを反映し、企業の設計・開発プロセスにうまく適合することを意味する。加えて、スケーラビリティの課題もある。レガシーな C の制御システムの中には、50 万行から 100 万行のコードで構成されるものもある。

我々は、C コードを Simulink モデルに変換する Modelify を構築した。ゼロから始めるのではなく、大規模ソフトウェアを理解し解析するための充実したプラットフォームである Atlas を活用した。以下の節では、Atlas とは何か、そして Atlas が提供する情報が Modelify 構築の課題への対処にどのように役立つかを説明する。

III. Atlas

Atlas を使えば、Java と C のコードを処理し可視化するカスタムの Eclipse プラグインを容易に作成できる[4]。Atlas はソースコードを構文解析・解析してグラフを生成し、そのグラフは Java や Scala など JVM 互換の任意の言語からクエリできる。

このグラフには次のものが含まれる。

  • 主要な宣言(プロジェクト、型、パッケージ、フィールド、メソッド)と、型階層などの関連する関係。
  • 呼び出しやフィールドの読み書きなど、メソッドレベルの関係。
  • 各メソッドの制御フロー
  • 詳細なデータフロー

例えば、次のクエリを使うと、プログラムが java.net パッケージとどのようにやり取りしているかを可視化できる。

Q java_net = pkg(“java.net”);
Q children = edges(XCSG.Contains).forward(java_net);
Q methods =
children.nodesTaggedWithAny(XCSG.Method);
Q callGraph =
index.edgesTaggedWithAny(XCSG.Call).reverse(methods)
show(callGraph);

図 2 に、これらのクエリの結果を示す

A. eXtensible Common Software Graph

Atlas が生成するグラフは、eXtensible Common Software Graph(XCSG)スキーマ[5]に従う。XCSG は、ソフトウェア(すなわちソースコードまたはバイナリ)の意味的に豊かなグラフ表現を定義しており、ソフトウェアからのパターンのマイニング、マルウェアや欠陥の検出、静的解析ツールの構築、コード理解など、多くの用途に適している。

XCSG は、異なる言語で書かれたソフトウェアを調和的に表現できるよう、プログラムの成果物について意味的に厳密な定義を持つ。厳密な意味論がなければ、解析ツールは容易に言語への偏りを持つようになり、特に複数の言語で書かれたソフトウェアを解析する際に、他の言語の誤った処理につながる。例えば、C と Java の static キーワードには、重なり合うが互換性のない用法があり、XCSG はこれを曖昧さなく区別する。プログラム表現に対する AST 的なアプローチと比べ、XCSG はグラフデータベース向けに設計されており、制御フローグラフやデータフローグラフなどの解析結果の表現も包含する。

XCSG がどのようなものかを知ってもらうため、ここでは Function の XCSG 定義を示す。XCSG の完全なリファレンスは次の場所にある: http://ensoftatlas.com/wiki/Extensible_Common_Software_Graph

XCSG:Function

Function の意味論は、C/C++ の関数と C++/Java のメソッドに共通するものである。

Function は抽象メソッドである場合もあるため、常に実装を表すわけではない。ただし、定義を伴わない関数宣言(C と C++ で可能なもの)のうち、何らかの Classifier の抽象メソッドではないものは Function とはみなされない。

タグ inline は、その関数をインライン化の候補とみなすべきだというコンパイラへのヒントが存在することを示す。これは、その関数への呼び出しが常にインライン化されることを保証するものではない。特に、動的ディスパッチを伴う呼び出しは、この属性の値にかかわらず一般にインライン化されない。メソッドのインライン化がその意味論に影響を与えてはならない。

メタクラスFunction
拡張元Node
説明Parameter を受け取ることがあり、Return 型を持つ、呼び出し可能なサブルーチン。
直接のサブ種別GlobalFunction, Method
すべてのスーパー種別Node, ModelElement
規定された入力エッジ
継承元入力エッジ先行ノード多重度説明
InvokedFunctionStaticCallSite✱StaticCallSite を、呼び出される正確な Function に接続する。
NodeContainsContainer1Contains エッジの具体的な種類によって規定される意味で、先行ノードが後続ノードを含むことを示す。
規定された出力エッジ
継承元出力エッジ後続ノード多重度説明
HasParameterParameter✱Function を、その Function の Parameter に接続する。
HasReturnValueMasterReturn0..1(void でない)Function を、その MasterReturn ノードに接続する。
ReturnsType1Function を、その戻り値の Type(Void の場合もある)に接続する。
ThrowsType✱Function が、示された Type の値またはインスタンスを例外として送出し得ることが静的に分かっていることを示す。

B. Atlas のクエリ

Atlas のクエリは、Java や Scala など JVM 互換の任意の言語から実行できる。迅速なプロトタイピングと自由な探索を支援するため、Atlas には Scala を使って Atlas のクエリを実行できるシェルビューが含まれている。

Atlas が提供する Q インターフェースには、クエリ式を構築するためのさまざまなメソッドがある。これらのメソッドはクエリを返すため、式を連鎖させることができる。以下にいくつかのクエリの例を示す。

index(ソースコードから計算された XCSG グラフ)から出発して、クエリ Q.types(java.lang.String) を使い、“Base” という名前のすべての型を選択できる。ここで「型」には、クラス、インターフェース、列挙型、アノテーションが含まれる。

Eclipse の Atlas シェルで java.net のコールグラフのクエリを実行し、各式が Atlas のクエリ式としてエコーされている画面。
3. Eclipse の Atlas シェルビュー
// all types named Base
var t = index.types(“Base”)

show() メソッドは結果をグラフエディタに表示する。

show(t)

クラス Base が、その親のパッケージとプロジェクトとともに表示される。これは、XCSG.Contains エッジを介して結果を指しているノードを含めることで、show() が自動的に視覚的なコンテキストを提供しているためである。

クラス Base が、パッケージとプロジェクト com.ensoftcorp.atlas.java.example.project の中に入れ子になっている Atlas のグラフ。
4. Base クラスと、それを含むパッケージおよびプロジェクトを示す Atlas のグラフ

“Base” という型をクエリする方法が得られたところで、それを使って何ができるだろうか。簡単なのは、型階層を取得することである。

XCSG のインターフェースを調べると、XCSG.Type ノードには、親の型を指す XCSG.Supertype タグ付きのエッジがあることが分かる。“Base” のサブタイプをクエリするにはどうすればよいだろうか。

XCSG.Supertype エッジをたどってサブタイプへ下りていけばよい。型はそのスーパータイプを指しているため、サブタイプを得るにはエッジを逆方向にたどる。エッジをたどるには、Q.reverse() クエリを使う。

ただしその前に、探索を行うグラフを用意しなければならない。特定の種類のエッジをたどる最も簡単な方法は、エッジにフィルタを設定することである。これは Q.edgesTaggedWithAny() クエリを使って行う。

関心のあるエッジには XCSG.Supertype のタグが付いている。では、これらすべてを組み合わせてみよう。

// get a reference to Base
var base = index.types("Base")

// filter edges
var edgesSupertype =
index.edgesTaggedWithAny(XCSG.Supertype)

// walk to the subtypes from Base
var result = edgesSupertype.reverse(base)
show(result)
例のプロジェクト内に入れ子になったクラス Subtype からクラス Base へ supertype エッジが伸びている Atlas のグラフ。
5. Base クラスとサブタイプ Subtype を示す Atlas のグラフ
calculated への代入のデータフローグラフで、定数 1、0.5、1.25 がそれぞれ p、i、d と乗算され、その積が加算され、和が df(local) とラベル付けされたエッジで calculated へ流れている。
6. pid.c(図 8)の 9 行目の XCSG データフローグラフ

IV. Modelify の構築

Modelify は、Atlas(それ自体が Eclipse プラグインの集合である)の上に構築された Eclipse プラグインである。Modelify を構築するにあたり、我々は Simulink に変換したい C コードの XCSG グラフを Atlas に計算させ、Atlas のクエリを使って XCSG グラフを探索し、Simulink モデルを出力する。Simulink モデル自体は Simulink の API を使って構築する。Atlas は何百万行ものコードを扱えるため[6]、Modelify は Atlas のスケーラビリティを受け継いでいる。

pid.c から生成された Simulink モデルで、入力 step、dt_in、last_in、sum_in、比例・積分・微分のゲイン、加算、関係演算子、スイッチ、出力 return、last_out、sum_out を持つ。
7. pid.c(図 8)から生成された Simulink モデル

Simulink と C には多くの違いがあるが、変換にとって最も重要な 2 つは次のとおりである。

  • Simulink はデータフロー言語である。変数を宣言する必要はなく、代わりにある計算の出力を別の計算に直接接続する。
  • Simulink は、信号処理から借用した高レベルの構成要素の使用を推奨している。

C のような言語のデータフローグラフは多くの解析に有用であるため、すでに XCSG グラフの一部になっている。例えば、pid.c(図 8)の 9 行目のデータフローグラフを図 6 に示す。変換の結果得られる Simulink モデルを図 7 に示す。

以下の節では、変換に必要なさまざまな情報を特定するために Atlas のクエリをどのように使えるかを示す。

比例・積分・微分の値を計算して MAX で上限を設ける PidStep 関数を含む pid.c のリスト。
8. C で実装された比例・積分・微分制御器(PID 制御器)である pid.c のリスト
PID 構造体、extern の pid ポインタ、定数 P、I、D、MAX を含むリストで、印字どおりの 11 行目 11.#define MAX 10 も含む。
9. C で実装された比例・積分・微分制御器(PID 制御器)である pid.c のリスト

A. 縮約

縮約は、低レベルの算術ブロックのグループを特定し、Simulink のより少数の高レベルブロックに縮約する。例えば、C における定数による乗算は、Simulink のゲインブロックに縮約できる。

どちらも Line 16 と記された定数ブロック I と積のブロックが、矢印で 1 つのゲインブロックに縮約されている。
10. 1 つの Gain ブロックに縮約された Constant ブロックと Multiplication ブロック

簡略化の候補を特定する Atlas のクエリを作成できる。

var q=
index.forwardStep(nodes(XCSG.Literal)).intersects(index.tagged(XCSG.Multiply)
縮約を行わない Simulink の PID モデルで、比例・積分・微分の各項に別々の定数ブロックと積のブロックがある。
11. 縮約を行わずに pid.c(図 7)から生成された Simulink モデル。図 6 と比較されたい。

上のクエリでは、q にはパラメータの一方が定数であるすべての乗算が含まれる。

B. グローバル変数

グローバル変数は、関数間で出力を書き込み入力を読み取るためによく使われる。関数がグローバル変数を使う場合、それらを Simulink の Inport と Outport として表現しなければならない。ここでも Atlas のクエリが役立つ。

var
df=edges(XCSG.Contains).forward(f).tagged(XCSG.DataFlow_Node)
var
in=edges(XCSG.DataFlow_Edge).reverseStep(df).intersection(nodes(XCSG.ProgramVariable)
var
out=edges(XCSG.DataFlow_Edge).forwardStep(df).intersection(nodes(XCSG.ProgramVariable)

上のクエリでは、関数 f について、in には Inport となるべきすべてのグローバル変数が、out には Outport となるべきすべてのグローバル変数が含まれる。

C. 条件ロジック

条件ロジックには 2 つの要素がある。1 つは、例えば if 文を支配する条件ノードを特定することであり、もう 1 つは、C で変数に書き込まれる値を Simulink の Switch ブロックへ振り向けることである。XCSG は、条件ロジックで使われるデータフローノードに直接タグを付けている

var
c=edges(XCSG.Contains).forward(f).tagged(XCSG.DataFlowCondition)

C の先行ノードは、単純に Switch ブロックの条件入力ポートへ振り向けることができる。

D. その他の扱いの難しい C の機能

もちろん、C には他にも扱いの難しい構成要素があり、企業によってそれらがどのように使われているかを予測するのは難しい。我々の解決策は、顧客のニーズに応じて、それらへの対応を必要に応じて構築することである。変換は自動であるが、C の中に最適には処理されない構成要素が見つかる可能性があるため、我々は全体のアプローチを半自動と呼んでいる。しかし、新しいケースに対処するクエリを素早く追加できることは、Atlas の強力な特長であると我々は考えている。

V. おわりに

Modelify のようなツールは、Atlas を使って素早く容易に構築できる。Atlas の意味的な厳密さ(XCSG)、スケーラビリティ、可視化機能を活用できる。読者の皆さんにも、Atlas を使って独自のツールを構築していただければ幸いである。

参考文献

  1. Holzwarth, R. and May, K., “Analysis of Traction Control Systems Augmented by Limited Slip Differentials,” SAE Technical Paper 940831, 1994, doi:10.4271/940831.
  2. Thate, J., Kendrick, L., and Nadarajah, S., “Caterpillar Automatic Code Generation,” SAE Technical Paper 2004-01-0894, 2004, doi: 10.4271/2004-01-0894.
  3. Aho, A., Lam, M., Sethi, R., Ullman, J., “Compilers: Principles, Techniques, and Tools,” Addison Wesley; 2nd edition, 2006.
  4. Deering, T., Kothari, S., Sauceda, J., Mathews, J., “Atlas: A New Way to Explore Software, Build Analysis Tools,” In Companion Proceedings of the 36th International Conference on Software Engineering (ICSE Companion 2014). ACM, New York, NY, USA, 588-591.
  5. eXtensible Common Software Graph on the Atlas Wiki: http://ensoftatlas.com/wiki/Extensible_Common_Software_Graph
  6. S. K. Ahmed Tamrawi, Gui Kang. Event Flow Graphs to Verify Absence of Vulnerabilities and Malicious Behaviors. IEEE Trans. Softw. Eng., 2013.