(注意)このブログは本家のほうの文章部分のみの転載です.ソースコードの配布,画像などについては本家のほうを参照してください.文章中のリンク先は面倒なのですべて本家のほうに変換してしまっているのでご注意ください.
運転疲れとあと腹が減っているのだが,書きためていたぶんがあるので,前回の続きを一気に書いてしまおう.
前回はプロファイラの -pg の機能を利用して,ログ保存をしてみた.今回はもうちょっと発展させてみる.
前回説明したのだけど,_mcount()から戻る際にはLRの値を設定した上で戻る必要がある.で,前回の実装では,_mcount() 側でLRを設定し,実際のリターンは bctr で行っていた.まあこれはこれでいいのだけど,実はここでblrlで戻ってしまえば,戻るときにLRの値が書き変わることで本来の関数の戻り先が _mcount() 側になるので,関数の先頭と終端で _mcount() を呼ばせることができるのではなかろうか.で,本来のプロファイラでは,_mcount() はそのように実装すべきなんじゃないかと思える.
で,書いたのが次のようなかんじ.
関数呼び出し時には logging_begin() が呼ばれ,関数の終了時(return文によりblr命令が実行されたとき)にはblrlの直後に戻り,logging_end() が呼ばれるようになっている.
logging_end() の呼び出し時に保存が必要なレジスタなのだけど,PowerPCのABIでは関数の戻り値はGPR3,GPR4で返すということになっている.なのでこの2つだけ保存しておくようにしている(でないと呼び出し元の関数の戻り値が破壊されてしまうことになる).
logging.c は,logging() を logging_begin() に名前変更して,あと logging_end() を追加した.以下のようなかんじ.
logging_end()では,とりあえずアドレスの最上位ビットを立てて保存することで,logging_begin()によるログと区別できるようにしてある.
ソースコードは以下のような感じ.
では実行してみよう.前回と同じように起動して,logコマンドを実行してみる.
おー,0x00043xxx と 0x80043xxx が表示されている.0x00043xxx が関数呼び出し,0x80043xxx が関数の終了に相当する.いいかんじだ.
ところでこれには現状で問題がある.というのは,引数の数が8個を越えるとGPR3~GPR10では渡しきれないので,残りの引数はスタックに積んで渡すことになる.しかし _mcount() 内部では呼び出し元の関数の前に,自前でスタックを積んでしまっているので,呼び出し元の関数は,引数が保存されているスタックを正常に読み取ることができない.なので9番目以降の引数は,本来の値とは異なるおかしな値が渡されることになる.(前回は _mcount() 側のスタックを解放してから呼び出し元に戻っているので,このような問題は無い)
この問題の根本的な原因は,_mcount()側でスタックを確保しっぱなしになっているということだ.これを回避する手段については,前回のように _mcount() から戻る際にはスタックを全解放するしかないが,それをやると2回目の呼び出し時にLRの値が残っていないので,正常に戻れない.
対策としては,以下が考えられる.
まあこれはそのうちちゃんと考えよう.
運転疲れとあと腹が減っているのだが,書きためていたぶんがあるので,前回の続きを一気に書いてしまおう.
前回はプロファイラの -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の値が残っていないので,正常に戻れない.
対策としては,以下が考えられる.
- 引数は8個までと制限を設ける.
- 関数終了時にも _mcount() を呼ばせるのはあきらめて,前回のコードを使う.
- _mcount()内でスタック作成時に,ひとつ前のスタックフレームの数バイトをコピーして持っておく(結局のところ引数の数に制限はあるので根本的な解決ではないが,制限を拡張してチューニングすることができる)
- LRの値を保存するための独自スタックを別に作る.
まあこれはそのうちちゃんと考えよう.