1. kkent030315

    Posted

    kkent030315
Changes in title
+Windowsカーネルモードドライバでワークアイテムを登録する
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,184 @@
+![kmdf2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/392dd8ba-a041-ad6f-2f34-48be39f29721.png)
+
+#はじめに
+
+####前回の記事: [Windowsカーネルモードドライバ(KMDF)の開発環境を構築する](https://qiita.com/kkent030315/items/62485176582fc8f040f0)
+
+前回の記事でKMDF(KernelMode Driver Framework)の開発環境を構築しました。
+今回は、カーネルモードドライバでシステム上で動作するワークアイテムを登録してみます。
+ユーザーモードで出来ない処理や、カーネル領域で定期的に回す必要のある作業項目ルーチンを登録できます。
+
+#概要
+今回のルーチンはシステムワーカースレッドで動作します。
+カーネル(Ring0)で動くので、何でも出来ます。
+
+[「リングプロテクション」](https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%97%E3%83%AD%E3%83%86%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
+![300px-Priv_rings.svg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535270/b721e578-75d9-d9d4-87a6-014288b61d7a.png)
+
+
+行う処理は以下の通りです。
+
+**0.メモリ領域確保**
+**1.新しい作業項目を割り当てて初期化**
+**2.コールバックルーチンを作業項目に関連付けて、システムワーカースレッドによって処理されるように作業項目をキューに配置**
+**3.必要に応じてリソースを解放**
+
+##ネイティブWORKITEM構造体
+
+```c++
+typedef struct _WORK_QUEUE_ITEM {
+ LIST_ENTRY List;
+ PWORKER_THREAD_ROUTINE WorkerRoutine;
+ __volatile PVOID Parameter;
+} WORK_QUEUE_ITEM, *PWORK_QUEUE_ITEM;
+```
+
+#実際にやってみる
+
+##ルーチン
+
+```c++:driver.c
+VOID NTAPI myRoutine()
+{
+ DbgPrint("ROUTINE STARTED!\n");
+
+ //やりたい処理
+
+ DbgPrint("ROUTINE FINISHED!\n");
+}
+```
+
+##メモリ領域確保
+
+```c++
+PVOID ExAllocatePool(
+ __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType,
+ SIZE_T NumberOfBytes
+);
+```
+
+```c++
+PWORK_QUEUE_ITEM WorkItem =
+(PWORK_QUEUE_ITEM)ExAllocatePool(NonPagedPool, sizeof(WORK_QUEUE_ITEM));
+```
+
+##作業項目を割り当てて初期化
+
+```c++
+void ExInitializeWorkItem(
+ PWORK_QUEUE_ITEM Item,
+ PWORKER_THREAD_ROUTINE Routine,
+ PVOID Context
+);
+```
+
+```c++
+ExInitializeWorkItem(WorkItem, (PWORKER_THREAD_ROUTINE)myRoutine, WorkItem);
+```
+
+##作業項目をキューに配置
+
+ワークキュータイプ一覧:
+基本的には、`CustomPriorityWorkQueue`を使用した方が良いです。
+ユーザーモードではなくカーネルモードで動作するので、高い優先度でルーチンを回すと
+他の重要な処理を妨げてしまいます。
+Ring0では最新の注意を払う必要があります。
+
+```c++
+typedef enum _WORK_QUEUE_TYPE {
+ CriticalWorkQueue, //優先レベル13
+ DelayedWorkQueue, //優先レベル12
+ HyperCriticalWorkQueue, //優先レベル15
+ NormalWorkQueue, //優先レベル8
+ BackgroundWorkQueue, //優先レベル7
+ RealTimeWorkQueue, //優先レベル18
+ SuperCriticalWorkQueue, //優先レベル14
+ MaximumWorkQueue, //優先レベルなし
+ CustomPriorityWorkQueue //優先レベル指定
+} WORK_QUEUE_TYPE;
+```
+
+`KeSetPriorityThread`でも優先レベルを変更できます。
+
+```c++
+KPRIORITY KeSetPriorityThread(
+ PKTHREAD Thread,
+ KPRIORITY Priority
+);
+```
+
+---
+
+```c++
+void ExQueueWorkItem(
+ __drv_aliasesMem PWORK_QUEUE_ITEM WorkItem,
+ WORK_QUEUE_TYPE QueueType
+);
+```
+
+```c++
+ExQueueWorkItem(WorkItem, DelayedWorkQueue);
+```
+
+##リソース解放
+
+`IoUninitializeWorkItem`して、`IoFreeWorkItem`します。
+
+```c++
+void IoUninitializeWorkItem(
+ PIO_WORKITEM IoWorkItem
+);
+```
+
+```c++
+void IoFreeWorkItem(
+ PIO_WORKITEM IoWorkItem
+);
+```
+
+```c++
+IoUninitializeWorkItem(WorkItem);
+IoFreeWorkItem(WorkItem);
+```
+
+#フルソースコード
+
+```c++:driver.c
+#include <wdm.h>
+
+DRIVER_INITIALIZE DriverEntry;
+
+VOID NTAPI myRoutine()
+{
+ DbgPrint("ROUTINE STARTED!\n");
+
+ //やりたい処理
+ DbgPrint("SETO KOUJI!\n");
+
+ DbgPrint("ROUTINE FINISHED!\n");
+}
+
+NTSTATUS DriverEntry(PDRIVER_OBJECT dObject, PUNICODE_STRING regPath)
+{
+ UNREFERENCED_PARAMETER(dObject);
+ UNREFERENCED_PARAMETER(regPath);
+
+ NTSTATUS status = STATUS_SUCCESS;
+ DbgPrint("HELLO WORLD!\n");
+
+ //メモリ領域確保
+ PWORK_QUEUE_ITEM WorkItem = (PWORK_QUEUE_ITEM)ExAllocatePool(NonPagedPool, sizeof(WORK_QUEUE_ITEM));
+ ExInitializeWorkItem(WorkItem, (PWORKER_THREAD_ROUTINE)myRoutine, WorkItem);
+ ExQueueWorkItem(WorkItem, DelayedWorkQueue);
+
+ //リソース解放
+ IoUninitializeWorkItem(WorkItem);
+ IoFreeWorkItem(WorkItem);
+
+ return status;
+}
+```
+
+#最後に
+システムワーカースレッドは便利ですが、一歩間違えるとPC再起動まで永遠とシステムで動作するので、重いです。
+あと、めっちゃ速いです。`while(TRUE)`すると数秒で数百万回ループします。流石Ring0