2. 驱动卸载例程

任务说明

本案例实现一个最小化的 Windows 内核驱动,核心目标是编写 DriverUnload 例程,使驱动在卸载时能够打印一条退出消息,并正确释放驱动加载期间分配的资源。资源清理以本驱动实际分配的内容为准:一个内核字符串与一个事件对象。通过本案例,你将掌握驱动卸载例程的注册方式、资源释放顺序以及使用 WinDbg 或 DebugView 观察卸载消息的验证方法。

驱动实现

// DriverUnloadDemo.c
#include <ntddk.h>

// 全局句柄:用于保存驱动加载时创建的事件对象句柄
static HANDLE g_ExitEventHandle = NULL;

// 驱动卸载例程:打印消息并清理资源
VOID DriverUnload(
    _In_ PDRIVER_OBJECT DriverObject
)
{
    UNREFERENCED_PARAMETER(DriverObject);

    // 1. 打印卸载消息
    DbgPrint("[MyDriver] DriverUnload called. Cleaning up...\n");

    // 2. 释放事件对象(若已创建)
    if (g_ExitEventHandle != NULL)
    {
        ZwClose(g_ExitEventHandle);
        g_ExitEventHandle = NULL;
        DbgPrint("[MyDriver] Event handle closed.\n");
    }

    // 3. 释放内核字符串(若已分配)
    // 本驱动在 DriverEntry 中未分配字符串,此处仅为演示清理路径
    // 若实际分配过,应调用 RtlFreeUnicodeString 释放
    DbgPrint("[MyDriver] Unload completed.\n");
}

// 驱动入口例程
NTSTATUS DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
)
{
    UNREFERENCED_PARAMETER(RegistryPath);
    NTSTATUS status = STATUS_SUCCESS;

    DbgPrint("[MyDriver] DriverEntry called.\n");

    // 注册卸载例程
    DriverObject->DriverUnload = DriverUnload;

    // 创建一个事件对象,用于演示资源分配与释放
    HANDLE eventHandle = NULL;
    OBJECT_ATTRIBUTES objAttr;
    UNICODE_STRING eventName = RTL_CONSTANT_STRING(L"\\BaseNamedObjects\\MyDriverExitEvent");

    InitializeObjectAttributes(&objAttr, &eventName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);

    status = ZwCreateEvent(&eventHandle, EVENT_ALL_ACCESS, &objAttr, NotificationEvent, FALSE);
    if (NT_SUCCESS(status))
    {
        g_ExitEventHandle = eventHandle;
        DbgPrint("[MyDriver] Event created successfully.\n");
    }
    else
    {
        DbgPrint("[MyDriver] Failed to create event, status: 0x%X\n", status);
    }

    return STATUS_SUCCESS;
}

资源清理逻辑说明

上述代码中,DriverEntry 通过 DriverObject->DriverUnload = DriverUnload 注册卸载例程。驱动加载时创建了一个命名事件对象,句柄保存在全局变量 g_ExitEventHandle 中。当驱动被卸载时,系统调用 DriverUnload,该例程首先打印卸载消息,然后关闭事件句柄并将全局句柄置空,最后打印完成消息。若驱动在加载阶段分配了其他资源(如内存、字符串等),应在关闭句柄之后、返回之前按逆序释放。本案例中未分配字符串,因此仅以注释形式说明释放路径,避免引入未使用的代码。

编译与部署

  1. 使用 Visual Studio 的 WDK 驱动项目模板创建空驱动项目,将上述代码保存为 DriverUnloadDemo.c 并添加到项目。
  2. 编译生成 DriverUnloadDemo.sys。
  3. 在测试机(或虚拟机)上,以管理员身份打开命令提示符,创建服务并启动驱动:
sc create MyDriver type= kernel binPath= C:\Path\To\DriverUnloadDemo.sys
sc start MyDriver
sc stop MyDriver
sc delete MyDriver

验证方法与判定标准

使用 WinDbg 连接测试机进行内核调试,或使用 DebugView 捕获内核输出。执行上述 sc start 与 sc stop 命令后,在调试输出中应依次观察到以下消息:

  • 驱动加载时:[MyDriver] DriverEntry called. 与 [MyDriver] Event created successfully.
  • 驱动卸载时:[MyDriver] DriverUnload called. Cleaning up...、[MyDriver] Event handle closed. 与 [MyDriver] Unload completed.

若三条卸载消息均按顺序出现,且系统未报告驱动卸载失败或蓝屏,则判定本案例实现正确。若事件创建失败,加载阶段会打印失败状态码,此时应检查事件名是否冲突或权限是否足够,但卸载例程仍会正常执行并打印消息。

驱动卸载例程资源生命周期 用户态 (sc.exe) 驱动加载/卸载路径 资源状态 sc start MyDriver DriverEntry 被调用 ZwCreateEvent 创建事件 事件句柄已创建 (g_ExitEventHandle) 注册 DriverUnload 回调 sc stop MyDriver DriverUnload 被调用 打印卸载消息 ZwClose 关闭事件句柄 句柄已关闭,置 NULL 验证点:观察 DbgPrint 输出