process_history更新 v1.10

すべてのプロセスの開始時間と終了時間を記録します。2000 SP4/XP SP2/Vista SP1にて動作します。作業時間の把握とかに使えるかもしれませんね。よければどうぞー(process_history.cab)。


アーカイブはドライバとその利用サンプルEXEで構成されています。ドライバをロードする機能は提供されていないので ldsys あたりを利用してください。

主な更新

  • システム起動からの「経過時間」ではなく「割り込み回数」を表示していた問題を修正
    • プロセスの生成・終了「日時」を表示するように変更
  • ドライバロード時点で存在しているすべてのプロセスを対象にするように変更
  • 動作環境外でのロードを禁止(OSバージョンに依存する定数を使用しているため)
  • プロセス起動時間をEPROCESS構造体から直接取得するように変更
  • Systemのプロセス起動時間を「システムブートの時刻」とするように変更
  • リソースリークその他青画面になる可能性のある箇所を修正
  • 排他制御の導入による青画面になる可能性のある箇所を修正

更新に伴う技術メモ

カーネルモードのミューテックス

ExInitializeFastMutexで初期化し、ExAcquireFastMutexシリアライズ(待機)、ExReleaseFastMutexミューテックスを開放する。カーネルモードでは半ば強制的に並列プログラミング+共有リソース操作になるので、これは勉強になった。

システム起動後の経過時間の算出

システム起動後の割り込み回数を取得し、割り込みが発生する時間間隔で乗算する。コードにすると以下。

LARGE_INTEGER ElapsedTime;
KeQueryTickCount(&ElapsedTime);

ElapsedTime.QuadPart = ElapsedTime.QuadPart * KeQueryTimeIncrement();

KeQueryTickCount の戻り値は特定の操作でリセットされるらしく、時折、smss.exeなどの開始時間がシステム起動時間より過去になることがある。しかしこれはこれで正しいらしく、Process Explorer などでも同様の現象が確認できた。


余談だが LARGE_INTEGER とカーネルモードで変換可能な TIME_FIELDS 構造体があるが、これは SYSTEMTIME 構造体とは似て非なるものでユーザーモードで受け取っても処理に困るだけだったりする。

フルパスを取得しない理由

当初バージョンアップ時に取得する予定であったが、実現困難により中止した。

プロセスのフルパスは PEB 構造体経由で取得することができ、フルパスを得るまでの構造体定義はドキュメント化されている(PEB::RTL_USER_PROCESS_PARAMETERS::ImagePathName)。これをプロセス生成時の通知ルーチンの中で取得すれば良いものと考えていたが、実際に試してみると、通知ルーチンが呼び出される段階ではPEB構造体が初期化されていないことが判った。

おそらくユーザーモードの情報が初期化されていない段階なのだろうが、なんにせよこれ以外のタイミングでスマートに取得する方法が思いつかなかった。過去に扱ったAPCの利用も考えたが、安全に実装する自信が持てなかったため諦めた。

また2000に限定すれば EPROCESS にある pImageFileName メンバを参照すればおk。でもXP/Vistaにはないっぽい。。。

kd> dt nt!_eprocess
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
...
   +0x284 pImageFileName   : Ptr32 _UNICODE_STRING
05/11追記

XP/Vistaにも近いものはあった。でもいいや、もう。

OS フィールド名 オフセット 値の例
2000 SP4 pImageFileName _EPROCESS+0x284 \WINNT\regedit.exe
XP SP2 SeAuditProcessCreationInfo::ImageFileName::Name _EPROCESS+0x1f4 \Device\HarddiskVolume1\WINDOWS\system32\regedit.exe
Vista SP1 SeAuditProcessCreationInfo::ImageFileName::Name _EPROCESS+0x1cc \Device\HarddiskVolume1\WINDOWS\system32\regedit.exe

EPROCESS構造体のExitTimeを取得しない理由

簡単には実現できなかったためだ。

プロセス通知ルーチンにプロセスの破棄が通知された時点での PsLookupProcessByProcessId 呼び出しは失敗するらしい。そのため、せっかく受け取ったPIDから EPROCESS を得ることができない。カレントの EPROCESS から ActiveProcessLinks を辿れば得られないこともないが、KeQuerySystemTime を使えば現在時刻は取得できるため、リスクに比して得られるものがないと判断した*1

ちなみにプロセス生成時の通知でも、PIDを利用した操作のいくつかが失敗する。時折 STATUS_ACCESS_VIOLATION を返す(例外発生ではない)こともあるようなので、通知ルーチン内でのプロセス操作APIは疑ってかかるように。

*1:カーネル構造体のリンクリストを自前で辿るのは危険だ。私は「読むだけならば」と思っていたが、読んでいる最中に別プロセッサが構造を操作する可能性がある。って Greg が言ってた。