Windows驱动开发入门-时间与线程

时间

KeQueryTickCount

返回自系统启动后经历的步进数,不同硬件环境不同。

1
2
3
VOID KeQueryTickCount(
OUT PLARGE_INTEGER TickCount
)

KeQueryTimeIncrement

获取步进100纳秒数。

1
ULONG KeQueryTimeIncrement()

例子

获得系统启动后经历的毫秒数:

1
2
3
4
5
6
7
8
9
VOID MyGetTickCount(PULONG msec) {
LARGE_INTEGER tick_count = { 0 };
ULONG myinc = KeQueryTimeIncrement();
KeQueryTickCount(&tick_count);
tick_count.QuadPart *= myinc;
tick_count.QuadPart /= 10000;
*msec = tick_count.LowPart;
return;
};

KeQuerySystemTime

获取格林威治时间。

1
2
3
VOID KeQuerySystemTime(
OUT PLARGE_INTEGER CurrentTime
)

ExSystemTimeToLocalTime

转换为当地时间。

1
2
3
4
VOID ExSystemTimeToLocalTime(
IN PLARGE_INTEGER SystemTime,
OUT PLARGE_INTEGER LocalTime
)

RtlTimeToTimeFields

把上面那俩返回的长长整型转换为TIME_FIELDS类型。

1
2
3
4
VOID RtlTimeToTimeFields(
IN PLARGE_INTEGER Time,
IN PTIME_FIELDS TimeFields
)

例子

返回当前年月日时分秒,因静态变量下面这段代码非多线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
#include <ntstrsafe.h>
#pragma comment(lib,"ntstrsafe.lib")
PWCHAR MyCurTimeStr(VOID) {
LARGE_INTEGER snow = { 0 }, now = { 0 };
TIME_FIELDS now_fields = { 0 };
static WCHAR time_str[32] = { 0 };
KeQuerySystemTime(&snow);
ExSystemTimeToLocalTime(&snow, &now);
RtlTimeToTimeFields(&now, &now_fields);
RtlStringCchPrintfW(time_str, 32, TEXT("%4d-%2d-%2d %2d-%2d-%2d"), now_fields.Year, now_fields.Month, now_fields.Day, now_fields.Hour, now_fields.Minute, now_fields.Second);
return time_str;
};

DPC定时器

KeSetTimer

设置一个定时器。

1
2
3
4
5
BOOLEAN KeSetTimer(
IN PKTIMER Timer, //定时器
IN LARGE_INTEGER DueTime, //延后执行时间
IN OPTIONAL PKDPC Dpc //要执行的回调函数结构
)

KeInitializeTimer

用来初始化一个上面提到的Timer。

1
2
3
VOID KeInitializeTimer(
KTIMER Timer
)

PKDEFERRED_ROUTINE

不是个结构体,这函数指针指向这样一个函数类型,该函数运行在APC中断级别:

1
2
3
4
5
6
VOID CustomDpc(
IN struct _KDPC* Dpc,
IN PVOID DeferredContext, //就这个有用
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)

例子

这其实只是个延时执行罢了,如果要实现定时反复执行的话,需要在每个自定义DPC回调函数最后用KeSetTimer保证下次还能被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <ntddk.h>
#include <ntstrsafe.h>
LONG count = 0;
KTIMER g_ktimer;
KDPC g_kdpc;
// 自定义定时器函数
VOID MyTimerProcess(__in struct _KDPC* Dpc, __in_opt PVOID DeferredContext, __in_opt PVOID SystemArgument1, __in_opt PVOID SystemArgument2) {
LARGE_INTEGER la_dutime = { 0 };
la_dutime.QuadPart = 1000 * 1000 * -10;
// 递增计数器
InterlockedIncrement(&count);
DbgPrint("DPC 定时执行 = %d", count);
// 再次设置定时
KeSetTimer(&g_ktimer, la_dutime, &g_kdpc);
return;
};
VOID UnDriver(PDRIVER_OBJECT driver) {
// 取消计数器
KeCancelTimer(&g_ktimer);
DbgPrint(("Uninstall Driver Is OK \n"));
return;
};
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {
DbgPrint("hello lyshark \n");
LARGE_INTEGER la_dutime = { 0 };
// 每隔1秒执行一次
la_dutime.QuadPart = 1000 * 1000 * -10;
// 1.初始化定时器对象
KeInitializeTimer(&g_ktimer);
// 2.初始化DPC定时器
KeInitializeDpc(&g_kdpc, MyTimerProcess, NULL);
// 3.设置定时器,开始计时
KeSetTimer(&g_ktimer, la_dutime, &g_kdpc);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
};

IO定时器

IoInitializeTimer

初始化定时器。

1
2
3
4
5
6
7
8
NTSTATUS IoInitializeTimer(
_In_ PDEVICE_OBJECT DeviceObject,
//设备对象
_In_ PIO_TIMER_ROUTINE TimerRoutine,
//回调例程
_In_opt_ __drv_aliasesMes PVOID Context
//回调例程参数
)

IoStartTimer

启动定时器。

1
2
3
VOID IoStartTimer(
_In_ PDEVICE_OBJECT DeviceObject
)

IoStopTimer

关闭定时器。

1
2
3
VOID IoStopTimer(
_In_ PDEVICE_OBEJECT DeviceObject
)

例子

精度就到秒级,比DPC的方法精度低。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <ntddk.h>
#include <ntstrsafe.h>
LONG count = 0;
// 自定义定时器函数
VOID MyTimerProcess(__in struct _DEVICE_OBJECT* DeviceObject, __in_opt PVOID Context) {
InterlockedIncrement(&count);
DbgPrint("定时器计数 = %d", count);
LONG preCount = InterlockedCompareExchange(&count, 3, 0);
//每隔3秒计数器一个循环输出如下信息
if (preCount == 0)
DbgPrint("[LyShark] 三秒过去了 \n");
return;
};
VOID UnDriver(PDRIVER_OBJECT driver) {
// 关闭定时器
IoStopTimer(driver->DeviceObject);
// 删除设备
IoDeleteDevice(driver->DeviceObject);
DbgPrint(("Uninstall Driver Is OK \n"));
return;
};
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath) {
DbgPrint("hello lyshark \n");
NTSTATUS status = STATUS_UNSUCCESSFUL;
// 定义设备名以及定时器
UNICODE_STRING dev_name = RTL_CONSTANT_STRING(L"");
PDEVICE_OBJECT dev;
status = IoCreateDevice(Driver, 0, &dev_name, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &dev);
if (!NT_SUCCESS(status))
return STATUS_UNSUCCESSFUL;
else {
// 初始化定时器并开启
IoInitializeTimer(dev, MyTimerProcess, NULL);
IoStartTimer(dev);
};
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
};

线程

KeDelayExecutionThread

在线程中睡眠。

1
2
3
4
5
6
7
8
NTSTATUS KeDelayExecutionThread(
IN KPROCESSOR_MODE WaitMode;
//KernelMode
IN BOOLEAN Alertable;
//是否允许线程报警并重新唤醒 一般就FALES
IN PLARGE_INTEGER Interval;
//睡眠多久
)

例子

指定睡眠多少毫秒。

1
2
3
4
5
6
7
8
9
#define DELAY_ONE_MICROSECOND (-10)
#define DELAY_ONE_MILLISECOND (DELAY_ONE_MICROSECOND*1000)
VOID MySleep(LONG msec) {
LARGE_INTEGER my_interval = { 0 };
my_interval.QuadPart = DELAY_ONE_MILLISECOND;
my_interval.QuadPart *= msec;
KeDelayExecutionThread(KernelMode, FALSE, &my_interval);
return;
};

KeInitializeEvent

初始化一个事件,常见事件类型有SynchronizationEvent和NotificationEvent,这两种事件被设置后所有被该事件阻塞的线程都可同时继续进行。这俩事件类型的区别在于,NotificationEvent通知事件类型在被KeSetEvent设置后将一直保持被设置,直到再次被KeResetEvent设置为未设。而SynchronizationEvent同步事件类型在KeWaitForSingleObject后自动转为未设。

1
2
3
4
5
6
7
VOID KeInitializeEvent(
IN PRKEVENT Event;
IN EVENT_TYPE Type;
//类型
IN BOOLEAN State
//设置状态 一般初始化未设状态FALSE
)

KeSetValue

设置事件。

1
2
3
4
5
6
7
LONG KeSetEvent(
IN PRKEVENT Event;
IN KPRIORITY Increment,
//提升优先权 暂时用0
IN BOOLEAN Wait
//是否后面紧跟一个KeWaitSingleObject 一般就TRUE
)

KeResetEvent

重设一个NotificationEvent通知事件。

1
2
3
LONG KeResetEvent(
IN PRKEVENT Event
)

PsCreateSystemThread

新建系统线程,该线程所在进程名为“System”。

1
2
3
4
5
6
7
8
9
10
11
NTSTATUS PsCreateSystemThread(
OUT PHANDLE ThreadHandle,
IN ULONG DesiredAccess,
IN OPTIONAL POBJECT_ATTRIBTUES ObjectAttributes,
IN OPTIONAL HANDLE ProcessHandle,
OUT OPTIONAL PCLIENT_ID ClientId,
IN PKSTART_ROUTINE StartRoutine,
//要启动的回调函数
IN PVOID StartContext
//回调函数传参
)

PKSTART_ROUTINE

系统线程自定义回调函数。线程结束时要PsTerminateSystemThread,句柄要用ZwClose关闭。

1
2
3
VOID CustomThreadProc(
IN PVOID context
)

例子

如果创建的新线程中使用了原进程中的某些局部变量时,如果原进程函数结束后某些句柄或指针随之失效,新线程则无法调用。所以新线程所用数据尽量在堆上申请,且原线程尽量在新线程结束后结束。这里使用同步事件来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static KEVENT s_event = { 0 };
VOID MyThreadProc(PVOID context) {
PUNICODE_STRING str = (PUNICODE_STRING)context;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, TEXT("%wZ\r\n"), str);
KeSetEvent(&s_event, 0, TRUE);
PsTerminateSystemThread(STATUS_SUCCESS);
return;
};
VOID MyFunction(VOID) {
UNICODE_STRING str = RTL_CONSTANT_STRING(TEXT("xxx"));
HANDLE thread = NULL;
NTSTATUS status = STATUS_SUCCESS;
KeInitializeEvent(&s_event, SynchronizationEvent, TRUE);
status = PsCreateSystemThread(&thread, 0, NULL, NULL, NULL, MyThreadProc, (PVOID)&str);
if (!NT_SUCCESS(status)) {
//...
};
ZwClose(thread);
KeWaitForSingleObject(&s_event, Executive, KernelMode, 0, 0);
return;
};