member functions tracer
なんか ModuleInfo とか ClassInfo あたりまでは reflection できてるのなー、ということで これ をちゃんと実装してみた。
http://shinh.skr.jp/d/tracer.tgz
% cat example.d import tracer; class C { void f() { g(); } void g() { } } void main() { initTracer(); C c = new C(); c.f(); } % dmd example.d tracer.d gcc-3.4 example.o tracer.o -o example -m32 -lphobos2 -lpthread -lm % example % ./dumptrace example void example.C.f() @0804a382 (from _Dmain @0804a3bc) void example.C.g() @0804a394 (from void example.C.f() @0804a38a)
こんな感じで呼び出し履歴を保存しておいて後でダンプできるわけ。
backtrace
で、そういう理由でこう、何度目かの backtrace 実装とかしてたんですが、 glibc の backtrace って DWARF2 使う実装とかあるんだなーと気付いた。 GCC ならこれ使うのが一番楽だし、 -fomit-frame-pointer ついてても大丈夫とか偉い。
// They are in libgcc (see glibc-2.7/sysdeps/ia64/backtrace.c) extern(C) int _Unwind_Backtrace(void* fn, void* a); extern(C) void* _Unwind_GetIP(void* ctx); struct Arg { void** buffer; int size; int cnt; } int backtrace_helper(void* ctx, void* data) { Arg* arg = cast(Arg*)data; if (arg.cnt != -1) { arg.buffer[arg.cnt] = _Unwind_GetIP(ctx); } if (++arg.cnt == arg.size) { return 5; } else { return 0; } } extern(C) int backtrace(void** buffer, int size) { if (size <= 0) return 0; Arg arg; arg.buffer = buffer; arg.size = size; arg.cnt = -1; _Unwind_Backtrace(&backtrace_helper, &arg); return arg.cnt; }
というわけで GCC 環境ならこれ使って、 Win32 では StackWalk なり x86 と x86_64 だけ頑張るなりすれば、メジャーどころな環境はだいたいおさえれそうなのかなぁと思いました。
あと x86_64 でスタックフレームがある場合の backtrace 関数とか書いたんだけど、 GDC のインラインアセンブラが 64bit 命令とかレジスタとか対応してないという問題でアセンブリでしょぼしょぼ書いたのだった。そしてその直後で上記を見つけて完全に意味の無い物体になってしまったのでここに意味もなく貼って供養。なむー
.globl backtrace backtrace: push %rbp mov %rsp, %rbp push %rdi push %rsi mov %rbp, %rdx loop: cmp $0, %rsi je done mov 8(%rdx), %rax mov %rax, (%rdi) mov (%rdx), %rdx add $8, %rdi dec %rsi cmp $0, %rdx jne loop done: sub (%rsp), %rsi xor %rax, %rax sub %rsi, %rax pop %rsi pop %rdi leaveq retq
ちなみに MS の calling convention だと RDI を RCX にして RSI を RDX にしないといけないぽい、が、未確認。
http://msdn2.microsoft.com/en-us/library/ms794612.aspx
どうでもいいんだけどMSDN の StackWalk見てて、IMAGE_FILE_MACHINE_* ってこんな少ないんかい! と思って winnt.h とか見てたら結構たくさん出てきてびびった。
#define IMAGE_FILE_MACHINE_UNKNOWN 0x0000 #define IMAGE_FILE_MACHINE_AM33 0x01d3 /* Matsushita AM33 */ #define IMAGE_FILE_MACHINE_AMD64 0x8664 /* x64 */ #define IMAGE_FILE_MACHINE_ARM 0x01c0 /* ARM little endian */ #define IMAGE_FILE_MACHINE_EBC 0x0ebc /* EFI byte code */ #define IMAGE_FILE_MACHINE_I386 0x014c /* Intel 386 or later processors and compatible processors */ #define IMAGE_FILE_MACHINE_IA64 0x0200 /* Intel Itanium processor family */ #define IMAGE_FILE_MACHINE_M32R 0x9041 /* Mitsubishi M32R little endian */ #define IMAGE_FILE_MACHINE_MIPS16 0x0266 /* MIPS16 */ #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 /* MIPS with FPU */ #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 /* MIPS16 with FPU */ #define IMAGE_FILE_MACHINE_POWERPC 0x01f0 /* Power PC little endian */ #define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 /* Power PC with floating point s upport */ #define IMAGE_FILE_MACHINE_R4000 0x0166 /* MIPS little endian */ #define IMAGE_FILE_MACHINE_SH3 0x01a2 /* Hitachi SH3 */ #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 /* Hitachi SH3 DSP */ #define IMAGE_FILE_MACHINE_SH4 0x01a6 /* Hitachi SH4 */ #define IMAGE_FILE_MACHINE_SH5 0x01a8 /* Hitachi SH5 */ #define IMAGE_FILE_MACHINE_THUMB 0x01c2 /* Thumb */ #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 /* MIPS little-endian WCE v2 */
CE とか Mobile とかで動いたりするんかな。