連載5-SOLIDのアドレスサニタイザ機能
前回、アドレスサニタイザ機能についてご紹介しました。
今回は深堀りをしてみます。
1.アドレスサニタイザ機能おさらい
おさらいします。
アドレスサニタイザ機能とは、本来アクセスすべきでないアドレスへのメモリアクセスを、アクセス直前に検出する機能です。
SOLIDのアドレスサニタイザ機能については、以下のURLで解説されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/address_sanitizer.html#id2
前回、いろいろなメモリアクセス違反を試してみました。
・ローカル変数
・グローバル変数
・ポインタ
・動的に確保(malloc)
上記すべて、プログラムを実行しメモリアクセス違反直前でブレークする、という検出方法でした。
これ、どのように実現しているのでしょうか。
前回最初の方に書きましたが、もう一度見てみます。
ーーーーーーーーーーーー
・コンパイラフロントエンドであるClangは、アドレスサニタイザ機能を持っています。
https://clang.llvm.org/docs/AddressSanitizer.html
・不正なメモリアクセスを検出するため、命令コードをプログラム中に埋め込みます。
SOLIDでは、それらと連携できるルーチンを、SOLID-OSのランタイムに組み込むことによって、不正アドレス検出機能を実現しています。
そして、不正アドレス検出「即時」、メモリアクセスが行われてしまう前にブレークします。
ーーーーーーーーーーーー
詳しく見ていきましょう。
今回も、Cortex-A9搭載ボード×PARTNER Jet2の組み合わせで試してみます。
2.アドレスサニタイザ機能有効・無効時のコード相違
前回試した、ローカル変数の範囲外ライトアクセスのコードをもう一度見てみます。
実行すると、範囲外にアクセスする直前に、Out of bound accessでブレークしましたね。
ブレークした時の、この部分の命令コードは以下でした。
ここで、アドレスサニタイザ機能が無効である場合の、この部分の命令コードを見てみましょう。
すっきりしていますね。
分かりやすいように並べてみましょう。
左側:アドレスサニタイザ機能 有効時のコード
右側:アドレスサニタイザ機能 無効時のコード
右側のコードでは、以下のようにシンプルです。
①buf[i]に入れる値をLDR命令でとってくる
②ADD命令で1を加算
③STRB命令でbuf[i]に相当するアドレスにその値を書く
しかし左側のコードでは、①の前に何やら、前処理があります。
この前処理、実はClangにより追加されており、メモリ違反を検出するための処理です。
アドレスサニタイザ機能をイネーブルにしてビルドすると、こういったメモリ違反検出コードが挿入されます。
とはいえ、どのメモリアクセスに対しても挿入する、というわけではありません。
コンパイラが妥当性を判断できない場合のみ。
すなわち、ビルド時点でアドレスが確定しないなどでコンパイラが必要と判断したときに、挿入されます。
もし、すべてのメモリアクセスの際にこういったコードが追加されているのならば、とてつもなく実行性能が悪くなってしまうため、必要なところにのみ挿入される仕様になっています。
興味深いのは、こういったことがClangコンパイラにより行われている事です。
アドレスサニタイザ機能実現において、Clangコンパイラのはたしている役割が結構大きいことがわかりますね。
ここで、asan_store1_noabordという関数がコールされていることに注目してください。
この関数は、どこにあるのでしょうか。
実はSOLID-OSの中にあります。
3.SOLID側での追加実装
3.1 メモリチェックのための関数
ソースコードになく、コンパイラにより挿入されている関数、SOLID-OSの中のソースコードを、許可を得て見てみます。
asan_check_memory_region関数や、そのサブルーチン関数も、SOLID-OSの中にあります。
ここで重要なことは、この関数が具体的に何をしているのか、ではなく、メモリチェックの際はSOLID-OSのランタイムルーチンを呼び出している、という事です。
この仕組みは、ClangとOSがタッグを組んで実現しているのです。
3.2 違反したらブレークする仕組み
違反していることを検出すると、ブレークしますよね。
これもSOLID-OSのソースコードに、そのようなコードがあるから、です。
かつ、ブレークすることによりSOLID-IDEに要因等を伝えていますが、それもSOLID-OSが行っています。
すなわち、SOLID-IDEに要因等を伝え、ブレークを発生させるコードが存在します。
違反が発生すれば、違反情報を採取したポインタを設定し、特別なブレークを発生せることで、IDE側と連携しています。
4.範囲外のアクセスとは?
次に、「範囲外」という事の検出、についてです。
妥当性チェックの対象である変数の情報が、シャドウメモリに格納されていて、それをチェックしています。
「シャドウメモリ」は、メモリマップデザイナで設定しています。
この例では、アドレス妥当性の判断のための情報を格納する領域として、0x40000000から0x2000000サイズ分を確保しています。
さらに、アドレス妥当性の判断を行う対象である変数の配置にも、工夫があります。
以下のコードで、動作を見てみましょう。
このroot_task()関数先頭で宣言している変数のアドレスを見やすくするコードです。
まず、このコードを、アドレスサニタイザ機能なしでビルドし、実行してみます。
アドレス0x80079400から32ビットサイズで、並んでいます。
では、アドレスサニタイザ機能付きでビルドし、実行してみます。
最初の32ビット変数はアドレス0x80095f30から配置されています。
次の32ビット変数は、ちょっと飛んで0x80095f40からの配置です。
その次も同様に、ちょっと飛んで配置されています。
アドレスサニタイザ機能を有効にすると、変数と変数の間に隙間が空くようになります。
隙間を空けることにより、アクセスしてはいけない領域を作りだし、チェック処理に工夫をしています。
5.まとめ
今回は、アドレスサニタイザ機能の実現手段についてご紹介しました。
ここに挙げた例の他にも、memcpy, strcpyに対してもアドレスチェックがなされます。
memcpy, strcpy関数の入り口で、指定アドレス範囲をチェックする機構が働く仕組みです。
実際のメモリアクセスの際は、もうチェック済の安心できるアドレス範囲であるため、メモリアクセス毎にアドレスをチェックしたりはしません。
アドレスチェックが必要な時、というのはClangにより判断され、必要以上にチェックは行いません。
そして必要と判断された場所では、OSのアドレスチェック関数がコールされます。
OSのアドレスチェック関数は、違反が発生するとブレークさせ、IDEに通知します。
IDEはその情報を、ユーザにわかりやすいよう、表示します。
ここで見えてくるのは、この機能はどこか一つが頑張ってサポートしている、というものではない、という事です。
”Clangだけでは成立せず”
”SOLID-OSのランタイムルーチンだけでも成立せず”
”ブレーク要因表示するためにSOLID-IDEまでを巻き込んだ、三位一体のデバッグ機能”
「OSとコンパイラを含むIDEが一体提供である」がために可能な機能であり、大きなメリットを感じました。
今後も、このような便利なデバッグ機能が開発されることに期待です。
次回は、少し話を戻して、MMUに関する便利なAPIをご紹介する予定です。