windows驱动编程入门

刚学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,就会中断下来,命令输入窗口变为可用状态,可以输入各种命令了~~

如果需要进入系统之后才加载驱动的话,可以等系统启动完毕后再中断,输入断点命令.这取决于你驱动的加载时机。

 

注意!此时虚拟机启动时会出现“假死”的现象,这是因为主机连接上之后,windug中断的原因(ctrl+break),此时你只需要在主机的windug的命令窗口中输入“g”然后回车,虚拟机就能够接着启动并进入系统。
然后可以进入调试了:
下面是windbg的一些基本命令:

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  #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

 

 

 

你可能感兴趣的:(windows,驱动)