ltraceで関数呼び出しをトレース

2012-05-16

ltraceとは

ltraceは共有ライブラリの関数呼び出しをトレースするコマンドです。

特にソースコードを変更せずに、コマンドで指定するだけなので気軽に使えます。

今回はltraceを使って関数の呼び出しをトレースする方法を紹介します。

下ごしらえ

まずは呼び出される共有ライブラリを作ります。

foo.cとして以下のコードを保存します。

#include <stdio.h>
#include "foo.h"

void foo()
{
  printf("foo called!\n");
}

今回はfoo.hとしてヘッダーファイルも用意します。

void foo();

ひとまずここまででコンパイルして共有ライブラリを作成します。

$ gcc -shared -fPIC -o foo.so foo.c

次に呼び出し元のコードをmain.cとして保存します。

#include "foo.h"

int main(int argc, char *argv[])
{
  foo();
  return 0;
}

コンパイルします。

$ gcc main.c foo.so

ちなみにmain.cを先に指定しないとリンクエラーになります。

参照元が先に指定されないとリンクエラーになるので注意が必要です。

リンクに成功したら実行してみましょう。

$ ./a.out
./a.out: error while loading shared libraries: foo.so: cannot open shared object file: No such file or directory

あれれ、エラーが出てますね。

私の環境ではLD_LIBRARY_PATHにカレントディレクトリが含まれていないのでfoo.soを実行時に見つけることができません。

LD_LIBRARY_PATHを指定して実行します。

$ LD_LIBRARY_PATH=. ./a.out
foo called!

無事に実行されました。

ltraceを使う

それでは準備ができましたので本題のltraceを使ってみましょう。

ltraceの引数として実行形式を指定します。

$ LD_LIBRARY_PATH=. ltrace ./a.out
__libc_start_main(0x40067c, 1, 0x7fffd110d578, 0x4006a0, 0x400730 <unfinished ...>
foo(1, 0x7fffd110d578, 0x7fffd110d588, 0x4006a0, 0x400730foo called!
) = 12
+++ exited (status 0) +++

実行されてトレースの情報が表示されていますが、なにやらプログラムの出力と混ざってしまったようです。

プログラムの出力を/dev/nullにつっこみます。

$ LD_LIBRARY_PATH=. ltrace ./a.out > /dev/null
__libc_start_main(0x4005b4, 1, 0x7fffa905a7d8, 0x4005e0, 0x4005d0 <unfinished ...>
foo(1, 0x7fffa905a7d8, 0x7fffa905a7e8, 0, 0x3abd38b300)                                          = 12
+++ exited (status 0) +++

余計なものがなくなりました。

ちなみに'= 12'はfooを呼び出したあとの戻り値用のレジスタの値が表示されているようです。

fooの戻り値の型はvoidなので戻り値はないのですが、printfの戻り値が、12バイト出力したので12で、 それが戻り値用のレジスタにはいったままなのでfooの戻り値が12と認識されています。

それでは試しにインラインアセンブラで戻り値用のレジスタに値'3'を入れてみましょう。

私の環境では戻り値用のレジスタは%raxなので次のように変更します。

#include <stdio.h>

void foo()
{
  printf("foo called!\n");
  __asm__ ("mov $3, %rax");
}

コンパイルして実行してみましょう。

$ gcc -shared -fPIC -o foo.so foo.c
$ LD_LIBRARY_PATH=. ltrace ./a.out > /dev/null
__libc_start_main(0x4005b4, 1, 0x7fff57970d68, 0x4005e0, 0x4005d0 <unfinished ...>
foo(1, 0x7fff57970d68, 0x7fff57970d78, 0, 0x3abd38b300)                                          = 3
+++ exited (status 0) +++

3になっていることがわかります。

まとめ

今回はltraceを使って関数の呼び出しをトレースする方法を紹介しました。

バイナリがどの共有ライブラリのどの関数を呼び出して、その戻り値がどうなっているのか簡単に確認できるため、 ソースのないプログラムでも何をやっているのかを知る手がかりとなります。

ご質問等、何かありましたらこちらへ。