驱动备忘录(1)

时间:2022-12-21 15:27:18

总结一下,避免以后忘记了

驱动程序中函数和变量的分配位置是可以改变的

#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")

#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")

       由上到下,依次代表分页代码,非分页代码,初始化代码(执行完毕后释放)。分页数据,非分页数据,初始化数据

使用KdPrint 而不是 DbgPrint

#if DBG

#define KdPrint(_x_) DbgPrint _x_
#define KdPrintEx(_x_) DbgPrintEx _x_
#define vKdPrintEx(_x_) vDbgPrintEx _x_
#define vKdPrintExWithPrefix(_x_) vDbgPrintExWithPrefix _x_
#define KdBreakPoint() DbgBreakPoint()

#define KdBreakPointWithStatus(s) DbgBreakPointWithStatus(s)

#else

#define KdPrint(_x_)
#define KdPrintEx(_x_)
#define vKdPrintEx(_x_)
#define vKdPrintExWithPrefix(_x_)
#define KdBreakPoint()

#define KdBreakPointWithStatus(s)

#endif // DBG

       很容易发现:KdPrint 在非调试状态下为空,调试状态下打印输出调试信息

加载NT式驱动程序的代码

       设备驱动程序的动态加载主要由服务控制管理程序(Service Control Manager. SCM) 系统组件完成。Windows 服务应用程序遵循服务控制管理器(Service Control Manager),Windows 服务可以在系统启动时加载,用户需要在服务控制平台开启或者关闭服务。程序员可以通过Windows 提供的相关服务函数进行加载或者卸载该服务。

  • 加载和卸载NT 驱动分为4个步骤:
    1. 为NT 驱动创建新的服务
    2. 开启此服务
    3. 关闭此服务。
    4. 删除NT 驱动所创建的所有服务
//1. 打开SCM 管理器函数
SC_HANDLE OpenSCManager(
LPCTSTR lpMachineName, // 计算机名称 NULL --本机
LPCTSTR lpDatabaseName, // SCM数据库名称 NULL---代表使用缺省数据库
DWORD dwDesiredAddress // 使用权限 一般SC_MANAGER_ALL_ACCESS
);

//返回值:失败为NULL

//2. 关闭服务句柄

BOOL CloseServiceHandle(SC_HANDLE hSCObject );

//3. 创建服务
//此函数的作用是创建SCM 管理器的句柄,后面介绍的操作都是基于这个句柄进行的
SC_HANDLE CreateService(
SC_HANDLE hSCManager, // SCM 管理器的句柄
LPCTSTR lpServiceName, // 服务名称
LPCTSTR lpDisplayName, // 服务显示出的名称
DWORD dwDesiredAccess, // 打开权限
DWORD dwServiceType, // 服务类型
DWROD dwErrorControl, // 关于错误处理的代码
LPCTSTR lpBinaryPathName, // 二进制文件的代码
LPDWORD lpLoadOrderGroup, // 用何用户组开启服务
LPCTSTR lpdwTagId, // 输出验证标签
LPCTSTR lpDependencies, // 所依赖的服务的名称
LPCTSTR lpServiceStartName, // 用户账户名称
LPCTSTR lpPassword // 用户口令
);

//lpServiceName 就是在设备管理器中看到的设备名称

//dwDesiredAccess 打开权限,如果没有特殊要求,SERVICE_ALL_ACCESS

//dwServiceType 服务类型:
//SERVICE_FILE_SYSTEM_DRIVER 文件系统的驱动
//SERVICE_KERNEL_DRIVER 普通程序的驱动,

//dwStartType 服务打开的时间:
//SERVICE_AUTO_START 驱动自动加载
//SERVICE_BOOT_START 被system loader 加载,即系统启动前就被启动
//SERVICE_DEMAND_START 按照需要时启动

//dwErrorControl 关于错误处理的代码:
//SERVICE_ERROR_IGNORE 遇到错误全部忽略掉
//SERVICE_ERROR_NORMAL 按照缺省办法处理
//SERVICE_ERROR_CRITICAL 增加对错误处理的校验,并提示出对话框,并且记录错误信息到log 文件中。

//lpBinaryPathName 服务所用的二进制代码,就是编译之后的驱动程序。

4. 打开服务

SC_HANDLE OpenService(
SC_HANDLE hSCManager, // SCM 数据库的句柄
LPCTSTR lpServiceName, // 服务名称
DWORD dwDesiredAccess // 访问权限
);

//hSCManager: SCM 管理器的句柄,也就是 OpenSCManager 打开的句柄
//lpServiceName 已经创建的服务名称
//dwDesiredAccess 打开权限,一般-----//SERVICE_ALL_ACCESS

5. 控制服务

//此函数的作用是对相应的服务,发送控制码,根据不同的控制码操作服务。
BOOL ControlService(
SC_HANDLE hService, // 服务的句柄
DWORD dwControl, // 控制码
LPSERVICE_STATUS lpServiceStatus //返回状态码
);

//hService 服务的句柄,也就是CreateService 创建的句柄,或者OpenService 打开的句柄。

//dwControl 对服务的控制码,此处列出常用的控制码:
//SERVICE_CONTROL_CONTINUE 针对暂停的服务发出继续运行的命令
//SERVICE_CONTROL_PAUSE 针对正运行的服务发出暂停的命令
//SERVICE_CONTROL_STOP 针对运行的服务发出停止的命令。

//dwDesiredAccess 打开权限。如果没有特殊要求,一般设置为SERVICE_ALL_ACCESS

//lpServicveStatus 服务返回的状态码

Ring3与Ring0 交互

  • 驱动与应用程序的通信首先需要通过IoCreateDevice生成一个控制设备对象,设备对象+分发函数基本构成驱动的整个框架。
NTKERNELAPI
NTSTATUS
IoCreateDevice(
__in PDRIVER_OBJECT DriverObject,
__in ULONG DeviceExtensionSize,
__in_opt PUNICODE_STRING DeviceName,
__in DEVICE_TYPE DeviceType, // 设备类型,当制作虚拟设备的时候,选择FILE_DEVICE_UNKNOWN 类型的设备
__in ULONG DeviceCharacteristics,
__in BOOLEAN Exclusive, // 是否独占,是:只能由一个应用程序打开,同一时刻只能打开一个句柄
__out PDEVICE_OBJECT *DeviceObject
);
// 该函数创建的设备具有默认的安全属性,需要UAC,IoCreateDeviceSecure 可以设置权限,
  • 控制设备对象的名字与符号链接
           IoCreateDevice传入的名字为设备名称,Ring3 看不到,需要创建一个“链接名称”。在驱动卸载的时候需要调用IoDeleteSymbolicLink删除对应的链接名称,调用IoDeleteDevice 删除对应的设备。
  • 通过什么交互?
           分发函数是一组用来处理发送给设备对象的请求的函数。开发编写后在驱动加载的时候告诉操作系统。每种Ring3 请求有一个主功能号,如IRP_MJ_CREATE,IRP_MJ_CLOSE或者IRP_MJ_DEVICE_CONTROL等等。我们主要分析IRP_MJ_DEVICE_CONTROL。
  • IRP
           I/O 管理器负责发起 I/O 请求,并且管理这些请求。由一系列内核模式下的例程所组成。无论是对端口的读写、对键盘的访问,还是对磁盘文件的操作都是统一为 IRP (I/O Request Packages) 的请求形式。其中 IRP 包含了对设备操作的重要数据。IRP 被传递到具体设备的驱动程序中,驱动程序负责”完成”这些IRP , 并将完成的状态 按原路返回到用户模式下的应用程序中。
  • 分发函数的函数声明
NTSTATUS 
DispatchDeviceControl(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp);

       pDevObj 代表操作的设备对象。pIrp 代表此次请求

缓冲区读写方式

  • 缓冲的I/O(<=一个页面大小,4KB)。I/O 管理器从非换页内存池中申请一个缓冲区,大小等于调用者的缓冲区大小。优点:线程安全、易用。缺点,只适合少量数据,大数据时由于两次拷贝操作效率低。
    驱动备忘录(1)
// Ring3操作的一个简单的代码片段

NTSTATUS status = STATUS_SUCCESS;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
ULONG ulReadLength = stack->Parameters.Read.Length;// 这里存储的是输入数据的大小

// 完成IRP
//设置IRP完成状态
pIrp->IoStatus.Status = status;

//设置IRP操作了多少字节
pIrp->IoStatus.Information = ulReadLength; // bytes xfered

memset(pIrp->AssociatedIrp.SystemBuffer,0xAA,ulReadLength);// 这里存储的是缓冲区的指针

//处理IRP
IoCompleteRequest( pIrp, IO_NO_INCREMENT );


// Ring3写操作的一个简单的代码片段

NTSTATUS status = STATUS_SUCCESS;
PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
// 获取存储的长度
ULONG ulWriteLength = stack->Parameters.Write.Length;
// 获取存储的偏移量
ULONG ulWriteOffset = (ULONG)stack->Parameters.Write.ByteOffset.QuadPart;

if(ulWriteOffset + ulWriteLength > MAX_FILE_LENGTH)
{
// 如果存储长度+偏移量 > 缓冲区长度,则返回无效。
status = STATUS_FILE_INVALID;
ulWriteLength = 0;
} else {
// 将写入数据,存储在缓冲区中
memcpy(pDevExt->buffer+ulWriteOffset,pIrp->AssociatedIrp.SystemBuffer/*这里是缓冲区指针*/,ulWriteLength);
status = STATUS_SUCCESS;
if( ulWriteLength + ulWriteOffset > pDevExt->file_Length)
{
pDevExt->file_length = ulWriteLength+ulWirteoffset;
}
}
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = ulWriteLength;
IoCompleteRequest(pIrp , IO_NO_INCREMENT);

return status;
//IOCTL 代码示例
NTSTATUS ControlPassThrough(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{

NTSTATUS Status = STATUS_SUCCESS;
PVOID InputBuffer = NULL;
PVOID OutputBuffer = NULL;
ULONG_PTR InputSize = 0;
ULONG_PTR OutputSize = 0;
ULONG_PTR IoControlCode = 0;
PIO_STACK_LOCATION IrpSp;
IrpSp = IoGetCurrentIrpStackLocation(Irp);

InputBuffer = Irp->AssociatedIrp.SystemBuffer;
OutputBuffer = Irp->AssociatedIrp.SystemBuffer;
InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
OutputSize = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

DbgPrint("InputSize:%d\r\n",InputSize);
DbgPrint("OutputSize:%d\r\n",OutputSize);

switch(IoControlCode)
{
case CTL_SYS:
{
KdPrint(("应用层事件到达"));
if (InputBuffer!=NULL&&InputSize>=4)
{
if (!MmIsAddressValid(OutputBuffer))
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
__try
{
wcscat((WCHAR*)OutputBuffer,L"BufferIO");

Irp->IoStatus.Information = wcslen((WCHAR*)OutputBuffer)*sizeof(WCHAR);
Status = Irp->IoStatus.Status = STATUS_SUCCESS;
DbgPrint("RetuenSize:%d\r\n",Irp->IoStatus.Information);

break;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Irp->IoStatus.Information = 0;
Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;

break;
}
}
Irp->IoStatus.Information = 0;
Status = Irp->IoStatus.Status = STATUS_SUCCESS;
break;

}
default:
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
}
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return Status;
}
  • 直接I/O。
           直接方式读写设备,操作系统会将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再次映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理内存。无论操作系统如何切换进程,内核模式的地址不变。操作系统先将用户模式的地址锁定后,操作系统用内存描述符表(MDL 数据结构)记录这段内存。用户模式的这段缓冲区在虚拟内存上是连续的,但是在物理内存上可能是非连续的。
           直接模式同样也可以避免驱动程序不安全的访问用户模式的内存地址。在调用DeviceIoControl时,输入缓冲区的内容被复制到Irp中的Irp->AssociatedIrp.SystemBuffer内存地址上,复制的字节数是按照DeviceIoControl指定输入字节数。但是对于DeviceIoControl指定的输出缓冲区的处理是,操作系统会将DeviceIoControl指定的输出缓冲区锁定,然后再内核模式的地址上重新映射一段地址。派遣函数中的IRP结构中的Irp->MdlAddress记录着DeviceIoControl指定的输出缓冲区。派遣函数应该使用MmGetSystemAddressForMdlSafe将这段内存映射到内核模式下的内存地址上。
    驱动备忘录(1)
           MDL 记录这段虚拟内存
           大小存储在mdl->ByteCount 中
           第一个页地址是mdl->StartVa
           首地址对于第一个页地址的偏移量是mdl->ByteOffset
           首地址应该是mdl->StartVa + mdl->ByteOffset.
           DDK 提供了几个宏方便程序员知道这几个数值:
    // 对于R/W例程
pDevObj->Flags |= DO_DIRECT_IO;

// 宏定义
#define MmGetMdlByteCount(Mdl) ((Mdl)->ByteCount)
#define MmGetMdlByteOffset(Mdl) ((Mdl)->ByteOffset)
#define MmGetMdlVirtualAddress(Mdl) ((PVOID) ((PCHAR)((Mdl)->StartVa) + (Mdl)->ByteOffset))

// Ring0 读操作代码示例
//stack->Parameters.Read.Length 对应 nNumbferOfBytesToRead
//通过IRP 的pIrp->MdlAddress 得到MDL 数据结构,这个结构描述了被锁定的缓冲区内存

//如果派遣函数的返回值是STATUS_SUCCESS 则ReadFile 返回TRUE,表明读操作成功。如果派遣函数返回的不是STATUS_SUCCESS .ReadFile 返回FALSE.派遣函数的返回值和GetLastError 对应。

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{


PVOID kernel_address = NULL;
NTSTATUS status = STATUS_SUCCESS;
ULONG ulReadLength = 0;
ULONG mdl_length =0;
PVOID mdl_address = 0;
ULONG mdl_offset = 0;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

KdPrint(("Enter HelloDDKRead\n"));

ulReadLength = stack->Parameters.Read.Length;
KdPrint(("ulReadLength:%d\n",ulReadLength));

mdl_length = MmGetMdlByteCount(pIrp->MdlAddress);
mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress);
mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress);

KdPrint(("mdl_address:0X%08X\n",mdl_address));
KdPrint(("mdl_length:%d\n",mdl_length));
KdPrint(("mdl_offset:%d\n",mdl_offset));

if (mdl_length!=ulReadLength)
{
//MDL的长度应该和读长度相等,否则该操作应该设为不成功
pIrp->IoStatus.Information = 0;
status = STATUS_UNSUCCESSFUL;
}else
{
//用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射
kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
KdPrint(("kernel_address:0X%X\n",kernel_address));
//DbgBreakPoint();
strcpy((char*)kernel_address,"Hello,World");
pIrp->IoStatus.Information = strlen((char*)kernel_address); // bytes xfered
}

pIrp->IoStatus.Status = status;

IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKRead\n"));
return status;
}


// Ring3 写操作代码示例


NTSTATUS HelloDDKWrite(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
PVOID kernel_address = NULL;
NTSTATUS status = STATUS_SUCCESS;
ULONG ulReadLength = 0;
ULONG mdl_length =0;
PVOID mdl_address = 0;
ULONG mdl_offset = 0;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
KdPrint(("Enter HelloDDKWrite\n"));

ulReadLength = stack->Parameters.Write.Length;
KdPrint(("ulReadLength:%d\n",ulReadLength));

mdl_length = MmGetMdlByteCount(pIrp->MdlAddress);
mdl_address = MmGetMdlVirtualAddress(pIrp->MdlAddress);
mdl_offset = MmGetMdlByteOffset(pIrp->MdlAddress);

KdPrint(("mdl_address:0X%08X\n",mdl_address));
KdPrint(("mdl_length:%d\n",mdl_length));
KdPrint(("mdl_offset:%d\n",mdl_offset));

if (mdl_length!=ulReadLength)
{
//MDL的长度应该和读长度相等,否则该操作应该设为不成功
pIrp->IoStatus.Information = 0;
status = STATUS_UNSUCCESSFUL;
}else
{
//用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射
kernel_address = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress,NormalPagePriority);
KdPrint(("kernel_address:0X%X\n",kernel_address));
//DbgBreakPoint();
KdPrint(("Write Code:\t%s\n",kernel_address));
pIrp->IoStatus.Information = ulReadLength; // bytes xfered
}

pIrp->IoStatus.Status = status;

IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKWrite\n"));

return status;
}
// IOCTL

NTSTATUS
ControlPassThrough(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{

NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION IrpSp;
PVOID InputBuffer = NULL;
PVOID OutputBuffer = NULL;
ULONG_PTR InputSize = 0;
ULONG_PTR OutputSize = 0;
ULONG_PTR IoControlCode = 0;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
InputBuffer = Irp->AssociatedIrp.SystemBuffer;
InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
OutputSize = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
DbgPrint("InputSize:%d\r\n",InputSize);
DbgPrint("OutputSize:%d\r\n",OutputSize);
switch(IoControlCode)
{
case IOCTL_TEST_IN_DIRECT:
{
DbgBreakPoint();
if (InputBuffer!=NULL && InputSize>=4)
{
if (Irp->MdlAddress==NULL)
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
OutputBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);
if (!MmIsAddressValid(OutputBuffer))
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
__try
{
((char*)OutputBuffer)[InputSize] = 0;
while(InputSize -- )
{

((char*)OutputBuffer)
[InputSize-1] = ((char*)InputBuffer)[InputSize-1] + 1;
}

Irp->IoStatus.Information = strlen((CHAR*)OutputBuffer);
//Irp->IoStatus.Information = 0; //这里对于数据传递无任何影响 影响的是应用层DeviceIoControl函数的第七个参数值 DbgPrint("ReturnSize:%d\r\n",Irp->IoStatus.Information);

Status = Irp->IoStatus.Status = STATUS_SUCCESS;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Irp->IoStatus.Information = 0;
Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
break;
}
}
default:
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
}
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return Status;
}
  • 两者皆非I/O。
           缓冲区管理的任务交给了驱动程序自己管理。在用CTL_CODE宏定义这种IOCTL时,应该指定Method参数为METHOD_NEITHER。这种方式的IOCTL因为要直接访问用户模式地址。使用用户模式地址必须保证调用DeviceIOControl的线程与派遣函数运行在同一个线程上下文中。输入缓冲区的地址,IrpSp->Parameters.DeviceIoControl.Type3InputBuffer得到。输出缓冲区的地址,由Irp的Irp->UserBuffer得到。由于驱动程序的派遣函数不能保证传递进来的用户地址是合法地址,所以最好对传入的用户模式地址进行可读写判断。一般通过ProbeForRead或者ProbeWrite函数。
           在两者皆非读写中,缓冲区内存地址 pIrp->UserBuffer。大小:stack->Parameters.Read.Length或者stack->Parameters.Write.Length
    驱动备忘录(1)
// IOCTL
NTSTATUS
ControlPassThrough(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{

NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION IrpSp;
PVOID InputBuffer = NULL;
PVOID OutputBuffer = NULL;
ULONG_PTR InputSize = 0;
ULONG_PTR OutputSize = 0;
ULONG_PTR IoControlCode = 0;
IrpSp = IoGetCurrentIrpStackLocation(Irp);

InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
OutputBuffer = Irp->UserBuffer;
InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
OutputSize = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;

IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;
switch(IoControlCode)
{
case IOCTL_TEST_NEITHER:
{
if (InputBuffer!=NULL&&InputSize>=4)
{
if (OutputBuffer==NULL)
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
if (!MmIsAddressValid(OutputBuffer))
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
__try
{
ProbeForWrite(OutputBuffer,OutputSize,sizeof(PVOID));
ProbeForRead(InputBuffer,InputSize,sizeof(PVOID));
((char*)OutputBuffer)[InputSize] = 0;
while(InputSize -- )
{

((char*)OutputBuffer)[InputSize-1] = ((char*)InputBuffer)[InputSize-1] + 1;
}

Irp->IoStatus.Information = strlen((CHAR*)OutputBuffer);
//Irp->IoStatus.Information = 0; //这里对于数据传递无任何影响 影响的是应用层DeviceIoControl函数的第七个参数值 DbgPrint("ReturnSize:%d\r\n",Irp->IoStatus.Information);

}
__except(EXCEPTION_EXECUTE_HANDLER)
{
Irp->IoStatus.Information = 0;
Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
}
break;
}
DbgPrint("应用层事件到达");
Irp->IoStatus.Information = 0;
Status = Irp->IoStatus.Status = STATUS_SUCCESS;
break;
}
default:
{
Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
Irp->IoStatus.Information = 0;
break;
}
}
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return Status;
}
//R/W 操作

NTSTATUS HelloDDKWrite(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
PVOID user_address = NULL;
NTSTATUS status = STATUS_SUCCESS;
ULONG ulWriteLength = 0;
ULONG ulWriteOffset = 0;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
KdPrint(("Enter HelloDDKWrite\n"));

ulWriteLength = stack->Parameters.Write.Length;
ulWriteOffset = (LONG)stack->Parameters.Write.ByteOffset.QuadPart;
user_address = pIrp->UserBuffer;

KdPrint(("ulWriteLength:%d\n",ulWriteLength));
KdPrint(("ulWriteOffset:%d\n",ulWriteOffset));
KdPrint(("user_address:%p\n",user_address));

__try
{
KdPrint(("Enter __try block\n"));
//判断空指针是否可写,显然会导致异常
ProbeForRead(user_address,ulWriteLength,4);

KdPrint(("UerData:\t%s\n"),(char*)user_address);

//由于在上面引发异常,所以以后语句不会被执行!
KdPrint(("Leave __try block\n"));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("Catch the exception\n"));
KdPrint(("The program will keep going\n"));
status = STATUS_UNSUCCESSFUL;
}

pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = strlen((char*)user_address); // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKWrite\n"));
return status;
}

NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
IN PIRP pIrp)
{
PVOID user_address = NULL;
NTSTATUS status = STATUS_SUCCESS;
ULONG ulReadLength = 0;
ULONG ulReadOffset = 0;

PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);

KdPrint(("Enter HelloDDKRead\n"));

ulReadLength = stack->Parameters.Read.Length;
ulReadOffset = (LONG)stack->Parameters.Read.ByteOffset.QuadPart;
user_address = pIrp->UserBuffer;

KdPrint(("ulReadLength:%d\n",ulReadLength));
KdPrint(("ulReadOffset:%d\n",ulReadOffset));
KdPrint(("user_address:%p\n",user_address));

__try
{
KdPrint(("Enter __try block\n"));
//判断空指针是否可写,显然会导致异常
ProbeForWrite(user_address,ulReadLength,4);

strcpy((char*)user_address,"Hello,World");

//由于在上面引发异常,所以以后语句不会被执行!
KdPrint(("Leave __try block\n"));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("Catch the exception\n"));
KdPrint(("The program will keep going\n"));
status = STATUS_UNSUCCESSFUL;
}

pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = strlen((char*)user_address); // bytes xfered
IoCompleteRequest( pIrp, IO_NO_INCREMENT );
KdPrint(("Leave HelloDDKRead\n"));
return status;
}

       驱动程序针对读和写操作,在代表目标设备的设备对象中登记了它所期望的缓冲区管理类型(创建设备对象的时候已经指定了 设备对象->Flags |= DO_BUFFERED_IO;//这个Flags可以控制R/W 操作的方式)。而IRP_MJ_DEVICE_CONTROL 中不同的子控制码中可以指定不同的I/O 控制方式

设备扩展的使用

// IoCreateDevice 中第二个参数传入扩展数据的大小
IoCreateDevice( pDriverObject,
sizeof(DEVICE_EXTENSION)...);

//今后的设备对象的使用过程中:PDEVICE_OBJECT->DeviceExtension 可以得到这块内存,相当于扩展了每个设备对象的特有的变量,比如:创建设备的时候保存设备符号链接名到扩展内存中,卸载驱动的时候从设备->扩展内存->符号连接名来删除符号链接
  • 一个加载与卸载驱动的例子程序:来自windows驱动开发技术详解
#include <windows.h> 
#include <winsvc.h>
#include <conio.h>
#include <stdio.h>

#define DRIVER_NAME "HelloDDK"
#define DRIVER_PATH "..\\MyDriver\\MyDriver_Check\\HelloDDK.sys"

//装载NT驱动程序
BOOL LoadNTDriver(char* lpszDriverName,char* lpszDriverPath)
{
char szDriverImagePath[256];
//得到完整的驱动路径
GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);

BOOL bRet = FALSE;

SC_HANDLE hServiceMgr=NULL;//SCM管理器的句柄
SC_HANDLE hServiceDDK=NULL;//NT驱动程序的服务句柄

//打开服务控制管理器
hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );

if( hServiceMgr == NULL )
{
//OpenSCManager失败
printf( "OpenSCManager() Faild %d ! \n", GetLastError() );
bRet = FALSE;
goto BeforeLeave;
}
else
{
////OpenSCManager成功
printf( "OpenSCManager() ok ! \n" );
}

//创建驱动所对应的服务
hServiceDDK = CreateService( hServiceMgr,
lpszDriverName, //驱动程序的在注册表中的名字
lpszDriverName, // 注册表驱动程序的 DisplayName 值
SERVICE_ALL_ACCESS, // 加载驱动程序的访问权限
SERVICE_KERNEL_DRIVER,// 表示加载的服务是驱动程序
SERVICE_DEMAND_START, // 注册表驱动程序的 Start 值
SERVICE_ERROR_IGNORE, // 注册表驱动程序的 ErrorControl 值
szDriverImagePath, // 注册表驱动程序的 ImagePath 值
NULL,
NULL,
NULL,
NULL,
NULL);

DWORD dwRtn;
//判断服务是否失败
if( hServiceDDK == NULL )
{
dwRtn = GetLastError();
if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS )
{
//由于其他原因创建服务失败
printf( "CrateService() Faild %d ! \n", dwRtn );
bRet = FALSE;
goto BeforeLeave;
}
else
{
//服务创建失败,是由于服务已经创立过
printf( "CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n" );
}

// 驱动程序已经加载,只需要打开
hServiceDDK = OpenService( hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS );
if( hServiceDDK == NULL )
{
//如果打开服务也失败,则意味错误
dwRtn = GetLastError();
printf( "OpenService() Faild %d ! \n", dwRtn );
bRet = FALSE;
goto BeforeLeave;
}
else
{
printf( "OpenService() ok ! \n" );
}
}
else
{
printf( "CrateService() ok ! \n" );
}

//开启此项服务
bRet= StartService( hServiceDDK, NULL, NULL );
if( !bRet )
{
DWORD dwRtn = GetLastError();
if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING )
{
printf( "StartService() Faild %d ! \n", dwRtn );
bRet = FALSE;
goto BeforeLeave;
}
else
{
if( dwRtn == ERROR_IO_PENDING )
{
//设备被挂住
printf( "StartService() Faild ERROR_IO_PENDING ! \n");
bRet = FALSE;
goto BeforeLeave;
}
else
{
//服务已经开启
printf( "StartService() Faild ERROR_SERVICE_ALREADY_RUNNING ! \n");
bRet = TRUE;
goto BeforeLeave;
}
}
}
bRet = TRUE;
//离开前关闭句柄
BeforeLeave:
if(hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if(hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
return bRet;
}

//卸载驱动程序
BOOL UnloadNTDriver( char * szSvrName )
{
BOOL bRet = FALSE;
SC_HANDLE hServiceMgr=NULL;//SCM管理器的句柄
SC_HANDLE hServiceDDK=NULL;//NT驱动程序的服务句柄
SERVICE_STATUS SvrSta;
//打开SCM管理器
hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
if( hServiceMgr == NULL )
{
//带开SCM管理器失败
printf( "OpenSCManager() Faild %d ! \n", GetLastError() );
bRet = FALSE;
goto BeforeLeave;
}
else
{
//带开SCM管理器失败成功
printf( "OpenSCManager() ok ! \n" );
}
//打开驱动所对应的服务
hServiceDDK = OpenService( hServiceMgr, szSvrName, SERVICE_ALL_ACCESS );

if( hServiceDDK == NULL )
{
//打开驱动所对应的服务失败
printf( "OpenService() Faild %d ! \n", GetLastError() );
bRet = FALSE;
goto BeforeLeave;
}
else
{
printf( "OpenService() ok ! \n" );
}
//停止驱动程序,如果停止失败,只有重新启动才能,再动态加载。
if( !ControlService( hServiceDDK, SERVICE_CONTROL_STOP , &SvrSta ) )
{
printf( "ControlService() Faild %d !\n", GetLastError() );
}
else
{
//打开驱动所对应的失败
printf( "ControlService() ok !\n" );
}
//动态卸载驱动程序。
if( !DeleteService( hServiceDDK ) )
{
//卸载失败
printf( "DeleteSrevice() Faild %d !\n", GetLastError() );
}
else
{
//卸载成功
printf( "DelServer:eleteSrevice() ok !\n" );
}
bRet = TRUE;
BeforeLeave:
//离开前关闭打开的句柄
if(hServiceDDK)
{
CloseServiceHandle(hServiceDDK);
}
if(hServiceMgr)
{
CloseServiceHandle(hServiceMgr);
}
return bRet;
}

void TestDriver()
{
//测试驱动程序
HANDLE hDevice = CreateFile("\\\\.\\HelloDDK",
GENERIC_WRITE | GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
0,
NULL);
if( hDevice != INVALID_HANDLE_VALUE )
{
printf( "Create Device ok ! \n" );
}
else
{
printf( "Create Device faild %d ! \n", GetLastError() );
}
CloseHandle( hDevice );
}

int main(int argc, char* argv[])
{
//加载驱动
BOOL bRet = LoadNTDriver(DRIVER_NAME,DRIVER_PATH);
if (!bRet)
{
printf("LoadNTDriver error\n");
return 0;
}
//加载成功

printf( "press any to create device!\n" );
getch();

TestDriver();

//这时候你可以通过注册表,或其他查看符号连接的软件验证。
printf( "press any to unload the driver!\n" );
getch();

//卸载驱动
UnloadNTDriver(DRIVER_NAME);
if (!bRet)
{
printf("UnloadNTDriver error\n");
return 0;
}

return 0;
}