運転疲れとあと腹が減っているのだが,書きためていたぶんがあるので, 前回の続きを一気に書いてしまおう.
前回はプロファイラの -pg の機能を利用して,ログ保存をしてみた. 今回はもうちょっと発展させてみる.
前回説明したのだけど,_mcount()から戻る際にはLRの値を設定した上で戻る必要が ある.で,前回の実装では,_mcount() 側でLRを設定し,実際のリターンは bctr で 行っていた.まあこれはこれでいいのだけど,実はここでblrlで戻ってしまえば, 戻るときにLRの値が書き変わることで本来の関数の戻り先が _mcount() 側になるので, 関数の先頭と終端で _mcount() を呼ばせることができるのではなかろうか. で,本来のプロファイラでは,_mcount() はそのように実装すべきなんじゃないかと 思える.
で,書いたのが次のようなかんじ.
.text .globl _mcount .type _mcount,@function _mcount: stwu 1,-48(1) stw 3,16(1) stw 4,20(1) stw 5,24(1) stw 6,28(1) stw 7,32(1) stw 8,36(1) stw 9,40(1) stw 10,44(1) mflr 3 stw 3,8(1) bl logging_begin lwz 10,8(1) mtlr 10 lwz 3,16(1) lwz 4,20(1) lwz 5,24(1) lwz 6,28(1) lwz 7,32(1) lwz 8,36(1) lwz 9,40(1) lwz 10,44(1) blrl stw 3,16(1) stw 4,20(1) lwz 3,8(1) bl logging_end lwz 10,52(1) mtlr 10 lwz 3,16(1) lwz 4,20(1) addi 1,1,48 blr関数呼び出し時には logging_begin() が呼ばれ,関数の終了時(return文により blr命令が実行されたとき)にはblrlの直後に戻り,logging_end() が呼ばれるように なっている.
logging_end() の呼び出し時に保存が必要なレジスタなのだけど,PowerPCのABIでは 関数の戻り値はGPR3,GPR4で返すということになっている.なのでこの2つだけ保存して おくようにしている(でないと呼び出し元の関数の戻り値が破壊されてしまうことに なる).
logging.c は,logging() を logging_begin() に名前変更して, あと logging_end() を追加した.以下のようなかんじ.
void logging_begin(int addr) { if (!logging_disable) { logging_buf[logging_cur++] = addr; if (logging_cur >= LOGGING_BUF_SIZE) logging_cur = 0; } } void logging_end(int addr) { if (!logging_disable) { logging_buf[logging_cur++] = addr | (1<<31); if (logging_cur >= LOGGING_BUF_SIZE) logging_cur = 0; } }logging_end()では,とりあえずアドレスの最上位ビットを立てて保存することで, logging_begin()によるログと区別できるようにしてある.
ソースコードは以下のような感じ.
では実行してみよう.前回と同じように起動して,logコマンドを実行してみる.
> echo aaa aaa OK > log 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 00043268 80043268 00043268 80043268 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 00043508 000430e4 800430e4 0004314c 8004314c 000430e4 800430e4 80043508 00043268 80043268 OK >
おー,0x00043xxx と 0x80043xxx が表示されている.0x00043xxx が関数呼び出し, 0x80043xxx が関数の終了に相当する.いいかんじだ.
ところでこれには現状で問題がある.というのは,引数の数が8個を越えると GPR3〜GPR10では渡しきれないので,残りの引数はスタックに積んで渡すことになる. しかし _mcount() 内部では呼び出し元の関数の前に,自前でスタックを積んで しまっているので,呼び出し元の関数は,引数が保存されているスタックを 正常に読み取ることができない.なので9番目以降の引数は,本来の値とは異なる おかしな値が渡されることになる.(前回は _mcount() 側のスタックを解放してから 呼び出し元に戻っているので,このような問題は無い)
この問題の根本的な原因は,_mcount()側でスタックを確保しっぱなしになっていると いうことだ.これを回避する手段については,前回のように _mcount() から戻る際には スタックを全解放するしかないが,それをやると2回目の呼び出し時にLRの値が残って いないので,正常に戻れない.
対策としては,以下が考えられる.
まあこれはそのうちちゃんと考えよう.