IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

dtrace を使ったシステムコールのトレース

dtrace とは

Mac OS X Leopard から導入された、プログラムを書き換えることなくプログラムの情報を監視(トレース)できる便利ツールです。

使い方

たとえば、 vim という名前のプロセスからシステムコール stat, open, stat64 されたときに第一引数(ファイル名)を監視するには以下のようにします。

$ sudo dtrace -n "syscall::open:entry, syscall::stat:entry, syscall::stat64:entry / execname == \"vim\" / { trace(copyinstr(arg0)) } "

説明

まず、 -n というのは「probe」の「name」を指定して dtrace する!という意味です。

$ sudo dtrace [-n [[[ provider: ] module: ] func: ] name [[ predicate ] action ]]
probe とは

probe というのは、 監視ポイントのことです。
以下のコマンドで監視ポイントの一覧を見ることができます。

$ sudo dtrace -l

たくさんあるので、 grep するといいです。

$ sudo dtrace -l | grep syscall

とにかくいろんな情報を監視できるのが分かりますね!

probe の指定方法

probe は固有の数字か、「provider:module:func:name」というコロンで区切られた文字列で指定します。
文字列で指定する場合は、さっきのように -n を使います。

$ sudo dtrace -n syscall::open:entry

syscall には module 名がないのでコロンコロンになってます。
また、
数字で指定する場合は、以下のように -i を使います。

$ sudo dtrace -i 18270

この数字は sudo dtrace -l で分かります。

action とは

action とは監視した情報をどのようにトレースするかという情報です。中括弧で囲って書きます。
たとえば、 プロセス名とプロセス ID を trace したい場合は、以下のようにします。

$ sudo dtrace -n "syscall::open:entry { trace(execname); trace(pid) }"

この中括弧の中には、 d 言語(別の D 言語と誤解しないように)というスクリプトステートメント(文)を書きます。
trace 以外にも様々な関数があり、 execname や pid 以外にも様々な変数があります。

predicate とは

predicate とは監視した情報をフィルタリングする場合に使います。スラッシュで囲って書きます。
ここには、 d 言語の式を書きます。
たとえば、以下のようにすれば vim という名前のプロセスか emacs という名前のプロセスだけをトレースすることができます。

$ sudo dtrace -n "syscall::open:entry / execname == \"emacs\" || pid == \"vim\" / { trace(execname); trace(pid) }"
d 言語の詳細なドキュメントは

ここにあります。
http://wikis.sun.com/display/DTrace/Documentation

d 言語を別のファイルにする

d 言語を別のファイルにすることもできます

#! /usr/sbin/dtrace -s

syscall::open:entry
/ execname == "emacs" || pid == "vim" /
{
  trace(execname);
  trace(pid);
}

profile:::tick-100sec
{
  trace("timeout!\n")
  exit(0)
}

パーミッションを設定すれば、そのまま実行できます。

実際に実行してみる

こんな感じです。

$ sudo dtrace -n "syscall::open:entry, syscall::stat:entry, syscall::stat64:entry / execname == \"vim\" / { trace(copyinr(arg0)) } " > dtrace.txt &
[1] 70629
dtrace: description 'syscall::open:entry, syscall::stat:entry, syscall::stat64:entry ' matched 3 probes

$ vim hoge

$ fg
sudo dtrace -n "syscall::open:entry, syscall::stat:entry, syscall::stat64:entry / execname == \"vim\" / { trace(copyinstr(arg0)) } " > dtrace.txt
^C

$ cat dtrace.txt 
CPU     ID                    FUNCTION:NAME
  0  17970                       stat:entry   /usr/lib/libncurses.5.4.dylib    
  0  17970                       stat:entry   /usr/lib/libiconv.2.dylib        
  0  17970                       stat:entry   /usr/lib/libgcc_s.1.dylib        
  0  17970                       stat:entry   /usr/lib/libSystem.B.dylib       
  0  17970                       stat:entry   /usr/lib/system/libmathCommon.A.dylib
  0  17604                       open:entry   /dev/dtracehelper                
  0  18270                     stat64:entry   /Users/amachang/Downloads        
  0  18270                     stat64:entry   /Users/amachang                  
  0  17970                       stat:entry   /usr/share/vim/vim70             
  0  17970                       stat:entry   /usr/share/vim                   
  0  18270                     stat64:entry   /Users/amachang/Downloads        
  0  17970                       stat:entry   hoge                             
  0  17604                       open:entry   /usr/share/terminfo/78/xterm-color
  0  17604                       open:entry   .                                
  0  18270                     stat64:entry   /usr/share/vim                   
  0  17970                       stat:entry   /usr/share/vim/vimrc             
  0  17970                       stat:entry   /usr/share/vim/vimrc             
  0  17604                       open:entry   .                                
  0  18270                     stat64:entry   /Users/amachang                  
  0  17970                       stat:entry   /Users/amachang/.vimrc           
  0  17970                       stat:entry   /Users/amachang/.vimrc           
:
:

おおお dylib が読み込まれて、設定ファイルが読み込まれていく様が分かりますね!

まとめ

Leopard に元々入ってる RubyPerl などはパッチがあたっていて関数呼び出しなどがトレースできるらしいのですが、自分がやってみたらできませんでした。何故だろう?
とにかく ktrace の変わりに dtrace を使ってみてはいかがでしょうか。