2005年11月18日 (金曜日)

22:42:49 # Life ptraceあそび. ptraceをひさしぶりにいじってみる. ppcのノートパソコンであそんでいるので,ppc版で遊ぶしかない. 以前テスト用に作成していたプログラムがコンパイルできないのをきっかけに調査してみた. コンパイルしようとすると, PTRACE_GETREGS が無い,というエラーが出る. PPCでは,ptraceでは,レジスタの内容は読めないのか?と気になって調査した. ヘッダファイルを追跡していくと,linux/user.hをincludeすると, asm/user.h asm-ppc/user.h asm/page.hlinux/ptrace.h asm/ptrace.hasm-ppc/ptrace.hという順にincludeされていく. そのどこにもGETREGSが定義されていない. 念のためppc64を見てみると,若干内容が違うが,定義されていなかった. PPC_PTRACE_GETREGSというのは定義されているのが謎をただよわせる. カーネル側のソースは,arch/ppc/kernel/ptrace.csys_ptraceにある. そこを見ると,たしかにGETREGSは実装されていないようだった. ちなみに,arch/ppc64/kernel/ptrace.cには, PPC_PTRACE_GETREGSというのが実装されていた. うーむ.これはなんとも言えない. コードを見ているだけでは,実装するのは超絶に簡単そうなのだが, なぜ実装されていないのだろう. ppc32の開発がとどこおってしまうほどppc64が主流になってしまったとかいうことはないと 思うのだが. 謎だ.

ppcで,syscallがどうよばれるのか,も調べてみた. arch/ppc/kernel/entry.S がどうやらsyscallを受けるコードらしい. sys_call_table というのがあり, そこを見てsyscallの番号に対応した関数を呼び出しているようだ. arch/ppc/kernel/misc.Ssys_call_tableの実態があり, そこがアドレス表(というか,関数のアドレスの一覧)になっている. そこにsys_ptraceもある. コメントが素敵だ. 「なぜこれは自動ではなく,またCで書かれていないのだ?」と書いてある. i386にはarch/kernel/syscall_table.Sというsys_call_table だけを保持しているソースがある.それと見た目が同じなので,コピーしているだけなのだろう. 内容は一部違うので,単純には置き換えられない. sys_forkのある欄がppc_fork に置き換えられたりしている.

カーネル側だけを解析しているとどうやって実際に発行しているのかがいまいちわからないので, helloと出力するだけのプログラムを作成し, gcc -O2 -static a.c として,スタティックリンクしたバイナリを作成した. これで,システムコールを呼ぶ部分までを含むa.outというバイナリができる. それで,逆アセンブルしてみる. objdump -d a.out (なお,ここでwriteの書き込んでいるfile descriptor は 0であるが, 普通は file descriptor 0(標準入力?) に書き込むということはしないだろうと思う. 多分ここの部分では,寝惚けていた)

#include <unistd.h>
main()
{
	write(0, "hello\n", 6);
}

	

関係のありそうなところを追跡してみる. writeシステムコールは4番だ. どうやら,scという命令があり,それがシステムコールを発行してくれるっぽい. r0に4を代入して,scを呼んでいる.

100002e0 <main>:
100002e0:       7c 08 02 a6     mflr    r0
100002e4:       94 21 ff f0     stwu    r1,-16(r1)
100002e8:       3c 80 10 06     lis     r4,4102 ;; "helloのアドレスの上位ビット"
100002ec:       38 60 00 00     li      r3,0
100002f0:       38 84 9d f4     addi    r4,r4,-25100 ;; "helloのアドレスの下位ビット?"
100002f4:       38 a0 00 06     li      r5,6
100002f8:       90 01 00 14     stw     r0,20(r1)
100002fc:       48 00 7b 45     bl      10007e40 <__libc_write>
10000300:       80 01 00 14     lwz     r0,20(r1)
10000304:       38 21 00 10     addi    r1,r1,16
10000308:       7c 08 03 a6     mtlr    r0
1000030c:       4e 80 00 20     blr

10007e40 <__libc_write>:
10007e40:       81 42 8c 20     lwz     r10,-29664(r2)
10007e44:       2c 0a 00 00     cmpwi   r10,0
10007e48:       40 82 00 14     bne-    10007e5c <__libc_write+0x1c>
10007e4c:       38 00 00 04     li      r0,4
10007e50:       44 00 00 02     sc
10007e54:       4c a3 00 20     bnslr+

該当する部分をgcc -S で出力させると(なぜこんなに違う?)
main:
        mflr 0
        stwu 1,-16(1)
        lis 4,.LC0@ha
        li 3,0
        la 4,.LC0@l(4)
        li 5,6
        stw 0,20(1)
        bl write
        lwz 0,20(1)
        addi 1,1,16
        mtlr 0
        blr
	

write関数を呼び出す代わりにシステムコールを直接よぶように変更してみた. writeはファイルデスクリプタなどをオプションとしてとる. libc側のインタフェースは write(fd, *buf, count). r0がシステムコール番号. r3がfdっぽい. r4が*buf. r5がcount. これは,fs/read_write.cにある,sys_writeを結局呼んでいるのだろう. r1はスタックポインタとして利用しているようだ.値を破壊して,blrを呼ぶと SIGSEGV が発生する. また,main関数からの帰り値としては,r3が利用されているようだ.

/*BINFMTASMCPP:
*/
	.section	.rodata
	.align 2
.LC0:
	.string	"hello\n"
	.section	".text"
	.align 2
	.p2align 4,,15
	.globl main
	.type	main, @function
main:
	lis 4,.LC0@ha
	li 3,1
	la 4,.LC0@l(4)
	li 5,6
	li 0,4
	sc
	li 3,0
	blr
	.size	main,.-main
	

...といじっていたが,で,ptraceはどうなった? ptraceをがんばろうとおもっても,システムコールをトラップするには, r0に何がはいっているか,などという情報が必要な気がする. straceはppc上でどうやって実装されているのかを調査する必要がある.

なにげなくいろいろと見ていると,arch/i386/kernel/vsyscall-sysenter.S で手書きでDWARF2デバッグ情報らしきものを書いてある. oprofileをみると,例えば,arch/i386/oprofile/backtrace.cは現在 FRAME_POINTERではないならJUNKだ,と返してしまうようになっているが,これを なんとかDWARF2を処理できるようにならないだろうか. スタックを見ればバックトレースを作成するのに十分な情報が取得できるような気がするのだが, なにか見落としているのだろうか. (これを見ていて気づいたのだが, なんとなく,ppcでは,oprofileで, backtraceを処理するコードがないようにも見える.この影響でcall-graphが計算できないことになるのではないだろうか.)

ぐぐってみたら,backtraceをdwarf2の情報から作成する サンプルっぽいのがあった. えらく大きいのと, まだ全容を把握できていないので,とりあえずメモ.

Junichi Uekawa

$Id: dancer-diary.el,v 1.89 2005/05/12 11:19:14 dancer Exp $