アドレスサニタイザの機能をシンプルに表現すると次の様になります。
メモリアクセス命令を実行する都度、アクセス対象が妥当かどうかチェックする機構。
図2-5の例でその基本的な仕組みを説明します。
プログラムをアドレスサニタイザモード(SOLIDのコンフグレーションでは、Debug_Tasan モード)でビルドすると、メモリへのライト命令(ここではbuf[i]=i;)の直前にメモリチェック機能をもつランタイム(以下チェックルーチンと呼びます)を呼び出すコードが自動的に挿入されます。プログラム実行時には、メモリにライト動作が実行される直前に、毎回ライト対象のメモリが妥当かどうかをチェックするプログラムが呼び出され、妥当性が確認されない場合には、プログラムの実行を中断し、デバッガに対して不当アクセスが発生したことを通知します。
(このプログラムは、第一回目の解説でも登場した配列オーバーランの例です。)
図2-5. メモリチェックルーチンを呼び出すコードが自動的に挿入される
ユーザーはチェックルーチンをコールするコード挿入箇所をわざわざ指定する必要はありません。
というのは、SOLIDツールチェーンに採用しているClangコンパイラにはアドレスサニタイザモードにおいて、コンパイルの際にメモリアクセス命令があるとその直前に予め指定されたチェックルーチンをコールするコード挿入する、という機能を持っており、SOLIDがその機能を利用しているためです。
このアドレスサニタイザ機能を使う場合、チェックルーチンの実行時間およびコードサイズのオーバーヘッドを最小限に抑えるため、プログラム中の全てのメモリアクセス命令に対してチェックルーチンを実行するわけではありません。Clangコンパイラがプログラムを解析し、アクセス対象のメモリが論理的に問題ないアドレスにあると判定した場合は、チェックルーチンのコールは行いません。
上記図2-5.の例のように、変数 i によりbuf[i]のアドレスが可変となるような、アクセス対象のメモリアドレスが確定しない場合にのみ、チェックルーチンをコールします。逆に、 i についてはメモリアクセスが発生する可能性があるものの、問題のないアドレスに配置されていることをコンパイラが知っているので、iのアクセス直前にはチェックルーチンをコールするコードを挿入せずにプログラムを実行します。
Clangコンパイラがアドレスサニタイザモードで実行するチェックルーチンは、SOLIDが専用に用意したデバッグ専用のランタイムであり、次のような動作をしています。
まず、アドレスサニタイザモードでビルドすると、論理空間上に変数を配置する際にその前後に「ガード領域」を挿入します(図2-6)。ガード領域には物理アドレスが割り当てられていても構いませんが、プログラムの動作においてはアクセス無効と定義されます。
図2-6. 変数の間にガード領域を挿入
次にプログラムを実行すると、アドレスサニタイザの監視対象関数の先頭で、アドレスサニタイザ専用のランタイムが論理空間のアドレス1バイトに対して1ビットの有効無効ビットを持った、アドレス判定テーブルを設定します。対応するアドレスが無効であれば”0”、有効であれば”1”という値を持ったテーブルです(図2-7)。
プログラムの進行に従いチェックルーチンが実行されると、アクセスしようとしているアドレスの有効・無効をアドレス判定テーブルを参照して判定します。判定結果が有効であれば、チェックルーチンは何もせずに、続くメモリアクセス命令を実行します。一方、判定結果が無効の場合は、アクセス違反を検出し、デバッガに通知するためブレークを発生させます。
図7において、buf[i]の i=10 になったところで判定テーブルの内容が 0となるため、チェックルーチンがこれを不正アクセスと判定します。
図2-7 有効無効アドレス判定テーブルを利用して、不正アクセスを判定する
少々細かい説明となりますが、この有効無効アドレス判定テーブルは、ターゲットメモリの中の “SOLID-RAM”という領域内に確保されます。
アドレスサニタイザによる実行時アドレスバグ自動検出の仕組みをまとめると、以下のようになります。
このように、コンパイラ、デバッガ、IDEとSOLID専用のランタイムが密接に連携してはじめて実現できるアドレスバグ自動検出機能を、SOLIDではアドレスサニタイザモードでビルド&実行するだけで誰でも簡単に利用できるようになっています。
このとき、変数の配置や、チェックルーチンの呼出しコード挿入も全てSOLIDが自動的に行うため、ユーザーがアドレスバグの発生しそうな箇所を意識する必要はありません。
なお、アドレスサニタイザモードでは、変数間のガード領域、有効無効アドレステーブル、チェックルーチンやチェックルーチンを呼び出すコードの挿入によりメモリ容量のオーバーヘッドがあります。また、チェックルーチン実行による実行速度低下が生じる点にも留意が必要です。
第三回では、実行時アドレスバグ自動検出機能を有効に利用していただくためのヒントなどをご紹介していきます。