トップ回答者
IRP_MJ_CLOSEハンドラでの情報取得について

質問
-
お世話になります。
flgMgr.sysを使用した
フィルタードライバを作成しています。自作のフィルタドライバを
ずっと起動させていると、
落ちる現象がありまして、
Windbgでダンプを参照すると、
どうやら
FltIsDirectory
で落ちているようです以下の
// ★★★
のところです。この関数は
IRP_MJ_CLOSE
のハンドラになっているのですが
渡す引数は
Data->Iopb->TargetFileObject
ではだめなのですかね?
(ダメだとしてもすぐ落ちないのは気になる。。)
fltInstanceは、
FltGetVolumeFromInstance関数が
成功のようなので問題ないと思うのですが。以上、よろしくお願いします。
/**************************************************************************** * ファイルクローズのハンドラ *--------------------------------------------------------------------------- * [RET] ハンドラの結果 */ FLT_PREOP_CALLBACK_STATUS SpyPreCloseOperationCallback ( __inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext ) { FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; PFLT_VOLUME fltVolume; UNICODE_STRING fltVolumeName; WCHAR fltVolumeTemp[TEMP_MAX_PATH] = {0}; UNICODE_STRING fileName; WCHAR fileNameTemp[TEMP_MAX_PATH] = {0}; LONG fltVolumeSize = 0; PFLT_INSTANCE fltInstance; NTSTATUS status; PFILE_OBJECT pFileObject; BOOLEAN isDirectory; fltInstance = Data->Iopb->TargetInstance; // インスタンスからボリューム情報取得 status = FltGetVolumeFromInstance(fltInstance, &fltVolume); if (status != S_OK) { DBG_PRINT("Error(%s,%d):Failed FltGetVolumeFromInstance, 0x%08X\n", __FILE__, __LINE__, status); } // UNICODE_STRING初期化 RtlInitEmptyUnicodeString(&fltVolumeName, fltVolumeTemp, TEMP_MAX_PATH); RtlInitEmptyUnicodeString(&fileName, fileNameTemp, TEMP_MAX_PATH); // ボリューム名取得 status = FltGetVolumeName(fltVolume, &fltVolumeName, NULL); if (status != S_OK) { DBG_PRINT("Error(%s,%d):Failed FltGetVolumeName, 0x%08X\n", __FILE__, __LINE__, status); } pFileObject = Data->Iopb->TargetFileObject; isDirectory = FALSE; // ★★★ ここでどうやら落ちる。。 status = FltIsDirectory(pFileObject, fltInstance, &isDirectory); if (status != S_OK && status != STATUS_NOT_SUPPORTED ) { // エラーの場合は無視 DBG_PRINT("Error(%s,%d):Failed FltIsDirectory, 0x%08X\n", __FILE__, __LINE__, status); return returnStatus; } if ( isDirectory ) { // ディレクトリの場合は無視 return returnStatus; } // ファイル名作成 status = RtlUnicodeStringCopy(&fileName, &fltVolumeName); if (status != S_OK) { DBG_PRINT("Error(%s,%d):Failed RtlUnicodeStringCopy, 0x%08X\n", __FILE__, __LINE__, status); } status = RtlUnicodeStringCat(&fileName, &pFileObject->FileName); if (status != S_OK) { DBG_PRINT("Error(%s,%d):Failed RtlUnicodeStringCat, 0x%08X\n", __FILE__, __LINE__, status); } DBG_PRINT("CheckClose (%s,%d):%08p %ws\n", __FILE__, __LINE__, FltObjects->FileObject, fileNameTemp); return returnStatus; }
- 編集済み phoenix343 2010年1月9日 1:59 ソースコードのフォントを等幅に変更
- 移動 Mike Wang (MSCS) 2012年10月2日 12:24 (移動元:Windows デバイスドライバー開発)
回答
-
phoenix343 さん、再びこんにちは。
ファイル システム フィルタ ドライバが IRP_MJ_CLOSE を受け取ったタイミングで、
削除対象となっている File Object にアクセスするのは、やってはいけない実装だと思います。
なぜなら、IRP_MJ_CLOSE を受け取った時点において、対象となる File Object の参照カウンタと
オープン ハンドル カウンタが既に 0 となっている状態、つまり破棄される直前だからです。これらのカウンタは、IRP_MJ_CREATE によってインクリメントされていき、IRP_MJ_CLEANUP が呼び出されるごとに
デクリメントされて、最終的にこれらカウンタの値が0になった時に、オブジェクト マネージャや I/O マネージャを経由して
IRP_MJ_CLOSE が発行され、その File Object が破棄される。。。という流れだったと思います。(ただしファイル システム フィルタ ドライバ側から見た場合、常に IRP_MJ_CREATE と IRP_MJ_CLEANUP の数が
一致するわけではありません。 この点に関しては、WDK ドキュメント "IRP_MJ_CLOSE" トピックに記載されています。)つまり、IRP_MJ_CLOSE がファイル システム フィルタ ドライバに発行された時点で、I/O マネージャなどの
NT カーネルはその File Object の削除を前提に処理を行っている訳で、今回の場合まさに削除しようとしている
File Object に対して、削除処理中にアクセスしようとしている訳ですから、BSOD になっても不思議ではないと思います。「なぜすぐに落ちないのか?」に関しては、I/O マネージャあるいはファイル システム ドライバ側がたまたま運よく
思いとどまっているだけが、たまたま別のスレッド コンテキストに切り替わっただけで、それ自体に意味はないと思います。
(少なくとも、正常に処理されている訳では無いと思います。)
どうしてもその理由を明確にしたいのであれば、WinDbg 等のデバッガで丹念にトレースしていけばわかるのでは?ちなみに、参照カウンタやオープン ハンドル カウンタは FILE_OBJECT 構造体 FsContext メンバの先に
FCB (File Control Block) 構造体として保持されているようですが、FsContext や FCB のデータ フォーマット
は非公開のようです。
(FCB は WinDbg を使って、ちゃんとシンボルを合わせれば、ある程度の情報は見えるようです。)- 回答としてマーク phoenix343 2010年1月9日 0:53
-
>のFltObjects->FileObject
>でも同様ですよね?
はい。
デバッガで確認すればすぐにわかると思いますが、FLT_RELATED_OBJECTS 構造体 FileObject メンバと
FLT_IO_PARAMETER_BLOCK 構造体 TargetFileObject メンバは、同じアドレスになっているはずだと思います。
ですので、同じ結果になると思います。>とすると、IRP_MJ_CLOSEがきたときはファイル名を取得することは不可能なんでしょうか?
そうだと思います。
(そもそも IRP_MJ_CLOSE で削除対象の File Object にアクセスを行う実装を行ったことはありませんが。。。)>渡されたFileObjectが使えないとなると、、、
>OpenしたときのFileObjectのアドレス値でも覚えておいて、
>IRP_MJ_CLOSEがきたときにFileObjectのアドレスで検索…かな?
一般的な実装かどうかはわかりませんが、私の場合、Create IRP が来たタイミングで FILE_OBJECT 構造体 FileName メンバに
セットされている名前を保持し、ファイル システムでの Create IRP が成功した場合に、FsContext にセットされたアドレスを
ベースに名前やその他の属性を管理するようにしています。
FsContext で管理している理由は、FILE_OBJECT だと、同一ファイルに対して複数の FILE_OBJECT が生成されてしまうからです。
追伸
下記 Filter Manager Service Function を組み合わせれば、もっとスマートに解決できるかもしれません。FltAllocateContext
http://msdn.microsoft.com/en-us/library/aa488574.aspxFltSetStreamContext
http://msdn.microsoft.com/en-us/library/aa488717.aspxFltGetStreamContext
http://msdn.microsoft.com/en-us/library/aa488667.aspxFltDeleteStreamContext
http://msdn.microsoft.com/en-us/library/aa488562.aspxWDK サンプルの ctx ドライバでこれら関数を使用しているので、参考になると思います。
- 回答としてマーク phoenix343 2010年1月8日 23:59
すべての返信
-
phoenix343 さん、再びこんにちは。
ファイル システム フィルタ ドライバが IRP_MJ_CLOSE を受け取ったタイミングで、
削除対象となっている File Object にアクセスするのは、やってはいけない実装だと思います。
なぜなら、IRP_MJ_CLOSE を受け取った時点において、対象となる File Object の参照カウンタと
オープン ハンドル カウンタが既に 0 となっている状態、つまり破棄される直前だからです。これらのカウンタは、IRP_MJ_CREATE によってインクリメントされていき、IRP_MJ_CLEANUP が呼び出されるごとに
デクリメントされて、最終的にこれらカウンタの値が0になった時に、オブジェクト マネージャや I/O マネージャを経由して
IRP_MJ_CLOSE が発行され、その File Object が破棄される。。。という流れだったと思います。(ただしファイル システム フィルタ ドライバ側から見た場合、常に IRP_MJ_CREATE と IRP_MJ_CLEANUP の数が
一致するわけではありません。 この点に関しては、WDK ドキュメント "IRP_MJ_CLOSE" トピックに記載されています。)つまり、IRP_MJ_CLOSE がファイル システム フィルタ ドライバに発行された時点で、I/O マネージャなどの
NT カーネルはその File Object の削除を前提に処理を行っている訳で、今回の場合まさに削除しようとしている
File Object に対して、削除処理中にアクセスしようとしている訳ですから、BSOD になっても不思議ではないと思います。「なぜすぐに落ちないのか?」に関しては、I/O マネージャあるいはファイル システム ドライバ側がたまたま運よく
思いとどまっているだけが、たまたま別のスレッド コンテキストに切り替わっただけで、それ自体に意味はないと思います。
(少なくとも、正常に処理されている訳では無いと思います。)
どうしてもその理由を明確にしたいのであれば、WinDbg 等のデバッガで丹念にトレースしていけばわかるのでは?ちなみに、参照カウンタやオープン ハンドル カウンタは FILE_OBJECT 構造体 FsContext メンバの先に
FCB (File Control Block) 構造体として保持されているようですが、FsContext や FCB のデータ フォーマット
は非公開のようです。
(FCB は WinDbg を使って、ちゃんとシンボルを合わせれば、ある程度の情報は見えるようです。)- 回答としてマーク phoenix343 2010年1月9日 0:53
-
かぴるんるんさん、再び返答ありがとうございます。
ファイル システム フィルタ ドライバが IRP_MJ_CLOSE を受け取ったタイミングで、
削除対象となっている File Object にアクセスするのは、やってはいけない実装だと思います。
2番目に渡される
__in PCFLT_RELATED_OBJECTS FltObjects
のFltObjects->FileObject
でも同様ですよね?
とすると、IRP_MJ_CLOSEがきたときはファイル名を取得することは不可能なんでしょうか?
渡されたFileObjectが使えないとなると、、、
OpenしたときのFileObjectのアドレス値でも覚えておいて、
IRP_MJ_CLOSEがきたときにFileObjectのアドレスで検索…かな?
何か知恵があれば教えてください、、 -
http://msdn.microsoft.com/en-us/library/aa488639.aspx を見ると
NTSTATUS
FltIsDirectory(
__in PFILE_OBJECT FileObject,
__in PFLT_INSTANCE Instance,
__out PBOOLEAN IsDirectory
);
Parameters
- Instance
- Opaque instance pointer for the instance associated with this file object.
と記載されているように見えます。
Data->Iopb->TargetInstance を渡してみたらどうなりますか??? -
>のFltObjects->FileObject
>でも同様ですよね?
はい。
デバッガで確認すればすぐにわかると思いますが、FLT_RELATED_OBJECTS 構造体 FileObject メンバと
FLT_IO_PARAMETER_BLOCK 構造体 TargetFileObject メンバは、同じアドレスになっているはずだと思います。
ですので、同じ結果になると思います。>とすると、IRP_MJ_CLOSEがきたときはファイル名を取得することは不可能なんでしょうか?
そうだと思います。
(そもそも IRP_MJ_CLOSE で削除対象の File Object にアクセスを行う実装を行ったことはありませんが。。。)>渡されたFileObjectが使えないとなると、、、
>OpenしたときのFileObjectのアドレス値でも覚えておいて、
>IRP_MJ_CLOSEがきたときにFileObjectのアドレスで検索…かな?
一般的な実装かどうかはわかりませんが、私の場合、Create IRP が来たタイミングで FILE_OBJECT 構造体 FileName メンバに
セットされている名前を保持し、ファイル システムでの Create IRP が成功した場合に、FsContext にセットされたアドレスを
ベースに名前やその他の属性を管理するようにしています。
FsContext で管理している理由は、FILE_OBJECT だと、同一ファイルに対して複数の FILE_OBJECT が生成されてしまうからです。
追伸
下記 Filter Manager Service Function を組み合わせれば、もっとスマートに解決できるかもしれません。FltAllocateContext
http://msdn.microsoft.com/en-us/library/aa488574.aspxFltSetStreamContext
http://msdn.microsoft.com/en-us/library/aa488717.aspxFltGetStreamContext
http://msdn.microsoft.com/en-us/library/aa488667.aspxFltDeleteStreamContext
http://msdn.microsoft.com/en-us/library/aa488562.aspxWDK サンプルの ctx ドライバでこれら関数を使用しているので、参考になると思います。
- 回答としてマーク phoenix343 2010年1月8日 23:59
-
かぴるんるんさん、
返信ありがとうございます。
読み返して気づきましたが
IRP_MJ_CREATEがくる分、
IRP_MJ_CLOSEでなく
IRP_MJ_CLEANUPがくるんですね。
うーむ、、
悩ましい。
IRP_MJ_CLEANUPも見るようにしますかね。
IRP_MJ_CLOSEは無視しようかな。実装めんどうだし 笑(ただしファイル システム フィルタ ドライバ側から見た場合、常に IRP_MJ_CREATE と IRP_MJ_CLEANUP の数が
一致するわけではありません。 この点に関しては、WDK ドキュメント "IRP_MJ_CLOSE" トピックに記載されています。)
とにかくクローズのログが出ればいいので、、 -
とりあえず、気になった点をいくつか。。。
>IRP_MJ_CLEANUPも見るようにしますかね。
少なくとも、今の SpyPreCloseOperationCallback ルーチンの実装には
いくつかの問題が潜在ししているように思います。
ですので、今のままの実装で IRP_MJ_CLEANUP に対応させても、
目的の機能の実現は難しいと思います。まず第1の問題としては、FILE_OBJECT 構造体 FileName メンバを
参照するタイミングです。
このメンバにファイル名がセットされていることが保証されるタイミングは、
IRP_MJ_CREATE が渡されてきたとき、つまりミニフィルタ ドライバの場合、
PreCreate Operation が呼び出されたときだけです。
PostCreate Operation を含むそれ以外のタイミングでは、ファイル システム
ドライバによって別の名前に書き換えたれていたり、あるいは NULL ポインタ
に置き換わっていたりする場合があるので、そのような場合だと、正しいファイル名
は表示できません。
ただ、ミニフィルタ ドライバの場合、
FltGetFileNameInformation()
というサービス関数が用意されているので、FILE_OBJECT 構造体 FileName メンバ
の直接参照ではなく、この関数を使用してファイル名を取得する実装に変更すれば、
IRP_MJ_CLEANUP のタイミングでも大丈夫かもしれません。
(私は試したことありませんけど。)もう1つの問題は、FltGetVolumeFromInstance() コールに対応する
FltObjectDereference() がコールが無いことです。
下記サイトでの説明にもありますが、FltGetVolumeFromInstance() コールで
取得した FLT_VOLUME 構造体データがいらなくなったら、FltObjectDereference()
をコールしてあげないといけないと思います。FltGetVolumeFromInstance
http://msdn.microsoft.com/en-us/library/aa488606.aspx
とりあえず私の方は、ctxドライバのような Stream Context ベースで
管理する実装の方がはるかにスマートであることに気が付いたので、
こっちの実装に変更することにしました。 -
かびるんるんさん、再び返信ありがとうございます。。
ただ、ミニフィルタ ドライバの場合、
実装を検討してみます。 IRP_MJ_CLEANUPにすることで落ちなくはなりましたが、その方がよいかな。。
FltGetFileNameInformation()
というサービス関数が用意されているので、FILE_OBJECT 構造体 FileName メンバ
の直接参照ではなく、この関数を使用してファイル名を取得する実装に変更すれば、
IRP_MJ_CLEANUP のタイミングでも大丈夫かもしれません。
(私は試したことありませんけど。)
もう1つの問題は、FltGetVolumeFromInstance() コールに対応する
指摘ありがとうございます。
FltObjectDereference() がコールが無いことです。
ここはすでに修正済みです。(一言書いとけば良かったですね…m(__;)m -
かびるんるんさん
再び気にしてくださり有難うございます
具体的に、どのようにおかしくなるのでしょうか?
落ちたりはしないんですけどね。
ちゃんとは読んでいないのですが、MiniSpy のコードを眺めた限りでは、Cleanup IRP でも
FltGetFileNameInformation() は機能しそうに見えたのですが。。。
コンパイルや沢山のファイルにアクセスしたりすると、目に見えてパフォーマンスが落ちるんです。
で、しまいにはエクスプローラを開こうとしても応答なしになるんで…
FltGetFileNameInformationの処理を外すと問題ないので、
この関数の中で、何か処理しちゃってそうですね。 -
MiniSpy を使ってちょっと試してみましたけど、私の環境ではちゃんと表示されて、サクサク動きましたよ。
参考になるかわかりませんが、調査用に追加したコードを以下に示しておきます。
filter\minispy.c
------------------------------------------------------------
#define PRIVATE_DEBUG_ENABLE TRUE
BOOLEAN g_OutputPrivateLogs = TRUE;
....
....
VOID
SpyOutputFileNameInformation
(
PFLT_FILE_NAME_INFORMATION fltFileNameInformation
)
{
if ( fltFileNameInformation )
{
if ( g_OutputPrivateLogs )
{
DbgPrint( ("<FLT_FILE_NAME_INFORMATION Structure> \n"
"Size = %d \n"
"NamesParsed = 0x%08X \n"
"Format = 0x%08X \n"
"Name = %wZ \n"
"Volume = %wZ \n"
"Share = %wZ \n"
"Extension = %wZ \n"
"Stream = %wZ \n"
"FinalComponent = %wZ \n"
"ParentDir = %wZ \n\n"),
fltFileNameInformation->Size,
fltFileNameInformation->NamesParsed,
fltFileNameInformation->Format,
&(fltFileNameInformation->Name),
&(fltFileNameInformation->Volume),
&(fltFileNameInformation->Share),
&(fltFileNameInformation->Extension),
&(fltFileNameInformation->Stream),
&(fltFileNameInformation->FinalComponent),
&(fltFileNameInformation->ParentDir) );
}
}
}
....
....
....
....FLT_PREOP_CALLBACK_STATUS
SpyPreOperationCallback (
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID *CompletionContext
)
{
FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; //assume we are NOT going to call our completion routine
PRECORD_LIST recordList;
PFLT_FILE_NAME_INFORMATION nameInfo = NULL;
UNICODE_STRING defaultName;
PUNICODE_STRING nameToUse;
NTSTATUS status;
#if MINISPY_NOT_W2K
WCHAR name[MAX_NAME_SPACE/sizeof(WCHAR)];
#endif#if PRIVATE_DEBUG_ENABLE
PFLT_IO_PARAMETER_BLOCK fltIoParameterBlock = Data->Iopb;
UCHAR MajorFunction = fltIoParameterBlock->MajorFunction;
UCHAR MinorFunction = fltIoParameterBlock->MinorFunction;
#endif PRIVATE_DEBUG_ENABLE....
....
status = FltGetFileNameInformation( Data,
FLT_FILE_NAME_NORMALIZED |
MiniSpyData.NameQueryMethod,
&nameInfo );#if PRIVATE_DEBUG_ENABLE
if( MajorFunction == IRP_MJ_CLEANUP )
{
if ( NT_SUCCESS( status ) )
{
SpyOutputFileNameInformation( nameInfo );
}
}
#endif PRIVATE_DEBUG_ENABLE
....
....
lstatus = FltGetFileNameInformation( Data,
FLT_FILE_NAME_OPENED |
FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP,
&lnameInfo );
if (NT_SUCCESS(lstatus)) {#if PRIVATE_DEBUG_ENABLE
if( MajorFunction == IRP_MJ_CLEANUP )
{
SpyOutputFileNameInformation( nameInfo );
}
#endif PRIVATE_DEBUG_ENABLE#pragma prefast(suppress:__WARNING_BANNED_API_USAGE, "reviewed and safe usage")
(VOID)_snwprintf( name,
sizeof(name)/sizeof(WCHAR),
L"<%08x> %wZ",
status,
&lnameInfo->Name );FltReleaseFileNameInformation( lnameInfo );
} else {
....
........
....retryStatus = FltGetFileNameInformation( Data,
FLT_FILE_NAME_NORMALIZED |
MiniSpyData.NameQueryMethod,
&nameInfo );if (!NT_SUCCESS( retryStatus )) {
//
// We always release nameInfo, so ignore return value.
//NOTHING;
}
#if PRIVATE_DEBUG_ENABLE
else
{
if( MajorFunction == IRP_MJ_CLEANUP )
{
SpyOutputFileNameInformation( nameInfo );
}
}
#endif PRIVATE_DEBUG_ENABLE....
....
....
....}
------------------------------------------------------------