我们学习程序设计,都是从“Hello World”开始的,驱动程序也不例外,今天我就写一个驱动版的“Hello World”来热热身,目的希望大家能对驱动程序的基本框架有所了解。
驱动程序分为2类,一个是Kernel模式驱动,另一个是Windows模式驱动,2种模式本质是相同,但细节不同,本文介绍的是内核模式驱动和驱动程序的安装、使用。
驱动程序同普通的EXE,DLL一样,都属于PE文件,而且都有一个入口函数。但EXE中,入口函数是main()/WinMain()和Unicode的wmain()/wWinmain(),DLL的入口函数则可有可无,它是DllMain()。驱动程序也有入口函数,而且是必须的,它是DriverEntry(),再次提示,它是必须的,因为I/O管理器会首先调用驱动程序的DriverEntry(),它的作用就像DllMain()--完成一些初始化工作。DriverEntry()一共有2个参数:1PDRIVER_OBJECT DriverObject,指向驱动程序对象的指针,我们操作驱动程序,全靠它,它是由I/O管理器传递进来的;2)PUNICODE_STRING RegistryPath,驱动程序的服务主键,这个参数的使用并不多,但要注意,在DriverEntry()返回后,它可能会消失,所以如果需要使用,记住先要保存下来。DriverEntry()的返回一个NTSTATUS值,它是一个ULONG值,具体的定义,请参见DDK中的NTSTATUS.H头文件,里边有详细的定义。
既然要写驱动版的“Hello World”,就需要确定如何来与驱动程序通信,常用的共享内存,共享事件,IOCTL宏,或者直接用ReadFile()或WriteFile()进行读写,在本文里我就采用一种简单的、但又很常用的IOCTL宏,它依赖的IRP派遣例程是IRP_MJ_DEVICE_CONTROL,Win32程序使用DeviceIoControl()与驱动进行通信,根据不同的IOCTL宏,输出不同的调试信息。为了简便,我并没有使用ReadFile()将信息读出来,而是直接用DbgPrint()输出,所以需要使用DbgView查看,其他调试工具也可以。PS:偷懒!
驱动程序与I/O管理器通信,使用的是IRP,即I/O请求包。IRP分为2部分:1)IRP首部;2)IRP堆栈。IRP首部信息如下:
IRP首部:
PVOID AssociatedIrp.SystemBuffer 如果执行缓冲区I / O,这个指针指向系统缓冲区
PMDL MdlAddress 如果直接I / O,这个指针指向用户缓冲区的存储器描述符表
PVOID UserBuffer I / O缓冲区的用户空间地址
IRP堆栈:
UCHAR MajorFunction 指示IRP_MJ_XXX派遣例程
UCHAR MinorFunction 同上,一般文件系统和SCSI驱动程序使用它
{
struct Read IRP_MJ_READ的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct Write IRP_MJ_WRITE的参数
ULONG Length
ULONG Key
LARGE_INTEGER ByteOffset
struct DeviceIoControl IRP_MJ_DEVICE_CONTROL参数
ULONG OutputBufferLength
ULONG InputBufferLength
ULONG IoControlCode
PVOID Type3InputBuffer
}
PDEVICE_OBJECT DeviceObject 请求的目标设备对象的指针
PFILE_OBJECT FileObject 请求的目标文件对象的指针,如果有的话
操作IRP。对于不同的IRP函数,操作也是不同的:有的只操作IRP首部;有的只操作IRP堆栈;还有操作IRP整体,下面是一些常用的函数:
IRP整体:
名称 描述 调用者
IoStartPacket 发送IRP到Start I/O例程 Dispatch
IoCompleteRequest 表示所有的处理完成 DpcForIsr
IoStartNextPacket 发送下一个IRP到Start I/O例程 DpcForIsr
IoCallDriver 发送IRP请求 Dispatch
IoAllocateIrp 请求另外的IRP Dispatch
IoFreeIrp 释放驱动程序分配的IRP I/O Completion
IRP堆栈:
名称 描述 调用者
IoGetCurrentIrpStackLocation 得到调用者堆栈的指针 Dispatch
IoMarkIrpPending 为进一步的处理标记调用者I/O堆栈 Dispatch
IoGetNextIrpStackLocation 得到下一个驱动程序的I/O堆栈的指针 Dispatch
IoSetNextIrpStackLocation 将I/O堆栈指针压入堆栈 Dispatc
在驱动程序,IRP派遣例程起着很重要的作用,每个IRP派遣例程,几乎都有对应的Win32函数,下面是几个常用的:
IRP派遣例程:
名称 描述 调用者
IRP_MJ_CREATE 请求一个句柄 CreateFile
IRP_MJ_CLEANUP 在关闭句柄时取消悬挂的IRP CloseHandle
IRP_MJ_CLOSE 关闭句柄 CloseHandle
IRP_MJ_READ 从设备得到数据 ReadFile
IRP_MJ_WRITE 传送数据到设备 WriteFile
IRP_MJ_DEVICE_CONTROL 控制操作(利用IOCTL宏) DeviceIoControl
IRP_MJ_INTERNAL_DEVICE_CONTROL 控制操作(只能被内核调用) N/A
IRP_MJ_QUERY_INFORMATION 得到文件的长度 GetFileSize
IRP_MJ_SET_INFORMATION 设置文件的长度 SetFileSize
IRP_MJ_FLUSH_BUFFERS 写输出缓冲区或者丢弃输入缓冲区 FlushFileBuffers FlushConsoleInputBuffer PurgeComm
IRP_MJ_SHUTDOWN 系统关闭 InitiateSystemShutdown
====================================================================================================
下面开始写我们的驱动版的“Hello World”,程序很简单,先介绍一下流程:
1,调用IoCreateDevice()创建一个设备,并返回一个设备对象。
2,调用IoCreateSynbolicLink()创建一个符号连接,使Win32程序可以使用驱动程序
3,设置IRP_MJ_DEVICE_CONTROL派遣例程HelloWorldDispatch()和卸载例程HelloWorldUnLoad()。
如果Win32程序使用DeviceIoControl(),则执行HelloWorldDispatch()函数
4,调用IoGetCurrentIrpStackLocation()得到当前调用者的IRP指针
5,取得IO控制代码,完成后使用IoCompleteRequest()完成IRP操作
如果使用ControlService()停止驱动程序,则执行HelloWorldUnLoad()函数
4,调用IoDeleteSymbolicLink()删除符号连接
5,调用IoDeleteDevice()删除已建立的设备
// 创建设备
IoCreateDevice(DriverObject, // 驱动程序对象
0 , // 扩展设备的大小,由于不需要,所以置0
& DeviceNameString, // 设备名称
FILE_DEVICE_UNKNOWN, // 设备类型
0 , // 指示设备允许的操作
FALSE, // 如果为TRUE,表示只能有一个线程使用该设备,为FALSE,则没有限制
& lpDeviceObject); // 返回的设备对象
// 创建符号连接
IoCreateSymbolicLink( & DeviceLinkString, // 存放符号连接的UNICODE_STRING
& DeviceNameString); // 设备名称
// 派遣例程和卸载例程
DriverObject -> MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloWorldDispatch;吧
DriverObject -> DriverUnload = HelloWorldUnLoad;
IRP派遣例程HelloWorldDispatch()
IrpStack = IoGetCurrentIrpStackLocation(pIrp); // 得到当前调用者的IRP堆栈
// 获取IO控制代码,并执行指定操作,这里只是DbgPrint()
IoControlCodes = IrpStack -> Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes) {
......
IoCompleteRequest(pIrp,IO_NO_INCREMENT); // 完成IRP操作
卸载例程HelloWorldUnLoad()
// 删除符号连接和设备
IoDeleteSymbolicLink( & DeviceLinkString);
IoDeleteDevice(DriverObject -> DeviceObject);
====================================================================================================完整代码:
#define __HELLOWORLD_C__
#define DEBUGMSG
#include " HelloWorld.h "
// 驱动入口
NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
PDEVICE_OBJECT lpDeviceObject = NULL; // 指向设备对象的指针
UNICODE_STRING DeviceNameString; // 设备名称
UNICODE_STRING DeviceLinkString; // 符号连接
// 调试信息
#ifdef DEBUGMSG
DbgPrint( " Starting DriverEntry() " );
#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( " Error IoCreateDevice() " );
#endif
goto Error;
}
RtlInitUnicodeString( & DeviceLinkString,DOS_DEVICE_NAME);
// 创建符号连接
ntStatus = IoCreateSymbolicLink( & DeviceLinkString, & DeviceNameString);
if ( ! NT_SUCCESS(ntStatus))
{
#ifdef DEBUGMSG
DbgPrint( " Error IoCreateSymbolicLink() " );
#endif
goto Error;
}
// 设置IRP派遣例程和卸载例程
DriverObject -> MajorFunction[IRP_MJ_CREATE] = // HelloWorldDispatch;
DriverObject -> MajorFunction[IRP_MJ_CLOSE] = // HelloWorldDispatch;
DriverObject -> MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloWorldDispatch;
DriverObject -> DriverUnload = HelloWorldUnLoad;
return ntStatus;
Error:
#ifdef DEBUGMSG
DbgPrint( " Error DriverEntry() " );
#endif
return ntStatus;
}
NTSTATUS HelloWorldDispatch (IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
ULONG IoControlCodes = 0 ; // I/O控制代码
PIO_STACK_LOCATION IrpStack = NULL; // IRP堆栈
// 设置IRP状态
pIrp -> IoStatus.Status = STATUS_SUCCESS;
pIrp -> IoStatus.Information = 0 ;
#ifdef DEBUGMSG
DbgPrint( " Starting HelloWorldDispatch() " );
#endif
IrpStack = IoGetCurrentIrpStackLocation(pIrp); // 得到当前调用者的IRP
switch (IrpStack -> MajorFunction)
{
case IRP_MJ_CREATE:
#ifdef DEBUGMSG
DbgPrint( " IRP_MJ_CREATE " );
#endif
break ;
case IRP_MJ_CLOSE:
#ifdef DEBUGMSG
DbgPrint( " IRP_MJ_CLOSE " );
#endif
break ;
case IRP_MJ_DEVICE_CONTROL:
#ifdef DEBUGMSG
DbgPrint( " IRP_MJ_DEVICE_CONTROL " );
#endif
// 取得I/O控制代码
IoControlCodes = IrpStack -> Parameters.DeviceIoControl.IoControlCode;
switch (IoControlCodes)
{
// 启动
case START_HELLPWORLD:
DbgPrint( " Starting "Hello World" " );
break ;
// 停止
case STOP_HELLPWORLD:
DbgPrint( " Stoping "Hello World" " );
break ;
default :
pIrp -> IoStatus.Status = STATUS_INVALID_PARAMETER;
break ;
}
break ;
default : break ;
}
ntStatus = pIrp -> IoStatus.Status;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return ntStatus;
}
VOID HelloWorldUnLoad (IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING DeviceLinkString;
PDEVICE_OBJECT DeviceObjectTemp1 = NULL;
PDEVICE_OBJECT DeviceObjectTemp2 = NULL;
#ifdef DEBUGMSG
DbgPrint( " Starting HelloWorldUnLoad() " );
#endif
RtlInitUnicodeString( & DeviceLinkString,DOS_DEVICE_NAME);
IoDeleteSymbolicLink( & DeviceLinkString); // 删除符号连接
if (DriverObject)
{
DeviceObjectTemp1 = DriverObject -> DeviceObject;
// 删除设备
while (DeviceObjectTemp1)
{
DeviceObjectTemp2 = DeviceObjectTemp1;
DeviceObjectTemp1 = DeviceObjectTemp1 -> NextDevice;
IoDeleteDevice(DeviceObjectTemp2);
}
}
}
#endif
====================================================================================================
驱动程序的编译需要使用DDK中的build实用程序,它是一个命令行程序,使用不是很方便。VC知识库有一篇在VC++ 6.0中编译驱动的文章,有兴趣可以去看看。
1,makefile
编译驱动程序,首先应该准备一个makefile,这个文件很简单,只有一句代码:
#
# DO NOT EDIT THIS FILE!!! Edit ./sources. if you want to add a new source
# file to this component. This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#
!INCLUDE $(NTMAKEENV)/makefile.def
正如描述的那样,不要修改这个文件---它是通用的!
2,sources
准备的第二个文件就是sources,它描述了一些编译的细节。针对本文的程序,sources文件的内容是这样的:
TARGETNAME=HelloWorld //驱动名称
TARGETPATH=. //编译后SYS的路径
TARGETTYPE=DRIVER //类型为驱动程序
SOURCES= HelloWorld.c //只有一个源文件
有了这2个文件后,就可以使用build进行编译了。进入「开始」菜单/程序/Development Kits/Windows 2000 DDK,
分别有3个CMD程序:1)Checked 64 Bit Build Environment,“Debug”的64位版本;2)Checked Build Environment
“Debug”的32位版本;3)Free Build Environment,“Release”的32位版本。不用说,肯定是使用Free Build Environment。
New or updated MSVC detected. Updating DDK environment....
Setting environment for using Microsoft Visual C++ tools.
Starting dirs creation...Completed.
C:/NTDDK>cd/
C:/>cd HelloWorld
C:/HelloWorld>build
BUILD: Object root set to: ==> objfre
BUILD: /i switch ignored
BUILD: Compile and Link for i386
BUILD: Loading c:/NTDDK/build.dat...
BUILD: Computing Include file dependencies:
BUILD: Examining c:/helloworld directory for files to compile.
c:/helloworld - 1 source files (127 lines)
BUILD: Saving c:/NTDDK/build.dat...
BUILD: Compiling c:/helloworld directory
Compiling - helloworld.c for i386
BUILD: Linking c:/helloworld directory
Linking Executable - i386/helloworld.sys for i386
BUILD: Done
1 file compiled
1 executable built
C:/HelloWorld>
现在C:/HelloWorld/i386目录下,就有了HelloWorld.sys。
====================================================================================================
驱动程序的安装如同安装服务一样,唯一不同的是,创建服务时,类型是内核驱动,其他跟操作服务没什么区别。
安装驱动程序流程:
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
这里有一个完整的驱动安装程序,所以我就不写了,只给出操作驱动程序的代码
完整代码:
#include < windows.h >
#include < winioctl.h >
#include < stdio.h >
#define DEVICE_HELLO_INDEX 0x860
#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 erron GetLastError()
#define MY_DEVICE_NAME "//./HelloWorld"
#define MY_DEVICE_START "-start"
#define MY_DEVICE_STOP "-stop"
BOOL DriverControl (TCHAR * Maik);
void Usage (TCHAR * Paramerter);
int main ( int argc,TCHAR * argv[])
{
if (argc != 2 )
{
Usage(argv[ 0 ]);
return 0 ;
}
if (strcmpi(argv[ 1 ],MY_DEVICE_START) == 0 || strcmpi(argv[ 1 ],MY_DEVICE_STOP) == 0 )
DriverControl(argv[ 1 ]);
else
{
Usage(argv[ 0 ]);
return 0 ;
}
return 0 ;
}
BOOL DriverControl (TCHAR * Maik)
{
HANDLE hDevice = NULL; // 设备句柄
DWORD RetBytes = 0 ;
// 获得设备句柄
hDevice = CreateFile(MY_DEVICE_NAME,GENERIC_READ | GENERIC_WRITE, 0 ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hDevice == INVALID_HANDLE_VALUE)
{
#ifdef DEBUGMSG
printf( " CreateFile() GetLastError reports %d " ,erron);
#endif
return FALSE;
}
// 启动
if (strcmpi(Maik,MY_DEVICE_START) == 0 )
{
// 传递启动的I/O控制代码
if ( ! (DeviceIoControl(hDevice,START_HELLPWORLD,NULL, 0 ,NULL, 0 , & RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf( " DeviceIoControl() GetLastError reports %d " ,erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
// 停止
if (strcmpi(Maik,MY_DEVICE_STOP) == 0 )
{
// 传递停止的I/O控制代码
if ( ! (DeviceIoControl(hDevice,STOP_HELLPWORLD,NULL, 0 ,NULL, 0 , & RetBytes,NULL)))
{
#ifdef DEBUGMSG
printf( " DeviceIoControl() GetLastError reports %d " ,erron);
#endif
CloseHandle(hDevice);
return FALSE;
}
}
if (hDevice)
CloseHandle(hDevice); // 关闭句柄
return TRUE;
}
void Usage (TCHAR * Paramerter)
{
fprintf(stderr, " ============================================================================ "
" 驱动版Hello World "
" 作者:dahubaobao[EST] "
" 主页:[url]www.eviloctal.com[/url] or [url]www.ringz.org[/url] "
" 邮件:[email]dahubaobao@eviloctal.com[/email] "
" OICQ:382690 "
" %s -start 启动 "
" %s -stop 停止 "
" 本程序只是用做代码交流,如有错误,还请多多包含! "
" ============================================================================ "
,Paramerter,Paramerter);
}
头文件HelloWorld.h代码:
#ifndef __HELLOWORLD_H__
#define __HELLOWORLD_H__
#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);
#endif
==========编写好的驱动在安装完驱动服务后,在注册表内的相关信息所在位置为:
从注册表内找驱动的相关信息:
HKEY_LOCAL_MACHINE->
SYSTEM->
CurrentControlSet->
Services->
jwhello(某个具体驱动)