![見出し画像](https://assets.st-note.com/production/uploads/images/108634095/rectangle_large_type_2_962c9bf9905f358341f21693f59f1fcf.png?width=1200)
連載4-SOLIDのアドレスサニタイザ機能
第一回から第三回まで、MMUを使った便利なデバッグ機能をご紹介しました。
今回は少し毛色が違いますが、不正なアドレスへのメモリアクセスを検出するデバッグ機能「アドレスサニタイザ機能」について、引き続きご紹介します。
1.アドレスサニタイザ機能
アドレスサニタイザ機能とは、本来アクセスすべきでないアドレスへのメモリアクセスを、アクセス直前に検出する機能です。
コンパイラフロントエンドであるClangは、アドレスサニタイザ機能を持っています。
https://clang.llvm.org/docs/AddressSanitizer.html
不正なメモリアクセスを検出するため、命令コードをプログラム中に埋め込みます。
SOLIDでは、それらと連携できるルーチンを、SOLID-OSのランタイムに組み込むことによって、不正アドレス検出機能を実現しています。
そして、不正アドレス検出「即時」、メモリアクセスが行われてしまう前にブレークします。
ここ、ポイントです。
不正アドレスにアクセスされ、よくわからない番地にプログラムが飛んでしまってリセットする直前!とかではないのです。
「そうなる前にブレークする」のがポイントです。
SOLIDのアドレスサニタイザ機能については、以下のURLで解説されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/address_sanitizer.html#id2
このURLを見ると、検出可能なメモリアクセス違反は以下です。
・ローカル変数 (スタック領域) の範囲外アクセス
・グローバル変数に対しての範囲外アクセス
・動的に確保 (malloc) したメモリに対しての範囲外アクセス
・freeしたメモリ領域へのアクセス
・二重free
どのように検出されるのか、見ていきましょう!
今回も、Cortex-A9搭載ボード×PARTNER Jet2の組み合わせで試してみます。
2.アドレスサニタイザ機能を使うには
まず、アドレスサニタイザ機能の使い方を見ていきます。
(1) ビルドの構成
ビルドの構成を Debug_tasan にします。
アドレスサニタイザ機能を使える構成、です。
![](https://assets.st-note.com/img/1687152551132-a4gfmzYC18.png)
(2) メモリマップデザイナ
メモリマップデザイナに、アドレスサニタイザ用のメモリ範囲設定があります。
アドレスサニタイザで監視する仮想アドレスの設定等を行います。
監視する仮想アドレスとしては、SOLID_CORE, HEAP, STACKを含めます。
![](https://assets.st-note.com/img/1687152582417-hhsywkcM23.png?width=1200)
以下URLに、メモリマップデザイナへ設定する際の注意事項が記載されています。
https://solid.kmckk.com/SOLID/doc/latest/user_guide/address_sanitizer.html#id8
3.アドレスサニタイザ機能を試す
では、試していきます。
3.1 ローカル変数の範囲外アクセス
まず、ローカル変数として配列を定義し、配列の範囲外をリードしてみます。
![](https://assets.st-note.com/img/1687152622873-TXfjs7zvqO.png?width=1200)
実行してみます。
![](https://assets.st-note.com/img/1687152636772-d1YgmWGCMK.png?width=1200)
Out of bound accessと表示されてブレークしました。
そして、沢山のデバッグに役立ちそうな情報が表示されています。
①メモリウインドウ
範囲外なのにリードされそうになったメモリが表示されています。
②ソースウインドウ
範囲外のリードを引き起こしそうになったコードがハイライト表示されています。
③RTOSビュワー
これは筆者が意図的に表示しました。
④で表示されているタスクIDから、タスク名がわかるように表示されています。
④デバッグ例外ビュー
範囲外なのにリードされそうになったメモリアドレス、アクセス情報、
それを引き起こしたコードがあるタスクのID、PC値が表示されています。
今回、
タスクID=4
であることがわかり、RTOSビュワーを見るとroot_taskであることがわります。
⑤ウォッチウインドウ
これも筆者が意図的に表示しました。
変数iが5なので、buf[]配列のサイズを超えています。
ライトアクセスも試してみましょう。
![](https://assets.st-note.com/img/1687152690799-rfOExBK7BT.png)
実行してみます。
![](https://assets.st-note.com/img/1687152717857-eWilyIAyli.png?width=1200)
リード同様、範囲外にアクセスする直前に、Out of bound accessと表示されてブレークしました。
0x80095f55のメモリは0のままであることがわかります。
すなわち、書き込みは実行されていません。
ここで逆アセンブルウインドウとレジスタウインドウを見てみましょう。
![](https://assets.st-note.com/img/1687152736153-G2DTDqeXj7.png)
STRB命令による書き込みより前で、アドレスチェックに引っ掛かり、ブレークが発生していることがわかります。
3.2 グローバル変数の範囲外アクセス
同じことを、グローバル変数に対しても試してみます。
![](https://assets.st-note.com/img/1687152800249-Inm1UfDH7l.png?width=1200)
実行してみます。
![](https://assets.st-note.com/img/1687152814785-DqAQcRTYde.png?width=1200)
先程と同様、範囲外アドレスにライトする前に、Out of bound accessと表示されてブレークが発生しました。
3.3 ポインタの想定外アドレスアクセス
次は、ポインタを試してみます。
とある変数に対しポインタを使うために定義したのに、その変数の範囲を超えてしまった場合を見てみます。
![](https://assets.st-note.com/img/1687152852440-klxh9WYqoC.png)
4バイトの変数 tmpdataに対し、1バイトずつリードし10バイト分読む、という、読みすぎのパターンです。
5回目のリードの際に範囲外アドレスへのリードとなってしまいます。
実行してみます。
![](https://assets.st-note.com/img/1687152867619-mZvOkV2QJW.png?width=1200)
5回目のリードの直前に、Out of bound accessと表示されてブレークが発生しました。
3.4 動的に確保したメモリに対しての範囲外アクセス
どんどん行きます。
(1) mallocの範囲外
mallocで確保したメモリ領域に対して、その範囲外にアクセスしてみます。
![](https://assets.st-note.com/img/1687152957206-V7yn8cPZK8.png)
分かっていただけます?この間違い。。。ちょくちょく遭遇しません?
定義するとき10進数、使うとき16進数。
純粋にケアレスミスなんですけどね。。。
(これ逆だと気が付かない。しかし無駄にメモリを食う羽目になる。)
実行してみます。
![](https://assets.st-note.com/img/1687152974456-csSCj4QZDD.png?width=1200)
Out of bound accessと表示されてブレークしました。
そうですね。iが10,すなわち0x0Aになったら、そこはもう範囲外ですね。
(2) freeしたメモリ領域へのアクセス
次、mallocで確保したメモリ領域をfreeしたのに、またその領域にアクセスしてみます。
![](https://assets.st-note.com/img/1687153003852-u553SgcEH9.png)
実行してみます。
![](https://assets.st-note.com/img/1687153020669-kI57SpGak2.png?width=1200)
ブレークしました。
ブレーク要因として「Use after free」と表示されました。
(3) 二重free
freeしたのに、またfreeしてしまう場合を試してみます。
![](https://assets.st-note.com/img/1687153068957-0E6PhxtwHL.png)
いくらなんでもこんな間違え方はしないですけどね。
試しのサンプルコードという事で。。。
複数メモリ領域を一気にfreeするような時、もう他でfreeされているのにまた含めてしまった、のような場合、ですよね。
実行してみます。
![](https://assets.st-note.com/img/1687153093290-wJE7iFCWkg.png?width=1200)
ブレークしました。
ブレーク要因として「Double free」と表示されました。
タスクIDは 0 となっていて、今までのようにアクセス情報が出てはいませんが、これはfree関数の中にあるメモリアクセスコードで検出されているからです。
自タスクからは、free()関数を呼んでいますので、その中で検出されています。
上記図の右下に表示されている、「呼び出し履歴」をたどっていくと、先ほどの二つ目のfree(buf)の中であることがわかります。
![](https://assets.st-note.com/img/1687153119787-5hIXxYcbK9.png?width=1200)
4.なんでもかんでも妥当性チェックするわけではない
SOLIDが使用しているClangのアドレスサニタイザ機能では、アドレスの妥当性チェックをすべてのメモリアクセスに対して要求しているわけではありません。
メモリアクセスの度に何やら後ろで動いていたら、プログラムの実行が遅くなって仕方がないですよね。
コンパイル時にアドレスが決まっていない場合にのみ、アドレスの妥当性チェックが働くようになっています。
なので、例えば、配列やポインタでないローカル変数へのアクセスに対しては、働きません。
コンパイルし命令コードになった時点で、メモリアクセス、すなわちロードストア命令のアクセス先アドレスは決まっているからです。
その時点で決まっているという事は、おかしなアドレスをアクセスするという現象は発生しない。
と、いう考えです。
試しにやってみましょう。
って、え?
配列でもポインタでもないローカル変数のアクセスでアドレス違反って、どうやって起こすの?
という事で、通常は考えられないケース、という事になります。
なので試しにやってみる事は、しません。
このように、コンパイラが生成するケース(すなわち人間が介在しないケース)は、特に実行中の妥当性チェックを行う必要はありません。
5.まとめ
今回は、アドレスサニタイザ機能についてご紹介しました。
例えば配列のオーバーフロー等、範囲外のメモリアクセスをしてしまう直前で捕まえてくれるのは嬉しいですね。
嬉しい理由、、、それは以下。
範囲外のアクセスしてしまった後は大体、なんだかよくわからないことになります。
しかもこれがまた、メモリの状態によっては大事に至らず気づかないことも多いです。
良く悩まされるのが、
「数時間に一度リセットが発生する。」
数時間に一度、大事に至ってしまうわけです。
これ、見せられる現象は「リセット」ですからね。
すべてリセットされた後ですから。
どうやってデバッグするよ?ですよ。
(まぁそれはそれで組み込みエンジニアの腕の見せ所なわけですが。)
前回のスタックオーバーフローでも同じ悩みを書きました。
しかし、スタックオーバーフローでも、配列オーバーフローでも、直前にブレークして、かつ、原因と引き起こした場所を教えてくれるのなら、デバッグに要する時間が大きく減ることになります。
嬉しいですね。
という事で、次回もこの話が続きます。
次回は「アドレスサニタイザ機能」について深堀りする予定です。