OSECPUでのポインタの扱い
テーマ1
int *a = malloc(1234);
free(a);
a[123] = 123;
- このプログラムは明らかにまずい、free()したあとのメモリに触っている。だからこれをちゃんと検出できる仕組みを作りたい。
- ポインタレジスタを128itにする。そのうちの32bitは普通のポインタで、残りの96bitはいろいろな情報を詰め込む。この96bit部分についてはアプリは読み書きできない。
- void *p; // ポインタ本体
- PTRHND *phnd; // ポインタハンドル
- int sign; // シグネチャ
- int dummy; // リザーブ
- ポインタハンドルを参照すれば、このポインタpの参照先のメモリがいつどこでmallocされたメモリに属しているかが分かる。この中にはシグネチャ(乱数でmalloc時に生成)も書かれている。
- ポインタを初期化する時に、ポインタハンドルからシグネチャをコピーしてくる。
- そしてポインタを使ってアクセスする時は、ポインタハンドルのシグネチャとポインタの中のシグネチャが一致しているかどうか比較する。不一致ならバグ検出エラーである。
- freeした場合、ポインタハンドルの先のシグネチャは乱数で上書きされる。だからfreeしたあとでポインタを再度使ってしまうミスはすべて検出できる。
- 乱数がたまたま一致してしまった!という場合は突破できてしまうけど、乱数以外のうまい方法を思いつかず・・・。
テーマ2
- [テーマ1]で「バグ検出エラー」が出るのは非常にうれしいけど、じゃあどのfreeのせいで使えなくなったのかを教えてくれたらもっとうれしい。
- それはfree時にポインタハンドルに情報を書き残しておけばよい。
- でもポインタハンドルを別のオブジェクトの管理のために再利用してしまう可能性がある!・・・だってそうしないとmalloc/freeを繰り返すプログラムがあったら、いつかメモリがなくなってしまう!
- ということで、malloc()時にポインタハンドルを再利用しないでねっていうデバッグ用オプションをつけられるようにする。デバッグで追跡する時は、怪しいところにこのフラグをつけておきましょう。
- なおプログラムが高速危険モードで実行されている時は、このフラグは無視されます。
- そもそも高速危険モードの場合は、いくつかのバグ検出機能もOFFになっています。全部OFFになっている可能性もあります。
テーマ3
- [テーマ1]や[テーマ2]で、不適切なfree()は多分容易に発見できるようになったと思います、思いたい!・・・次はfreeが足りない場合を考えます。
- これはプログラムの実行中の好きなタイミングで、ポインタハンドルの一覧が見られたらいいのかなと思いました。
- ポインタハンドルにはいろんな情報がつまっています。malloc()時にいろんな情報をもらいます。
- これで「あれ?このオブジェクトがどうしてまだ居座っているんだ?」ってなって気づくことができるのではないかと・・・。
- ループする度にリークしているのが分かったりしたらいいですよね。
テーマ5
- OSECPUのデータポインタにできること、できないこと。まとめ。
- 仕様を詰めたのでやっと断言できるようになりました。
- (1) unionは使えません(そういう構文がない)。
- (2) 互換性のあるポインタへのキャストはできます。
- (3) int a[3]; で宣言されているのなら、a[4]へはアクセスできません。a[-1]もできません。
- (4) 2つのポインタを大小比較することができますが、これは両ポインタが同じ配列に属している場合に限られます。
- (5) 大きな配列から部分配列を作ることができます(これは配列のコピーをするわけではありません・・・もちろんコピーして部分配列を作ることもできます)。
- (6) 型を間違えたらエラーです。
- (7) OSECPUのCPUはPxxについて、型情報やアクセス可能域に関する情報を持っていますが、これはエラー判定のためだけに使われることになっていて、これらの情報を取得する命令は一切ありません。
- (8) ADDPTR命令でポインタのシークができますが、この時アクセス域を超えてしまったらその時点でエラーです。結果はC言語と同様にクラスのサイズでスケールします。
- つまり実際にメモリアクセスを発生させなくてもエラーが出ます。
- (9) SUBPTR命令で二つのポインタの引き算ができますが、これは比較可能なポインタ同士でなければエラーです。結果はC言語と同様にクラスのサイズでスケールします。
- (10) メモリ上の値の符号の有無やエンディアンについては、JITCが自動で判定します。この決定に逆らうことはできません。ですから signed char のメモリに対して、ゼロ拡張のリードはできないのです。
- (11) たとえば signed char のメモリに対して128を書き込もうとしたらエラーです。
- このエラーを検出できない可能性もあります・・・すみません!
- (12) [テーマ1]のようにfree()したメモリ域を指しているポインタはすべて無効になっています。
- (13) realloc()ではメモリ域を縮小する場合であっても、ポインタが更新されます。
- これは同じ領域を指している他のポインタを確実に無効化するためで、新しいサイズを確保してコピーした後、古いオブジェクトはfreeされるのです。
- (14) malloc()でint4個分の領域を取ったのなら、やはりそこより先にはシークもアクセスもできません。
- (15) int a[4][4]; のような配列があったとして、これを int *b = a[0]; にすることはできます。そしてb[4]は間違いなくa[1][0]を指しているわけですが、b[4]はエラーになってアクセスできません。
- これをどうしてもやりたいときはint b[16];で領域を確保して、a[][]の内容をコピーしてください。それ以外の方法は用意しません。
- (16) データポインタにJMPすることはできません。しかしOSを使えばメモリ上にOSECPUのバイトコードを並べた状態で、それにJITコンパイラをかけることができ、その際は結果として分岐可能なポインタが返されます。
- これを使えば言語処理系みたいなものも作れますね!
- C言語ではこれが標準の範囲ではできません。プログラム内でC言語のソースを生成しても、実行できる保証はないのです。gccをsystem()関数で起動しますか?そして出力された実行ファイルをsystem()で起動しますか?いずれにせよ、それは環境依存の方法です。
- (17) ある命令に対して、エラーを起こすかどうかをチェックする命令はありません。またエラーが起きた時にそれをエラーハンドラというか例外ハンドラで受ける方法もありません。
- この手のポインタのエラーは、普通のプログラムのやり方で容易に防げるものです。ですからOSECPUのエラー検出能力に頼ってはいけません。
テーマ6
- システムコールから戻る時にレジスタを放置して大事なポインタが流出してしまったらどうしよう?
- システムコールから帰るときはやばいポインタが残っていないかチェックする?というかやばいポインタってどうやってOSが入手するんだ?結局はこんなこと気にする必要がない???
こめんと欄
- このページにこめんと欄はありません。このページの内容にコメントしたいときはimpressionsにお願いします。