MallocDebug と mach_override

追記:あとで分かったことがあったので一部書き直しました。

今日ひょんなことから XCode Tools の一部で /Developer/Applications/Performance Tools にある MallocDebug.app を使ってみた。
これ、便利すぎ。valgrind がないというだけで Mac OS X の開発は気が重かったけど、大体は要求をカバーしてくれそうだ。
f:id:moriyoshi:20080202025316p:image
ただ、このツールはGUIアプリのようにセッションが長いプログラムのデバッグ用に作られたようで、ターゲットが終了してしまうとそれまでの情報がすべて失われてしまうので、コンソールで動くちょっとしたアプリなどでは使い物にならない。

そこで、exit() で停止させることを考えて、GDB からターゲットを起動し、

set environ DYLD_INSERT_LIBRARIES=libMallocDebug.A.dylib
break exit
run

のようにやってみるも失敗。既に libMallocDebug の管理下にあるプロセスには GDB がアタッチできないようだ。

なので次のような簡単なコードの dylib を同様に DYLD_INSERT_LIBRARIES に挟んでやることで解決。

#include <unistd.h>
#include <stdlib.h>
static void sleeper(void)
{
    sleep(100);
}

void __initialize(void)
{
    atexit(sleeper);
}

上記を

$ gcc -dynamiclib -init ___initialize -o sleeper.dylib sleeper.c

としてコンパイル、sleeper.dylib を DYLD_LIBRARY_PATH のどっかに入れておいて

DYLD_INSERT_LIBRARIES=libMallocDebug.A.dylib:sleeper.dylib ls

とかして MallocDebug からアタッチする。

ちなみに、

LD_PRELOAD 的な方法

#include <stdio.h>
#include <dlfcn.h>
#include <unistd.h>

static void (*real_exit)(int);

void exit(int code)
{
    sleep(100);
    real_exit(code);
}

void __initialize(void)
{
    real_exit = dlsym(RTLD_NEXT, "exit");
}

これを

gcc -flat_namespace -dynamiclib -init ___initialize -single_module -o sleeper.dylib sleeper.o 

のようにしてコンパイル。実行方法は一緒。

mach_override の場合

mach_override を使うと、ランタイムで関数の実装を置き換えることができる。ページではリンク切れになってしまっているが、ダウンロードは http://extendamac.svn.sourceforge.net/svnroot/extendamac/trunk/code/mach_override/ から可能。

#include <mach_override.h>
#include <unistd.h>

void (*real_exit)(int);

static void my_exit(int code)
{
    sleep(100);
    real_exit(code);
}

void __initializE(void)
{
    mach_override("_exit", NULL, (void *)my_exit, (void **)&real_exit);
}

これを

gcc -init ___initialize -dynamiclib -I../mach_override -o sleeper.dylib sleeper.c ../mach_override/mach_override.c -framework CoreServices

こんな風にコンパイルしてやればいいと思う。