IAR Embedded Workbench for AVR での安全なプログラミング

テクニカル・ノート 59350

アーキテクチャ:

AVR

コンポーネント:

compiler

更新日:

2018/08/13 8:15

はじめに

本テクニカルノートでは、IAR Embedded Workbench for AVRを使用した安全なプログラミングについて説明します。

同時にアクセスされる変数を保護する

複数のスレッドからアクセスされる変数については、それらの変数を適切にマークし、しかるべき方法で保護しなければなりません。ただし、読み取り専用の変数についてはその限りではありません。

変数を適切にマークするには、volatileキーワードを使用します。このキーワードは、変数が他のスレッドから変更される可能性があることをコンパイラに示します。すると、コンパイラでは、変数の最適化を回避(レジスタ内の変数を追跡するなど)し、変数への書き込みを遅延させず、ソースコードで指定された回数だけ変数にアクセスするように注意します。

変数のアクセスシーケンス中に割り込みが発生しないようにしなれければなりません。それには、割り込みが発生する可能性のあるコードで__monitorキーワードを使用します。これは、ライトシーケンスとリードシーケンスの両方に対して行わねばなりません。そうしなければ、部分的に更新された変数を読み取ることになりかねません。

これは、あらゆるサイズのすべての変数に該当します。1バイトの変数へのアクセスはアトミック処理とすることもできますが、これには保証はなく、継続的にコンパイラの出力を調べるのでない限りは、「いついかなるときでも」これに依存すべきではありません。__monitorキーワードを使用してシーケンスをアトミック処理とする方がより安全です。

可能であれば、インラインアセンブラではなく組み込み関数を使用する

AVRデバイスには、正確なタイミングと特殊な命令シーケンスを必要とするメカニズムが存在します。そのようなメカニズムは、通常、組み込み関数の形で利用できます。利用可能な組み込み関数は、inc\inavr.hヘッダファイルで宣言されています。

組み込み関数は、通常の関数呼び出しと変わらないように見えますが、実際にはコンパイラが認識する組み込み関数です。これは、ソースファイルにおける関数呼び出しのように見えますが、実際には、コンパイラによってインライン命令のシーケンスが生成されます。

インラインアセンブラを使用する場合と比較した場合の組み込み関数の利点は、どのような処理が行われるかをコンパイラが把握でき、レジスタの割当てや変数を使用してコンパイラがシーケンスを正しく構成できるということです。また、そのようなシーケンスをどのように最適化したらよいかをコンパイラに把握させることができます。これは、インラインアセンブラシーケンスでは不可能です。その結果、目的のシーケンスをコードに適切に組み込むことができ、コードを最大限最適化することが可能となります。

可能であれば、インラインアセンブラではなくアセンブラ関数を使用する

インラインアセンブラシーケンスでは、C/C++で記述された前後のコードとのインタフェースが明確に定義されていません。このため、インラインアセンブラコードが脆弱になります。また、将来コンパイラをアップグレードした場合に保守面で問題が生じる可能性もあります。また、インラインアセンブラが使用されている関数をコンパイラが最適化できないため、インラインアセンブラコードの脆弱性を軽減できません。

これらの理由から、通常はインラインアセンブラの使用を避けてください。多くの場合、特殊なアセンブラコード(上記を参照)を記述しなくて済むようにするための組み込み関数が用意されているため、それらの関数を使用するようにしてください。

適切な組み込み関数がない場合、Cから呼ばれるアセンブラモジュールでアセンブラコードを記述するのがベストです。これにより、以下のような利点が得られます。

  1. 関数呼び出しのメカニズムが明確に定義され、新しいバージョンのコンパイラがリリースされてもそのメカニズムが変わってしまうことがありません。通常、__version_1のメカニズムを使用するのがベストです。これによりコードの脆弱性の問題が解消されます。
  2. コードが読みやすくなります。
  3. オプティマイザを正しく機能させることができます。

しかし、関数呼び出しによってオーバヘッドが大幅に増加しないのでしょうか。その答えは、「状況による。」です。確かに、CALL命令とRET命令を使用すると、ある程度のオーバヘッドが発生し、一部のレジスタがコンパイラによってスクラッチレジスタ(関数呼び出しによって破壊されるレジスタ)であるとみなされます。

その一方で、コンパイラは、インラインアセンブラ命令によってスクラッチレジスタが破壊されるものとみなします。CALLとRETによるオーバヘッドは、これらの要因によって決定されます。多くの場合、最適化をオフにした場合のオーバヘッドは、CALL命令とRET命令によるオーバヘッドをいとも簡単に上回ります。

EEPROMの書き込みメカニズムを保護する

EEPROM変数への書き込みメカニズムがリエントラントとならないように保護しなければなりません。すなわち、2つのスレッド(例えばメインコードと割り込み処理)からEEPROMに書き込む場合、__monitorキーワードを使用してEEPROMへの書き込みを保護し、EEPROMの書き込みメカニズムを使用する他のコードによって、EEPROMの書き込みシーケンスが割り込まれないようにする必要があります。

ワード単位のSFRの書き込みを保護する

ワード単位のSFRは、AVRに内蔵されている一時バイトを使用してアクセスされます。そのような一時バイトは1つしかありません。2つのスレッド(例えばメインコードと割り込み処理)からワード単位のSFRにアクセスする場合、書き込みアクセス中に割り込みが発生しないように保護しなければなりません。さもないと、発生頻度の低い非常に難解なバグが発生するおそれがあります。割り込み処理内で割り込みを有効にするのを常としている場合、ワード単位のSFRへのアクセス中にそのような割り込みが発生しないように保護しなければなりません。

ロックされたレジスタをプッシュしない

レジスタをロックし、それらのレジスタにグローバル変数の値を格納する場合、アプリケーションでは、そのレジスタがグローバル変数で使用するためにロックされているものとして扱ってください。ユーザが作成したアセンブラコードでこのレジスタを使用しないでください。また、このレジスタを貸し出している間にその変数をスタックにプッシュできると考えないでください。

これは、ロックされたレジスタ変数が割り込み処理からアクセスされる可能性があるためです。処理速度を上げるために変数の値をレジスタに格納する場合があります。このとき、割り込み処理を高速に処理するために、割り込み処理でそれらのレジスタにアクセスしたい場合があります。

また、サードパーティ製のアセンブラコードをインポートする場合も注意が必要です。それらのコードではレジスタが使われている可能性がありますが、割り込み発生時にそれらのレジスタをスタックに保存しても意味がありません。

ロックされたレジスタは常に触らないことが重要です。

 

全ての製品名は、それぞれの所有者の商標または登録商標です。

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