SOLID未分類 SOLIDで安全なリアルタイムシステムを開発する(連載3)

SOLIDで安全なリアルタイムシステムを開発する(連載3)

(2023/6/12)

[連載3] SOLIDのMMU使いこなしー2

第一回で、SOLIDの持つ便利機能を二つご紹介しました。

・NULLポインタアクセス検出
・スタックオーバーフロー検出

今回は、これらの機能の実現方法について、第二回でご紹介したMMU関連機能を使いながら、深堀りをしてご紹介していきます。

 

1.不当なメモリアクセス

まず、おさらいします。

MMUは、CPUから要求されたアドレスのメモリデータを、バスが解釈できるアドレスに変換するためのテーブルを持っています。

CPUから要求されたアドレス:CPUが解釈しているのは「仮想アドレス(論理アドレスとも呼ぶ)」
バスが解釈できるアドレス:「物理アドレス」

要求された「仮想アドレス」に対応する「物理アドレス」が、テーブルに載っていない場合、対応する物理アドレスに変換できません。

この場合、

「CPU(で動作しているファームウェア)が間違えているに違いない!」

と、MMUは思います。
CPUに連絡してあげなきゃ!

で、「不当アドレスアクセス(データアボート)例外」を発生させます。

MMUの持つアドレス変換テーブルは、それだけではありません。
CPUからアクセス要求される仮想アドレスに対し、アクセス権限についても付属して解釈することができます。
リードのみ許可する、とか、特権モードの時だけアクセスしていいよ、とか。

要求された「仮想アドレス」に対応する「物理アドレス」が、テーブルに載っている場合でも、付属するアクセス属性が一致していないと、対応する物理アドレスに変換できません。

MMUはまた思います。
「CPUに連絡してあげなきゃ!」

で、「不当アドレスアクセス(データアボート)例外」を発生させます。

その例外ベクタで何をするのか、は、ファームウェアの管轄になります。
別スレッドのためのアドレス変換テーブルを登録してもいいです。
そもそもそんなアドレスアクセスする予定なかった、事をプログラマに通知する、でもいいです。

SOLIDは後者です。
「変なアドレスにアクセス来たよ」を、SOLID-IDEの画面で通知します。
アクセス対象アドレスと、アクセスタイプ(リードかライトか)、それからアクセスした命令があるPC値と一緒に表示します。

第一回で、Raspberry Pi4とSOLIDを用いて、NULLポインタアクセスの検出を行ったときのスクリーンショットを再び見てみます。

※Raspberry Pi4のCPUであるBCM2711は、 64ビットのCortex-A72 (ARM v8)  ×4なので、アドレスは64ビット表示になります。

アクセス対象アドレスが0番地なら、きっとNULLポインタでアクセスした可能性が高いですね。

そして、それだけでなく、もうひと手間を惜しみません。
その「変なアドレス」が、スタック領域のチョイ先なら、スタック破壊かもしれない、という事を、エラー画面につけたします。

ユーザは、普段、スタックが今どのアドレスにあるのか、異常がない限りあまり気にしません。
いきなり、アドレス0x00000002e0a10ff0に不当なリードアクセスあり!と言われても、ちょっと「え?」となってしまいますよね。

スタック破壊かもしれない、と教えてもらって、その時のスタック範囲やタスクID等をGUIで表示してもらうと、もうそれは一目瞭然ですね。

では、どうやって、MMUはこれらを「不当アドレスアクセスだ」と認識するのでしょうか。

答えは。。。
冒頭に書いたように、
「要求された「仮想アドレス」に対応する「物理アドレス」が、テーブルに載っていない、あるいはアクセス属性が一致しない」
を意図的に作り出しています。

 

2.NULLポインタアクセス検出実現方法

ここ以降は、Cortex-A9搭載ボード×PARTNER Jet2の組み合わせで見ていきます。

(今回使用するCortex-A9は、32ビットアドレスなので、表示されるアドレスは32ビット表示になります。)

 

2.1 NULLポインタアクセスするプログラム

まず、サンプルプログラムのroot taskにNULLポインタへのライトアクセスをするコードを書いてみます。

実行してみます。

0番地へのデータアボート例外がライトアクセスで検出されました。

 

2.2メモリマップデザイナの設定状態

ここでメモリマップデザイナの設定を見てみます。

 

あれ?
仮想アドレス、0番地付近、登録されてんじゃん!

と思った方、早とちりです。

右横、Descriptionというオプションがあります。
ここで、NOR-FlashROMと登録されています。

ROMです。

リードアクセスのみ、許可されています。

「アクセス属性が一致していない」が作り出され、データアボート例外が発生する、というわけです。

ちなみに、Cortex A9の場合、MMUが識別することができる属性は以下です。

 

2.3 0番地への不正リードアクセスは?

では、NULLポインタに対しリードアクセスが発生した場合は、捕まえられるのでしょうか。

この例の場合、0番地付近はROMに命令コードがおかれている、というコンフィグレーションで動いています。すなわち、0番地付近に命令フェッチによるメモリリード要求が来ます。

データリードなのか、命令フェッチなのかまでは、MMUではわからないので、リードアクセスに関しては許可します。
したがって、このままではNULLポインタに対してのリードアクセスは、捕まえることができません。

「このままでは」、です。
という事は、
コンフィグレーションを少し考えれば大丈夫です!

0番地付近、通常RTOSが動作しているときは、必要がない事が多いです。
0番地付近にはリセットベクタがありますね。
リセット発生後はMMUがDisableなので、そもそもアドレス変換テーブルは関係ありません。

リセット後、初期化ルーチンでMMUをEnableにする時には、もうプログラムはRAMに展開してRAM上で動いていることが多いです。その際、各ベクタも移動しています。
ですので、アドレス変換テーブルからROM部分は削除しても大丈夫であることが多いです。(もちろん、システムの仕様によります。)

で、もし、リセット発生したら?
またMMUがDisable状態からスタートするので、大丈夫です。

と、いう事で。
ROM部は、アドレス変換テーブルから削除してしまいましょう!

では、NULLポインタ(0番地)をリードしてしまうプログラムを走らせてみます。

NULLポインタへのリードが捕まりました!

 

 

3.スタックオーバーフロー検出実現方法


3.1
スタックオーバーフローするプログラム

第一回では、特にタスクを作成せずに、main関数からスタックオーバーフローするような関数をコールしました。

今回は、「スタックオーバーフローあるある」の、

作成タスクに割り当てたスタックが足りない!

を、(サンプルプログラムを拝借して)試してみます。

*あらかじめカーネルコンフィグは生成済です。

タスクの各パラメータは以下です。

では、このタスク内で、スタックをオーバーフローさせてみましょう。

0x8fc00ff8へのライトアクセスで、スタックを突き破っているようです。
左下のメモリウインドウでも、0x8fc0100以前のアドレスはデータを取って来ることができません。

それだけではありません。
「デバッグ例外」ウインドウを良く見てみましょう。
スタックを突き破ってしまったタスクID値も表示されています。
今回、タスク5ですね。

左下のRTOSビュアーを見てみると、タスク5は、Running状態にあり、chilld_taskというエントリ名のタスクであることがわかります。
このタスクのスタックが狭すぎるのか、タスク内関数に問題があるのか、、、

デバッグの際、このように、疑わしい範囲が絞られるのは有難いです。

本機能は、SOLIDのユーザガイド内に、「スタックフェンス」という言葉で記載されています。
よろしければご参照ください。

https://solid.kmckk.com/SOLID/doc/latest/user_guide/stack_fence.html

 

 

3.2 メモリマップデザイナの設定状態

メモリマップデザイナの設定を見てみましょう。

OSSTACKという名称で登録されています。

仮想アドレスは、0x8fc00000から4Mbyte分登録されています。
一方、対応する物理アドレスは、NO-ADDRESSとなっており、明示的に記されていません。

これは、OSに任せる、という設定です。

 

メモリマップ名称や属性については、以下のURLに記載されています。

https://solid.kmckk.com/SOLID/doc/latest/os/cs/memorymap.html#reserved-memory-map-name

 

3.3 MMUアドレス変換テーブルの設定状態

では、OSに任されたこの「OSSTACK」領域ですが、今現在はどのように割ついているでしょうか?

SOLID-IDEは、PARTER Jetの持つコマンドを直接実行できます。
(昔のICEを使っていた方には、こちらの方がむしろ馴染みかもしれません)

MMUコマンド、というコマンドで、今現在のMMU登録状況を見ることができます。
具体的には、仮想アドレスを入力すると、対応する物理アドレス等が表示されます。

入力してみましょう。

現行タスクのスタック範囲である、0x8fc0100以上0x8fc0200未満は、アドレス変換テーブルに登録されているようです。

そして、その前後は登録されていません。
したがって、スタック範囲をオーバーしてしまうと、とたんにMMUがデータアボート例外を発生する、という動きになっていました。

SOLID OSは各タスクのスタック範囲を知っています。
そのため、そのタスクごとに、スタック範囲をアドレス変換テーブルに反映させることが、できているんですね。

そして、そのスタック範囲から少し超えたところには、連続して他のセクション等を配置することはせず、少し隙間を開けます。
こうすることによって、スタック範囲を超えてしまった瞬間に、アドレステーブルに載っていない範囲のアクセス、となる、という事です。

このような動作について、詳しく説明されているURLがありますので、ぜひご覧ください。

https://solid.kmckk.com/SOLID/archives/2844

 

3.4 検出できないパターン

先程、
「SOLID OSは各タスクのスタック範囲を知っています。」
と、しれっと書きました。

この機能は、SOLID OSがスタックの範囲を知っていることが前提です。
ユーザが自分でわりあててしまったら、OSに任せない事になるので、検出できません。

先程のプログラム内、タスク生成部を少し変更してみます。

tsk.stkをNULLにすると、OSに任せるという意味です。
ここに具体的な数字を入れてしまうと、このアドレス固定となり、OSの管理対象から外れてしまいます。

したがって、先述のように、タスク毎にそのスタック領域をアドレス変換テーブルに登録する、という制御ができなくなります。

この状態でプログラムを実行してみます。

いやー、そうですよね!
なんか、この、絶望感。。。

いつもいつも、スタックオーバーフローは、訳がわからない現象に化けて発生しますよね!
これ、デバッガつないでいるからまだ例外発生でブレークしますが、普通に走らせてると「謎のリセット」が発生して、そこからデバッグが始まるパターンですよね。。。

これを見てしまうと。
やっぱり、さっきの「この辺でスタックオーバーフローが起きてるんじゃない?」という事を、スタックオーバーフローが発生した瞬間にツールから言ってもらえる事のありがたみが、とてもわかりますね。。。

その他、留意すべき事項について、こちらのURLで解説されています。
よろしければご参照ください。
https://solid.kmckk.com/SOLID/archives/2971

 

 

4.まとめ

最後にスタックオーバーフローが引き起こす、現実逃避したくなるような現象を見たところで、今回は終わります。

こういった、気が遠くなるようなデバッグには、極力直面したくないので、ツールの力を借りれるのはとてもありがたいと思った次第です。

 

次回はもう一つの便利機能、「アドレスサニタイザ」についてご紹介&深堀りをする予定です。