2008年9月6日

Process Monitorの出力をいじる

WindowsにはProcess Monitorというツールがある。多分アンカーの指している場所がダウンロードサイトの中で最も簡単に手に入れられるページだと思う(日本語で説明が書いてあるから)。

対応OSは 2000, XP, 2003, そして Vista だそうだ。

Process Monitor (procmon.exe)は、NT kernelへのシステムコール…というかリクエストキューに積まれたリクエストを記録し続ける。記録されるのは全てのプロセスのリクエストだ。unixのstraceなどと違って「このプロセスの」とか「あのプロセスだけ」という制約は無い。そのおかげでシステム全体の状態が判るが、これはセキュリティ上は結構危険な状態でもある。

procmon.exeは記録したシステムコールを表示するときにも使える。いくつかのフィルター条件を設定して望みのものだけを選び出すことが出来る。ある特定のプロセスが発行したもの、特定のスレッドが発行したもの、あるファイル(あるいはレジストリパス)に対するIO、あるシステムコールだけを選ぶ…などができる。実はシステムコールを発行した(キューに積んだ)時刻だけではなく、これが完了するまでにかかった時間も記録している。
「システムコールが終わるまでに0.1sec以上かかった」
とか
「システムコールに対する応答が無かった」
などという条件も設定できるのだ(NTのシステムコールは「キューにリクエストを積んで」「応答を待つ」形式なので、本質的に全て非同期コールだ)。

障害解析やパフォーマンス解析においてこれほど強力なツールはまず、ない。

が、世の中どんなに頑張っても
「それは俺の設定したい条件じゃない」
と言うことはあるわけで。そうなるとPerlのようなスクリプト言語の出番になる。




Perlに読めるようにするためには、procmon.exeが記録した内容を何らかのテキストの形で出力してもらわなくてはいけない。それ自体は簡単だ。

% procmon.exe /OpenLog 'filename.PML' /SaveAs 'filename.CSV'


PMLは procmonが出力するバイナリフォーマットのログ、filename.csvは「csvフォーマットでファイルを出力しろ」と言う意味だ。ちなみにこの場合、csvファイルはutf-8で出してくれる。なんとUCS2やUTF-16じゃないのだ。ありがたい。

しかし、この出力には2つ問題がある。

1つ目はファイルの先頭にBOM … Byte Order Mark …がついている点だ。確かにこれがあれば確実にUTF-8だと判るのだが、私の知る限り perl が標準入力から読み込むファイルの操作に関して、BOMを適切に処理できる、と言う話は聞かない。だから、BOM…この場合はファイル先頭の3byteなのだが…を削らなくてはいけない。

2つ目はCSVそのものにある。いや、もちろん、CSVだって悪いわけじゃない。もし1つのValueの中にカンマで分割された文字列が入っているのでなければ

perfmon.exe の記録のいくつかのフィールドが、それ自体カンマで区切った複数フィールドから成り立っているのだ。例えば Detail というフィールドなんかがそうだ。たとえば:
"User Time: 0.0781250, Kernel Time: 0.0312500, Private Bytes: 1,806,336, Working Set: 5,439,488"

とか(これは Process Profiling をやっているときの記録だな)、
"Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, Impersonating: S-x-x-xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxx, OpenResult: Opened"

(これはファイルに対する IRP_MJ_CREATE のDetailだな)、このように1つのエントリ自体が複数のカンマで区切られたフィールドになっている。判ると思うが、各エレメントは " で囲まれている。

すると、単純にカンマをセパレータにして split をするとエライ目に合うわけだ。そこで、CSVをTSV… Tab Separated Values …形式にする。" で囲まれた領域に TAB は含まれていないので split をしかけるときにセパレータを /\t/ と定義すれば、あっさりと正しく split できるようになる。ファイルをこの形式にしておく事で、この後何度も統計処理や絞込みのために行う splitting rule を簡素化できるわけだ。

TSVにするフィルター自体はすごく簡単に作れる。これがコードだ:
tail --bytes=+4 input.CSV |\
sed -r -e 's/\r$//g' \
-e 's/\",\"/\"\t\"/g'\
> output.TSV


最初の tail --bytes=+4 は input.CSVの先頭3バイトを取り外す。これでBOMが無くなる。

GNU sed に与えた -r オプションは「拡張正規表現」を使えるようにする、というものだ。実際の正規表現はその次の2つの -e で始まるものになる。

's/\r$//g' は行末の 0x0d を取り外しているだけだ。ようするに改行コードを unix 形式になおしている。

's/\",\"/\"\t\"/g'は 『","』 となっている場合の , を TAB に入れ替える。ここには、procmon は「"," というフィールドデータを吐かない」という前提を使っている。少なくとも今の所、見たことは無い。多分大丈夫だろう。




ちなみに。TSVに直した後も、各エレメントは " で囲まれたままだ。で、 " で囲まれたエレメントを処理するときには「まだ」面倒な問題が残っている。

" で囲まれた中で " を表現するには『""』のように"を2回連続しなくてはいけない。

で、「そのプログラムはどのように起動したのか」を説明する「Command Line」フィールドに絶対パスが例の『Program Files』を含む場合、このようになってしまうのだ:


"""C:\Program Files\IBM\Director\websrv\dirwbs.exe"""


一番外側の " は「フィールド」を囲む " だが、その内側の "" は実際には1つの " を " で囲まれたフィールド内で表現するためのものだ。

この点だけは注意した方がよい。