2006年2月12日

ltrace で共有ライブラリの関数呼び出しをトレースする

ltrace は共有ライブラリの関数呼び出しをトレースする Linux 用のツールです。システムコールをトレースするstrace と同様に、デバッグに大変役立ちます。

 

ltrace は Debian GNU/Linux の場合は sudo apt-get install ltrace でインストールできます。

ltrace の使い方は簡単です。基本的には ltrace コマンドの引数にトレースしたいコマンドとその引数を並べれば OK です。デフォルトでは ltrace のメッセージは標準エラーに出力されます。これをファイルに出力させるには -o オプションを用います。たとえば、次のように実行します。

% ltrace -o log.txt wget https://www.codeblog.org/

この例では wget が https://www.codeblog.org/ のコンテンツを取得するときの共有ライブラリの呼び出しをトレースしています。wget は https の通信に OpenSSL の libssl.so を用いています。ltrace が出力した log.txt を SSL で grep するとどのような関数が呼び出されたかがわかります。

% grep SSL log.txt | head
SSL_library_init(0, 0, 0, 0, 0)                  = 1
SSL_load_error_strings(0, 0, 0, 0, 0)            = 0
OPENSSL_add_all_algorithms_noconf(0, 0, 0, 0, 0) = 1
SSL_library_init(0, 0, 0, 0, 0)                  = 1
SSLv23_client_method(0, 0, 0, 0, 0)              = 0x40038880
SSL_CTX_new(0x40038880, 0, 0, 0, 0)              = 0x808b228
SSL_CTX_set_verify(0x808b228, 0, 0x8068585, 0, 0) = 0x8068585
SSL_new(0x808b228, 0x7a060ed3, 1, 0, 0)          = 0x808cd20
SSL_set_fd(0x808cd20, 3, 1, 0, 0)                = 1
SSL_set_connect_state(0x808cd20, 3, 1, 0, 0)     = 0
SSL_connect(0x808cd20, 3, 1, 0, 0 

-p オプションを用いると既存のプロセスにアタッチすることもできます。

仕組み

ltrace の仕組みはデバッガと似ています。 ltrace はデバッガと同様に、トレースの対象となるプロセスにソフトウェア的なブレークポイントを埋め込みます。上のように ltrace wget ... と実行した場合、 ltrace は次のような処理を行います。

  • 環境変数PATHをたどって wget のバイナリの絶対パスを調べる (私の環境では /usr/bin/wget)
  • /usr/bin/wget のバイナリと依存しているすべての共有ライブラリを elfutils を用いて読み込み、関数のシンボル名とその PLT 内のアドレスのリストを取得する
  • fork して子プロセス内で ptrace(PTRACE_TRACEME, ...) をセットし、それから wget を exec する
  • wait() で待っている親プロセスに SIGTRAP が伝わる
  • 親プロセスでは先ほど作っておいたリストを元に、各関数の PLT の該当アドレスにブレークポイント命令 (i386 では 0xcc) を書き込む 。このとき、書き換える前の値を保存しておく
  • これにより子プロセスが共有ライブラリの関数を呼び出すたびに SIGTRAP が発生するので、親プロセスはループ内で wait で SIGTRAP を待って適宜ブレークポイントしつつ、子プロセスが終了するまでループを回す

親プロセスはブレークポイントでトレースを出力した後に、ブレークポイント命令を書き込んだアドレスの値を元に戻し、ptrace(PTRACE_SINGLESTEP, ...) で子プロセスを 1命令分だけ進めます。1命令の実行が終わると、ふたたび親プロセスに制御が戻り、ブレークポイントを復元します。

PLT (Procedure Linkage Table) には ELF の共有ライブラリの関数を呼び出すときに必ず経由するコードが各関数ごとに用意されています。 ltrace はこの PLT にブレークポイントを書き込むことによって、共有ライブラリの関数呼び出しをフックしています。

まとめ

ltrace を使うと、共有ライブラリの関数呼び出しをトレースできます。プログラムのデバッグと動作の把握に大変役立つツールです。