先搭建内核开发环境
windbg驱动调试的环境搭建。
推荐阅读微软官方文档开始使用 WinDbg(内核模式) - Windows drivers | Microsoft Learn
windows系统内核调试 环境搭建(保姆级)_dsigntool-CSDN博客
下载 Windows 驱动程序工具包 (WDK) - Windows drivers | Microsoft Learn
驱动开发:WinDBG 配置内核双机调试 - lyshark - 博客园
一些要用到的命令。
串行接口
\\.\pipe\com_2
符号表环境变量
_NT_SYMBOL_PATH SRV*D:\Myself_Software\Windows_soft\symbols*http://msdl.microsoft.com/download/symbols
windbg内核调试操作
开启调试服务(需要关闭安全引导)
bcdedit /debug on
bcdedit /dbgsettings serial debugport:1 baudrate:115200
.reload #加载内核符号
windbg常用快捷键
F5 继续
F10 逐过程
F11 逐语句
Shift+F11 单步跳出
F7 运行到行
Ctrl + F9 在突出显示的行上切换启用断点状态
Shift + F9 添加断点
Ctrl+Shift+O 打开脚本
Ctrl+Shift+Enter 执行脚本
Ctrl+S 保存脚本
Alt+S、N 新脚本
控制台常用命令
.hh 命令访问参考命令帮助
g 运行
.symfix 设置符号表或添加本地符号(.symfix+ path)
.reload 加载符号表 (/f添加路径)
lm 列出所有模块
lm m name v 特性模块信息
x [Options] Module!Symbol 检查符号
x /D model!name 查找符号可用通配符*
bl 查看断点
bp 设置一个将一直处于活动状态的断点,直到其所在模块被卸载。
bu 设置一个断点,该断点在卸载模块时未解析,并在重新加载模块时重新启用。
bm 为符号设置一个断点。 此命令适当地使用 bu 或 bp,并允许使用通配符(*)对匹配的每个符号(如类中的所有方法)设置断点。
bc 清除列表中的断点。 使用 bc * 清除所有断点。
bd 禁用断点。 使用 bd * 禁用所有断点。
be 启用断点。 使用 be * 启用所有断点。
ba <access> <size> <address> {options} 设置在访问内存位置时触发的断点(e 执行:当 CPU 从地址中提取指令时。r 读/写:当 CPU 读取或写入地址时。w write:当 CPU 写入地址时)
.bpcmds (显示断点命令)
dv 命令来显示给定帧的所有局部变量的名称和值
kp 显示堆栈和参数的完整列表。
kn 允许您查看堆栈以及旁边的帧信息。
!process 调试器扩展来显示或设置进程信息
dv dename!name 查与例程关联的区域设置变量
!process 0 0 显示所有进程的摘要信息。(0 27)输出全部
!thread 命令查看线程
.thread 设置当前线程
r 查看寄存器
!lmi 扩展显示有关模块的详细信息
!dh 扩展显示标头信息
lsf- [filename] 添加源码
.lsrcpath+ 设置本地源路径
驱动开发环境配置
微软的教程非常的好用,下面我只提出几点需要记住的比较重要的事项。
编写 Hello World Windows 驱动程序(内核模式) - Windows drivers | Microsoft Learn
我这里使用的是windows11 + vs2022的主机和用于调试的windows 10的虚拟机。开发框架采用KWDF。
KWDF是以调用回调函数的来执行操作的,每一个函数都对应一个事件,通过事件的触发来调用回调函数进行数据处理。由于封装的方法较多,开发比较简单。
根据微软的教程,配置好环境,我们在编写完后可以用vs直接把驱动部署在虚拟机上,其中的驱动文件位置在
%systemdrive%\drivertest\drivers
把devcon.exe放到虚拟机的驱动位置下,通过命令来进行驱动程序的安装。(好像需要管理员权限)
devcon install XXX.inf root\XXX
root\后面的内容可以在inf文件中找到(驱动程序路径)
[Standard.NT$ARCH$]
%KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
源码调试时用,把pdb文件所在位置设置为本地源路径,.reload后会自动加载源码的符号。
.lsrcpath+ 设置本地源路径
在windbg的左上角点击文件,选择source file添加源代码(直接显示源代码窗口),或者用lsf- [filename] 添加源码(无窗口)。根据微软文档的解释,如果使用 WinDbg,只要程序计数器位于调试器具有其源信息的代码中,就会立即显示“源”窗口。即用源代码进行调试。在源模式下调试 - Windows drivers | Microsoft Learn
跟随官方文档,我们的环境差不多安装完成,接下来正式进行代码编写。
驱动开发结构分析与学习
下面通过一个以diviceiocontrol进行通信的程序为例子分析一个驱动程序的一些主要要素。
Deviceiocontrol:应用程序与驱动程序通信 DeviceIoControl - 沉疴 - 博客园
#include <ntddk.h>
#include <wdf.h>
#include <initguid.h>
// 自定义 IOCTL 控制码,用于deviceiocontrol通信执行操作 参数(irp生成函数,设备对象类型,ioct控制码(0x800--0xfff),
// 访问权限)
#define Driver_IOCT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 回调函数声明
DRIVER_INITIALIZE DriverEntry; //声明入口
EVT_WDF_DRIVER_DEVICE_ADD HelloKMDFEvtDeviceAdd; //声明在设备连接时调用的函数
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL HelloKMDFEvtIoDeviceControl; //deviceiocontrol消息处理函数
// 驱动入口 参数(驱动对象,注册表路径)
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
WDF_DRIVER_CONFIG config; //WDF_DRIVER_CONFIG结构体,保存了驱动的一些信息
WDF_DRIVER_CONFIG_INIT(&config, HelloKMDFEvtDeviceAdd); //初始化回调函数(把回调函数注册到驱动的信息中)
KdPrint(("HelloKMDF: DriverEntry\n")); //输出信息
//KMDF创建并注册驱动对象,这代表我们已经完成所有回调函数的部署,KMDF框架自动帮我们注册驱动。
return WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, WDF_NO_HANDLE);
}
// 设备添加时回调(由WDF_DRIVER_CONFIG_INIT()注册)
NTSTATUS HelloKMDFEvtDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT DeviceInit) {
UNREFERENCED_PARAMETER(Driver);
WDFDEVICE device;
NTSTATUS status;
// 设置设备名字
DECLARE_CONST_UNICODE_STRING(devName, L"\\Device\\HelloKMDF");
WdfDeviceInitAssignName(DeviceInit, &devName);
// 创建设备
status = WdfDeviceCreate(&DeviceInit, WDF_NO_OBJECT_ATTRIBUTES, &device);
if (!NT_SUCCESS(status)) return status;
// 创建默认队列处理 DeviceIoControl
WDF_IO_QUEUE_CONFIG ioQueueConfig;
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&ioQueueConfig, WdfIoQueueDispatchSequential);
ioQueueConfig.EvtIoDeviceControl = HelloKMDFEvtIoDeviceControl;
return WdfIoQueueCreate(device, &ioQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, WDF_NO_HANDLE);
}
// 处理用户态 IOCTL 请求
VOID HelloKMDFEvtIoDeviceControl(WDFQUEUE Queue, WDFREQUEST Request,
size_t OutputBufferLength, size_t InputBufferLength,
ULONG IoControlCode) {
UNREFERENCED_PARAMETER(Queue);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(InputBufferLength);
if (IoControlCode == IOCTL_HELLOKMDF) {
KdPrint(("HelloKMDF: 收到 IOCTL_HELLOKMDF 指令!\n"));
}
WdfRequestComplete(Request, STATUS_SUCCESS);
}