どうもみむらです。
最近とある用があってカーネルモードドライバを書くことが出てきました。
なんだかんだ、Linux 用のドライバも Windows 用のドライバも慣れると楽しいですね。
(ブルースクリーンに行かないように考えながらコードを書くのが良い感じに脳みその体操になってとっても楽しいです。 気を抜くとすぐにブルースクリーンになりますし。)
さてさて。
Windows なカーネルモードドライバを書いている上でなかなかに問題になることの一つに
IRQL の話があると思います。
詳しくはこちらのウェブサイトに詳しくまとめられていますのでそちらにお任せするとして。
http://d241445.hosting-sv.jp/community/report/report11.html
要は各割り込みに優先度を付けて、すぐに行わなければならないやつをすぐに行えるようにする・・と
おおざっぱですがそのような感じです。
また各 IRQL によって実行可能な API が限られており、
もしそれを無視して実行すると
こんな感じで、 “IRQL_NOT_LESS_OR_EQUAL” な BSoD が発生します。
(ちなみにこれは KeBugCheckEx に 0xA と適当(?)な数字を設定して呼び出しました。)
各種コールバックでは DISPATCH_LEVEL でやってくるケースもあり、
PASSIVE_LEVEL でのみ呼び出せる関数をどうやって呼び出すか。
今回の一件は、
フィルタを挟み込んでデータをファイルに書き出すというようなドライバを書いていて、
どこかのタイミングで書き出せればいいと考えて、
ExQueueWorkItem を DelayedWorkQueue で呼び出して、
呼び出し先にファイル書き込みを書く・・という方法で対処しました。
コードとしてはこんな感じ
#include <Ntifs.h>
#include <ntddk.h>
#include <Ntstrsafe.h>
#define STR_LENGTH 0x1000
typedef struct _DATA
{
LARGE_INTEGER time;
wchar_t str[STR_LENGTH];
} DATA, *PDATA;
void _PassiveWrite(PDATA);
/* 外部から呼び出される関数。ここは IRQL <= DISPATCH_LEVEL であれば実行可能 */
void Write(PDATA d)
{
WORK_QUEUE_ITEM workitem;
PDATA data;
/* 非ページプール上に領域を確保する */
data = (PDATA)ExAllocatePoolWithTag(NonPagedPool, sizeof(DATA), 0x0616);
if(data == NULL)
return;
/* データを準備する。 */
ExInitalizeWorkItem(&workitem,_PassiveWrite,data);
memcpy_s(data,sizeof(DATA),d,sizeof(DATA));
/* Queue に登録する */
ExQueueWorkItem(&workitem, DelayedWorkQueue);
}
/*
ExQueueWorkItem によって登録され、実行可能になった時に呼び出される。
ここの IRQL は PASSIVE_LEVEL になる
*/
void _PassiveWrite(PDATA data)
{
HANDLE handle;
NTSTATUS status;
UNICODE_STRING str;
IO_STATUS_BLOCK statusblock = {0};
/* ファイルオープン */
{
UNICODE_STRING path;
OBJECT_ATTRIBUTES attr = {0};
RtlInitUnicodeString(&path, L"\\??\\C:\\Logs\\nonohara.log");
InitializeObjectAttributes(&attr,&path,
OBJ_CASE_INSENSITIVE |
OBJ_KERNEL_HANDLE |
OBJ_FORCE_ACCESS_CHECK,
NULL, NULL);
status = ZwCreateFile(
&handle,FILE_APPEND_DATA,
&attr,&statusblock,NULL,
FILE_ATTRIBUTE_NORMAL,
0,FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,0
);
}
if(!NT_SUCCESS(status))
{
ExFreePoolWithTag(data,0x0616);
return;
}
/* 書き込む文字列を作る */
str.Buffer = ExAllocatePoolWithTag(NonPagedPool,STR_LENGTH*2,0x1133);
str.MaximumLength = STR_LENGTH*2;
str.Length = 0;
RtlUnicodeStringPrintf(&str,L"%I64u,%s\r\n",
data->time,
data->str
);
/* 書き込む */
ZwWriteFile(handle, NULL, NULL, NULL,
&statusblock, str.Buffer, str.Length, NULL, NULL);
/* バッファーをクリアする */
ZwFlushBuffersFile(handle,&statusblock);
ZwClose(handle);
/* メモリを解放する */
ExFreePoolWithTag(str.Buffer, 0x1133);
ExFreePoolWithTag(data, 0x0616);
}
たとえばこれを応用して、
ドライバを開始したタイミングでファイルを開いてハンドルを握っておき
(その間、共有状態で CreateFile をしていないので他からのアクセスがロックされる。)
ドライバが停止するタイミングでバッファをフラッシュしてクローズする・・とか。
(その間バッファリングが効くので、たぶん良い感じになる・・はず)
後は何か、特定の動作があった場合に何かを呼び出すとかそういうのが書けます。
・・・とりあえず、個人的なメモ。
誰かドライバを書いていてこの記事で解決することがあれば・・。