刚学windows驱动,经过几天的摸索,有一些经验,先总结如下;
必备工具:windows驱动开发包:wdk;调试工具:windbg;驱动加载工具:INSTDRV.exe
至于windows驱动的一些流程,说实话我也没弄太清楚,只不过先弄一个例子手动抄写一些,编译调试一下看看吧。
例子程序是我在网上找的:
/***************************************************************
程序名称:Hello World for WDM
文件名称:HelloWDM.cpp
作者:罗聪
日期:2002-8-16
***************************************************************/
//一定要的头文件,声明了函数模块和变量:
#include "HelloWDM.h"
/***************************************************************
函数名称:DriverEntry()
功能描述:WDM程序入口
***************************************************************/
//extern "C"是必须的,表示“用C链接”。如果你的文件名是HelloWDM.c的话,这句可以省略。
extern "C"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
//指定“添加设备”消息由函数“HelloWDMAddDevice()”来处理:
DriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
//指定“即插即用”消息由函数“HelloWDMPnp()”来处理:
DriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
//返回一个NTSTATUS值STATUS_SUCCESS。几乎所有的驱动程序例程都必须返回一个NTSTATUS值,这些值在NTSTATUS.H DDK头文件中有详细的定义。
return STATUS_SUCCESS;
}
/***************************************************************
函数名称:HelloWDMAddDevice()
功能描述:处理“添加设备”消息
***************************************************************/
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject)
{
//定义一个NTSTATUS类型的返回值:
NTSTATUS status;
//定义一个功能设备对象(Functional Device Object):
PDEVICE_OBJECT fdo;
//创建我们的功能设备对象,并储存到fdo中:
status = IoCreateDevice(
DriverObject, //驱动程序对象
sizeof(DEVICE_EXTENSION), //要求的设备扩展的大小
NULL, //设备名称,这里为NULL
FILE_DEVICE_UNKNOWN, //设备的类型,在标准头文件WDM.H或NTDDK.H中列出的FILE_DEVICE_xxx值之一
0, //各种常量用OR组合在一起,指示可删除介质、只读等。
FALSE, //如果一次只有一个线程可以访问该设备,为TRUE,否则为FALSE
&fdo); //返回的设备对象
//NT_SUCCESS宏用于测试IoCreateDevice内核是否成功完成。不要忘记检查对内核的所有调用是否成功。NT_ERROR宏不等同于!NT_SUCCESS,最好使用!NT_SUCCESS,因为除了错误外,它还截获警告信息。
if( !NT_SUCCESS(status))
return status;
//创建一个设备扩展对象dx,用于存储指向fdo的指针:
PDEVICE_EXTENSION dx = (PDEVICE_EXTENSION)fdo->DeviceExtension;
dx->fdo = fdo;
//用IoAttachDeviceToDeviceStack函数把HelloWDM设备挂接到设备栈:
dx->NextStackDevice = IoAttachDeviceToDeviceStack(fdo, PhysicalDeviceObject);
//设置fdo的flags。有两个“位”是必须改变的,一个是必须清除DO_DEVICE_INITIALIZING标志,如果在DriverEntry例程中调用IoCreateDevice(),就不需要清除这个标志位。还有一个是必须设置DO_BUFFER_IO标志位:
fdo->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
fdo->Flags &= ~DO_DEVICE_INITIALIZING;
//返回值:
return STATUS_SUCCESS;
}
/***************************************************************
函数名称:HelloWDMPnp()
功能描述:处理“即插即用”消息
***************************************************************/
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp)
{
//创建一个设备扩展对象dx,用于存储指向fdo的指针:
PDEVICE_EXTENSION dx=(PDEVICE_EXTENSION)fdo->DeviceExtension;
//首先要通过函数IoGetCurrentIrpStackLocation()得到当前的IRP,并由此得到Minor Function:
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(Irp);
ULONG MinorFunction = IrpStack->MinorFunction;
//然后把这个Minor Function传递给下一个设备栈:
IoSkipCurrentIrpStackLocation(Irp);
NTSTATUS status = IoCallDriver( dx->NextStackDevice, Irp);
//处理“即插即用”次功能代码:
//当Minor Function等于IRP_MN_REMOVE_DEVICE时,说明有设备被拔出或卸下,这时要取消资源分配并删除设备:
if( MinorFunction==IRP_MN_REMOVE_DEVICE)
{
//取消设备接口:
IoSetDeviceInterfaceState(&dx->ifSymLinkName, FALSE);
RtlFreeUnicodeString(&dx->ifSymLinkName);
//调用IoDetachDevice()把fdo从设备栈中脱开:
if (dx->NextStackDevice)
IoDetachDevice(dx->NextStackDevice);
//删除fdo:
IoDeleteDevice(fdo);
}
//返回值:
return status;
}
/***************************************************************
程序名称:Hello World for WDM
文件名称:HelloWDM.h
作者:罗聪
日期:2002-8-16
***************************************************************/
//头文件,只是声明一些函数和变量,比较简单就不多说了,请读者自行研究:
#ifdef __cplusplus
extern "C"
{
#endif
#include "ntddk.h"
#ifdef __cplusplus
}
#endif
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
PDEVICE_OBJECT NextStackDevice;
UNICODE_STRING ifSymLinkName;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT DriverObject,
IN PDEVICE_OBJECT PhysicalDeviceObject);
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT fdo,
IN PIRP Irp);
编写sources文件:
TARGETNAME=HelloWDM
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=HelloWDM.c
编写makefile文件:
!INCLUDE $(NTMAKEENV)/makefile.def
然后在某个盘符下面建立一个目录,把这几个文件放到该目录下面,注意!整个目录文件的路径不能有空格,否则编译出错!我就在这上面栽了跟斗,刚开始在桌面下面建立文件夹,后来老是报错,原来桌面路径中有一个Documents and Settings中间有空格,所以报错。
然后在wdk里面选择Windows Driver Kits->WDK 7600.16385.1->Build Environments;然后选择要加载驱动的目标机编译环境,单击进入cmd命令环境,输入build -ceZ进行编译。在这中间我遇到了两个错误,第一个是由于我单词拼写错误,第二个错误比较重要,主要原因是在编写驱动程序时,一定要把变量定义放在函数的刚开始,然后后面是语句,否则会编译出错。
最后就是调试了。在这里我们一般用虚拟机调试。建立调试环境的过程如下:
1、在主机上的windug的配置:
是在G盘,建立一个mylocalsymbols文件夹,用来存放系统的符号文件;然后再在建立一个mysyssymbols文件夹,用来存放自己的调试符号文件。
2、主机环境变量的设置:
如果你想Windbg启动时自动识别符号路径的设置的话,我们就来建立一个环境变量
在"我的电脑"上右击,"属性"->"高级"->"环境变量",然后我们给当前用记新建一个名为_NT_SYMBOL_PATH的环境变量;值为:
G:/work/mylocalsymbols;G:/work/mysyssymbols;SRV*D:/MyLocalSymbols*http://msdl.microsoft.com/download/symbols
这样设置以后,系统的调试符号就在G:/work/mylocalsymbols下,而我们自己驱动的调试符号就在G:/work/mysyssymbols下
当符号文件不匹配时,Windbg会自动连网从http://msdl.microsoft.com/download/symbols下载符号文件到G:/work/mylocalsymbols,下次再用到时就不用下载了
设置好保存就可以了,修改环境变量需要重新启动系统才能生效。
然后是vmware的设置:
1.编辑Vmware设置,添加一个串口设备.
首先我们要添加一个串口设备.打开你的虚拟机,选择“编辑虚拟机设置”;然后"Add...(添加)"->"Serial Port(串口)"->"Output to named pipe(输出到命名管道)"一路Next,名称就用默认的"//./pipe/com_1",这个其实对应于你的串口名称com1,用别的名字当然也可以,不过后面要对应;
第二行选择"This end is the server"
第三行选择"The other end is an application"
同时把下面的"Connect at power on(打开电源时连接)";
2.设置串口波特率
启动虚拟机中的OS,进入系统,打开设备管理器选项,这时就看到刚才添加的串口com1了,双击com1设备进入属性设置,在"端口设置"选项卡中选择"每秒位数"也就是波特率为115200
3.修改boot.ini(如果是Vista以上就不是修改boot.ini了,需要别的修改方式)
这一步准确讲是添加DEBUG启动模式,对于Win2003及以前的系统,可以通过修改boot.ini来实现,对于Vista,Server2008,Win7等就得用bcdedit来编辑启动设置了,有需要的可以搜索"Windbg内核调试之一 Vista Boot Config设置"来查找那篇文章
我的GuestOS是WinXP Sp3,所以采用boot.ini的方法,打开C盘,把隐藏文件显示出来,去掉boot.ini的只读属性,双击打开来
通常你只会看到一个启动选项,我们复制一个,在后面加上 /debug /debugport=com1 /baudrate=115200
注意修改时间timeout,不要为0或太短,否则你连选择的机会都没有。
然后你可以往虚拟机中放一些测试驱动常用的工具,比如DebugView,InstDrv等等。
4.建立双机调试快捷方式
在桌面建立一个Windbg的快捷方式命名为“双机调试”,然后编辑其属性,把“目标”后面加上
-k com:port=//./pipe/com_1,baud=115200,pipe
比如我的电脑上,完整的应该是这个样子:
"C:/Program Files/Debugging Tools for Windows (x86)/windbg.exe" -k com:port=//./pipe/com_1,baud=115200,pipe
重新启动虚拟机中的系统,在启动菜单时选择有“启用调试程序”的那一项,回车确认,然后把虚拟机最小化,回到桌面双击刚才建好的“双机调试”快捷方式,如果没有什么意外的话,稍等一下就会看到:
Connected to Windows XP 2600 x86 compatible target,ptr64 FALSE
Kernel Debugger connection established.
这就表示已经连接成功了,接下来会显示一下符号路径,内核基址等信息。
此时按下Ctrl+Break,就会中断下来,命令输入窗口变为可用状态,可以输入各种命令了~~
如果需要进入系统之后才加载驱动的话,可以等系统启动完毕后再中断,输入断点命令.这取决于你驱动的加载时机。
1.基本调试控制
运行程序(Run): 快捷键:F5 命令:g
单步步入(Step In): 快捷键:F8 命令:p
单步步过(Step Over): 快捷键:F10
运行到光标所在行: 快捷键:F7
执行到返回:gu
执行到指定地址:g [Address]
重新运行调试程序: 快捷键:Ctrl+Shift+F5(这个对驱动一般用不到)
2.断点
断点之于调试当然是非常重要的
常用命令:
bp [Address]or[Symbol] 在指定地址下断
可以使用地址或符号,如
bp 80561259(Windbg默认使用16进制)
bp MyDriver!GetKernelPath
bp MyDriver!GetKernelPath+0x12
bp [Address] /p eprocess 仅当当前进程为eprocess时才中断
这个很常用,比如你bp nt!NtTerminateProcess,但是只想在某一进程触发此断点时才断下来,那就加上这个参数吧,因为内核中的代码是各个进程共用的,所以此命令很实用
bp [Address] /t ethread 仅当当前线程为ethread时才中断,用法跟/p参数类似
bu [Address]or[Symbol] 下一个未解析的断点(就是说这个断点需要延迟解析)
这个也很常用,比如我们的驱动名为MyDriver.sys,那么在驱动加载之前下断bu MyDriver!DriverEntry,
然后加载这个驱动时就可以断在驱动入口,并且这个是不需要调试符号支持的
bl 列出所有断点,L=List
bc[id] 清除断点,c=Clear,id是bl查看时的断点编号
bd[id] 禁用断点,d=Disable,id即断点编号
be[id] 启用断点,e=Enable,id为断点编号
3.查看和修改数据
调试中不可避免的要查看和修改数据
查看内存:
db/dw/dd/dq [Address] 字节/字/双字/四字方式查看数据
da/du [Address] ASCII字符串/Unicode字符串方式查看指定地址
其它常用的如查看结构
dt nt!_EPROCESS
dt nt!_EPROCESS 89330da0 (把0x89330da0作为对象指针)
修改内存:
eb/ew/ed/eq/ef/ep Address [Values]
字节/字/双字/四字/浮点数/指针/
ea/eu/eza/ezu Address [Values]
ASCII字符串/Unicode字符串/以NULL结尾的ASCII字符串/以NULL结尾的Unicode字符串
搜索内存:
s -[b/w/d/q/a/u] Range Target
搜索字节/字/双字/四字/ASCII字符串/Unicode字符串
4.寄存器
在用Windbg调试时可以Alt+4直接调出寄存器窗口,然后拖放到合适的位置就可以。
要修改呢就直接双击相应的项就可以了。
把命令的方式也说一下,比较简单:
r 显示所有寄存器的值
r eax 显示eax的值
r eax=1 修改eax的值为1
5.辅助命令
!process 显示当前进程信息
!process 0 0 显示当前所有进程(会有僵尸进程)
!process 1f4 显示pid为1f4的进程信息,后面也可以跟eprocess的值
!thread 显示当前线程信息
!thread
!process 1f4 显示tid为768的线程信息,后面也可以跟ethread的值
栈相关:
k 显示调用栈
kb 显示ebp和前3个参数
kp 以函数调用形式显示栈
然后就可以加载驱动,并进行调试了。
ok,下面再把整个调试过程做个简单的总结:
第一步、设置主机windbg的参数。
第二步、添加vm的串口,并修改串口参数;然后修改vm目标机的boot.ini文件,增加通过串口调试启动选择。
第三步、修改主机windbug快捷方式的启动参数,把编译好的符号文件.pdb放入到自己先前设置的符号文件路径中。
第四步、重新启动虚拟机,以调试方式进入系统。
第五步、启动主机的windbg。
第六部、windbg连接成功后,输入“g”命令,让目标机继续运行,并进入系统。
第七步、在windug中按下“ctrl+pausebreak”键,把要调试的驱动主函数DriverEntry设为断点。
第八部、输入g命令继续运行。
第九步、在目标机中用instdrv.exe加载驱动,并运行。
第十步、在windug中输入F10、或者F11进行调试。
另外有一个单独加载驱动程序的程序,大家可以参考一下(我也是从网上找到的)
驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什么区别。
安装驱动程序流程:
1,调用OpenSCManager()打开服务控制管理器
2,调用CreateService()创建一个服务,服务类型为内核驱动
3,调用OpenService()取得服务句柄
启动服务
4,调用StartService()启动服务
停止服务
4,调用ControlService()停止服务
删除服务
4,调用DeleteService()删除服务
5,调用CloseServiceHandle()关闭服务句柄
操作驱动程序流程:
1,调用CreateFile()取得设备句柄
2,调用DeviceIoControl()传递I/O控制代码
3,调用CloseHandle()关闭设备句柄
http://www.xfocus.net/tools/200411/882.html
这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码
完整代码:
=================================================================================================================================
参考资料
《Windows 2000 DDK》
《Windows 2000 驱动程序设计》
附录代码:
- #ifndef __HELLOWORLD_C__
- #define __HELLOWORLD_C__
- #define DEBUGMSG
- #include <ntddk.h>
- #define DEVICE_HELLO_INDEX 0x860
- //2个IOCTL宏
- #define START_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define STOP_HELLPWORLD CTL_CODE(FILE_DEVICE_UNKNOWN,DEVICE_HELLO_INDEX+1,METHOD_BUFFERED,FILE_ANY_ACCESS)
- #define NT_DEVICE_NAME L"//Device//HelloWorld" //设备名称
- #define DOS_DEVICE_NAME L"//DosDevices//HelloWorld" //符号连接
- NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp);
- VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject);
- //驱动入口
- NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
- {
- NTSTATUS ntStatus=STATUS_SUCCESS;
- PDEVICE_OBJECT lpDeviceObject=NULL; //指向设备对象的指针
- UNICODE_STRING DeviceNameString={0}; //设备名称
- UNICODE_STRING DeviceLinkString={0}; //符号连接
- //调试信息
- #ifdef DEBUGMSG
- DbgPrint("Starting DriverEntry()/n");
- #endif
- RtlInitUnicodeString(&DeviceNameString,NT_DEVICE_NAME); //初始化Unicode字符串
- //创建设备
- ntStatus=IoCreateDevice(DriverObject,0,&DeviceNameString,FILE_DEVICE_UNKNOWN,0,FALSE,&lpDeviceObject);
- //使用NT_SUCCESS宏检测函数调用是否成功
- if (!NT_SUCCESS(ntStatus))
- {
- #ifdef DEBUGMSG
- DbgPrint("IoCreateDevice() error reports 0x%08X/n",ntStatus);
- #endif
- return ntStatus;
- }
- RtlInitUnicodeString(&DeviceLinkString,
连接:http://www.cnblogs.com/piccolo/articles/237507.html