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.cの sys_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.Sにsys_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の情報から作成する サンプルっぽいのがあった. えらく大きいのと, まだ全容を把握できていないので,とりあえずメモ.
$Id: dancer-diary.el,v 1.89 2005/05/12 11:19:14 dancer Exp $