(移植編第7回)関数終了時のログもとってみるか

2009/04/19

あなたは 人目のお客様です.

運転疲れとあと腹が減っているのだが,書きためていたぶんがあるので, 前回の続きを一気に書いてしまおう.

前回はプロファイラの -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の値が残って いないので,正常に戻れない.

対策としては,以下が考えられる.

  1. 引数は8個までと制限を設ける.
  2. 関数終了時にも _mcount() を呼ばせるのはあきらめて,前回のコードを使う.
  3. _mcount()内でスタック作成時に,ひとつ前のスタックフレームの数バイトを コピーして持っておく(結局のところ引数の数に制限はあるので根本的な解決では ないが,制限を拡張してチューニングすることができる)
  4. LRの値を保存するための独自スタックを別に作る.
きちんとやろうとしたら4の方法しか思いつかないのだけど,これはスタックの 計算処理が入るので,mcount()がちょっと複雑になってしまう.まあ現実的な ところでは1か2だろうか.

まあこれはそのうちちゃんと考えよう.


メールは kozos(アットマーク)kozos.jp まで