C/C++プログラマのためのDTrace入門

第3回 マルチスレッド/子プロセス/共有ライブラリ

この記事を読むのに必要な時間:およそ 7 分

共有ライブラリ

今時のプログラムは共有ライブラリ(いわゆるlib*.so)を使用するのが一般的になっています。

主要な機能の大半が共有ライブラリによって実現されている,といったケースも多々ありますので,共有ライブラリからの情報採取の重要性は非常に高いと言えます。

共有ライブラリの問題

リスト8に示すスクリプトwatch_flow_whole.dを使用して,ZFSの/usr/sbin/zpoolコマンド実行時のlibc.so 中の関数に対する呼び出しフローを採取してみましょう。

リスト8 関数フロー採取のDスクリプト (watch_flow_whole.d)

pid$target:$1::entry,
pid$target:$1::return
{
}

以下に実行例を示します。

図7 libc.soの関数フロー採取

$ dtrace -F \
         -s watch_flow_whole.d \
         -c '/usr/sbin/zpool status' \
         libc.so
dtrace: script 'watch_flow_whole.d' matched 5815 probes
CPU FUNCTION
  0  -> __tls_static_mods
  0    -> lmalloc
  0      -> getbucketnum
  0      <- getbucketnum
  0      -> initial_allocation
  0        -> __systemcall
  0        <- __systemcall
  0      <- initial_allocation
  0    <- lmalloc
  0    -> tls_modinfo_alloc
....

dtrace コマンドに渡している引数"libc.so" は,watch_flow_whole.dでのプローブ記述のモジュール指定部分に記述された"$1"と置換されます。

第2回での文法の説明の際には割愛しましたが,プローブ記述のモジュール指定部分には,コマンド名以外にも共有ライブラリ名を指定することができます。共有ライブラリ名を指定した場合,共有ライブラリ中の関数が情報採取の対象となります。

それではlibc.so以外の共有ライブラリの関数フローも採取してみましょう。まずはzpoolが参照している共有ライブラリの一覧を取得します。

図8 zpool参照先共有ライブラリの取得

$ ldd /usr/sbin/zpool
        libzfs.so.1 =>      /lib/libzfs.so.1
        libnvpair.so.1 =>   /lib/libnvpair.so.1
        libdevid.so.1 =>    /lib/libdevid.so.1
.....
$ 

先ほどはlibc.soの関数フローを採取しましたので,同じ要領で今度はlibzfs.soの関数フローを採取してみましょう。

図9 libzfs.soの関数フロー採取~その1

$ dtrace -F \
         -s watch_flow_whole.d \
         -c '/usr/sbin/zpool status' \
         libzfs.so
dtrace: failed to compile script watch_flow_whole.d: line 1: \
  probe description pid12634:libzfs.so::entry \
  does not match any probes
$ 

どうやら思ったようには実行されません。⁠指定と合致するプローブが無い」と言っているようです。

実はこの問題は,共有ライブラリのローディング契機に由来するものです。

通常のC/C++プログラムの起動では,シンボル解決の必要上,プロセスが起動された時点で当該プロセスの空間にlibc.soがマッピングされます(※4⁠

プロセスが起動された段階で,当該プロセスのアドレス空間からlibc.soを参照することができるため,DTrace は情報採取用のコードを埋め込むことができます。

しかし libzfs.so の場合,共有ライブラリ内のいずれかの関数が呼ばれるまでは,プロセスの空間内にマッピングされません。

そのため,DTraceによる情報採取コードの埋め込みをコマンド起動時点で行うことができず,結果として上記のようなエラーメッセージが表示されてしまうわけです。

※4)
環境によってはマッピングされない可能性もあります。

共有ライブラリ中の関数フローの採取

対象とする共有ライブラリからの情報採取ができないのは,プロセス起動の段階で共有ライブラリがマッピングされていないのが原因ですから,以下の様な解決策が考えられます。

  1. 共有ライブラリがマッピングされるまで待ってから採取を開始する
  2. プロセス起動時点で共有ライブラリをマッピングさせる

前者の方法は,デバッガ等を使用することで共有ライブラリのマッピング契機を検知し,その時点からdtraceコマンドの適用を開始する,というものですが,あまり簡単な方法とは言えません(※5⁠⁠。

後者の方法は,LD_PRELOAD_32環境変数(※6)に共有ライブラリを設定することによって,プロセス起動時点で指定共有ライブラリのマッピングが実施される,という性質を利用するもので,非常に簡単に実現できますから,ここではこちらの方法を説明したいと思います。

説明とは言うものの,採取対象としたい共有ライブラリをLD_PRELOAD_32環境変数に指定した状態でdtraceコマンドを起動する,という簡単な手順です。

図10 libzfs.soの関数フロー採取~その2

$ export LD_PRELOAD_32=/lib/libzfs.so.1
$ dtrace -F \
        -s watch_flow_whole.d \
        -c '/usr/sbin/zpool status' \
        libzfs.so
dtrace: script 'watch_flow_whole.d' matched 828 probes
CPU FUNCTION
  0  -> zfs_iscsi_init
  0  <- zfs_iscsi_init
  0  -> _zfs_init_libshare
  0  <- _zfs_init_libshare
  0  -> libzfs_init
  0    -> zfs_prop_init
  0      -> register_index
  0        -> register_impl
  0          -> zprop_get_proptable
....

今度は期待通りに共有ライブラリlibzfs.soの関数フローを採取することができました。

採取用スクリプトのプローブ記述を複数列挙すれば,libzfs.so 中の関数の呼び出し元となるzpool コマンド中の関数フローと併せて採取することもできます。

リスト9 複数モジュールからの採取

pid$target:zpool::entry,
pid$target:zpool::return,
pid$target:libzfs.so::entry,
pid$target:libzfs.so::return
{
}

ちなみに,Solaris環境においてdtrace実行にpfexecを併用している場合,ユーザ権限でLD_PRELOAD_32を設定した状況でpfexecを実行すると,セキュリティ的な観点からLD_PRELOAD_32設定が無視されます。

図11 LD_PRELOAD_32の権限問題

$ export LD_PRELOAD_32=/lib/libzfs.so.1
$ pfexec dtrace -F \
                -s watch_flow_whole.d \
                -c '/usr/sbin/zpool status' \
                libzfs.so
ld.so.1: pfexec: warning: /lib/libzfs.so.1: open failed: \
  illegal insecure pathname
....

LD_PRELOAD_32の設定は,事前にroot権限への昇格を行ってから実施するようにしてください。

※5)
リンク時点で参照先が確定する共有ライブラリではなく,自前でdlopen()する共有ライブラリを対象とするようなケースでは,こちらの方法でないと対処できません。
※6)
採取対象が 64 ビットアプリケーションの場合は,LD_PRELOAD_64 を設定してください。

次回予告

次回は,関数やモジュールの指定以外の手法で情報採取対象を絞り込む方法など,より踏み込んだDTraceの使用方法について説明します。

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。