您的位置:首页 > 其它

MSDN系列(12)--KMD(Kernel Mode Driver)的编写安装与调试

2006-02-22 14:43 489 查看
标题: MSDN系列(12)--KMD(Kernel Mode Driver)的编写安装与调试

日期: 2004-07-06 20:17
更新: 2004-07-12 14:47

--------------------------------------------------------------------------

☆ 概述
☆ 编写loopback.c文件
1) KdPrint
2) 内部设备名、外部设备名
3) 池(Pool)
4) CRT/RTL
5) Unload
6) UNICODE_STRING
7) 设备扩展
8) IRP
9) IoCompleteRequest
10) DRIVER_OBJECT/DEVICE_OBJECT
11) 内核态的结构化异常处理
12) 丢弃初始化例程、控制驱动程序分页
☆ 编写dirs文件
☆ 编写sources文件
☆ 编写makefile文件
☆ 编译产生loopback.sys文件
☆ 编写loopback.inf文件
☆ 安装loopback.inf文件
☆ 卸载loopback.sys及相关设置
☆ installdriver.c
☆ 察看KdPrint输出
☆ loopbacktest.c
☆ 用SoftICE对loopback.sys进行源码级调试
☆ 后记
☆ 参考资源

--------------------------------------------------------------------------

☆ 概述

在<<MSDN系列(2)--学习写一个Hello World驱动>>中已经介绍过编写驱动的基本步骤。
当时那个hello.sys连个框架都算不上,这次的loopback.sys则是一个可加载、卸载
的框架。本文试图记录KMD(Kernel Mode Driver)的编写安装与调试过程,采用纯DDK
编程,以减少第三方工具封装后带来的困挠。

我的最终目的不是写硬件驱动,能摸到NDIS边上即可。更多时候可能仅仅是想利用驱
动进入Ring 0做一些实验或其他什么非常规主流动作。因此最普通的KMD足够了。现
在流行的驱动框架对我无用,不做深究,在下非学院派,只折腾能为我所用的东西,
也只在用到的时候才去折腾。

☆ 编写loopback.c文件

--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7 & Windows DDK 2600.1106
* build -cZ -x86
*/

/************************************************************************
*                                                                      *
*                               Head File                              *
*                                                                      *
************************************************************************/

/*
* #include <wdm.h>
*/
#include <NTDDK.h>

/************************************************************************
*                                                                      *
*                               Macro                                  *
*                                                                      *
************************************************************************/

/*
* 后面要追加DeviceNumber
*/
#define INTERNALNAME    L"//Device//LoopbackInternal"
/*
* DosDevices
*/
#define EXTERNALNAME    L"//??//LoopbackExternal"
/*
* 参ExAllocatePoolWithTag第三形参的DDK文档
*/
#define PRIVATETAG      'OFSN'

/*
* 设备扩展是自定义结构
*/
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT  DeviceObject;
ULONG           DeviceNumber;
PVOID           DeviceBuffer;
ULONG           DeviceBufferSize;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

/************************************************************************
*                                                                      *
*                            Function Prototype                        *
*                                                                      *
************************************************************************/

/*
* 非Dispatch函数
*/
static NTSTATUS LoCreateDevice
(
IN  PDRIVER_OBJECT  DriverObject,
IN  ULONG           DeviceNumber
);
/*
* 非Dispatch函数
*/
static VOID     LoDeleteDevice
(
IN  PDEVICE_OBJECT  DeviceObject
);
/*
* 在DDK文档中搜索DispatchClose
*/
static NTSTATUS LoDispatchClose
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
);
/*
* 在DDK文档中搜索DispatchCreate
*/
static NTSTATUS LoDispatchCreate
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
);
/*
* 在DDK文档中搜索DispatchRead
*/
static NTSTATUS LoDispatchRead
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
);
/*
* 在DDK文档中搜索DispatchWrite
*/
static NTSTATUS LoDispatchWrite
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
);
/*
* 非Dispatch函数,在DDK文档中搜索Unload
*/
static VOID     LoDriverUnload
(
IN  PDRIVER_OBJECT  DriverObject
);
NTSTATUS DriverEntry
(
IN  PDRIVER_OBJECT  DriverObject,
IN  PUNICODE_STRING RegistryPath
);

/*
* 参看<<The Windows 2000 Device Driver Book, Second Edition>>中5.2.6、
* 5.2.7小节。INIT、PAGE应该是大小写敏感的,可我居然看到过init、page,不清
* 楚怎么回事,稳妥起见还是用INIT、PAGE算了。
*/
#ifdef ALLOC_PRAGMA

#pragma alloc_text( INIT, LoCreateDevice    )
#pragma alloc_text( PAGE, LoDeleteDevice    )
#pragma alloc_text( PAGE, LoDispatchClose   )
#pragma alloc_text( PAGE, LoDispatchCreate  )
#pragma alloc_text( PAGE, LoDispatchRead    )
#pragma alloc_text( PAGE, LoDispatchWrite   )
#pragma alloc_text( PAGE, LoDriverUnload    )
#pragma alloc_text( INIT, DriverEntry       )

#endif

/************************************************************************
*                                                                      *
*                            Static Global Var                         *
*                                                                      *
************************************************************************/

/************************************************************************/

/*
* 非Dispatch函数。如果不做错误处理,函数将精简很多,实际上就是调用两个函
* 数,IoCreateDevice与IoCreateSymbolicLink,分别建立内部设备名、外部设备
* 名。
*/
static NTSTATUS LoCreateDevice
(
IN  PDRIVER_OBJECT  DriverObject,
IN  ULONG           DeviceNumber
)
{
NTSTATUS            status;
PDEVICE_OBJECT      DeviceObject;
PDEVICE_EXTENSION   DeviceExtension;
UNICODE_STRING      DeviceName;
UNICODE_STRING      SymbolicLinkName;
UNICODE_STRING      NumberUnicodeString;
UNICODE_STRING      InternalNameUnicodeString;
UNICODE_STRING      ExternalNameUnicodeString;

KdPrint((  "Entering LoCreateDevice()/n" ));
/*
* If the string is NULL-terminated, Length does not include the
* trailing NULL.
*/
NumberUnicodeString.Length              = 0;
/*
* 0xFFFFFFFF转换成10进制是4294967295,最长10个字符,加上结尾的NUL,不
* 超过11。
*/
NumberUnicodeString.MaximumLength       = 16;
/*
* PVOID ExAllocatePoolWithTag
* (
*     IN  POOL_TYPE   PoolType,
*     IN  SIZE_T      NumberOfBytes,
*     IN  ULONG       Tag
* );
*
* 这里分配了内存,记得在后面释放它。
*/
NumberUnicodeString.Buffer              = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
NumberUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == NumberUnicodeString.Buffer )
{
/*
* DDK文档中指出,如果ExAllocatePool返回NULL,主调者应该返回
* STATUS_INSUFFICIENT_RESOURCES。但是DDK文档中没有指出
* ExAllocatePoolWithTag返回NULL时该如何,我只好类比一下,也返回
* STATUS_INSUFFICIENT_RESOURCES。不知这里是否可以返回
* STATUS_NO_MEMORY。
*/
return( STATUS_INSUFFICIENT_RESOURCES );
}
/*
* NTSTATUS RtlIntegerToUnicodeString
* (
*     IN      ULONG           Value,
*     IN      ULONG           Base    OPTIONAL,
*     IN OUT  PUNICODE_STRING String
* );
*
* converts an unsigned integer value to a NULL-terminated string of one
* or more Unicode characters in the specified base.
*/
status                                  = RtlIntegerToUnicodeString
(
DeviceNumber,
10,
&NumberUnicodeString
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
return( status );
}
/*
* VOID RtlInitUnicodeString
* (
*     IN OUT  PUNICODE_STRING DestinationString,
*     IN      PCWSTR          SourceString
* );
*
* 这个函数没有动态分配内存
*/
RtlInitUnicodeString( &DeviceName, INTERNALNAME );
/*
* 在后面追加DeviceNumber。没有wsprintf()可用,不得已,只好这样变态地
* 处理。
*/
InternalNameUnicodeString.Length        = DeviceName.Length + NumberUnicodeString.Length;
InternalNameUnicodeString.MaximumLength = InternalNameUnicodeString.Length + 2;
InternalNameUnicodeString.Buffer        = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
InternalNameUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == InternalNameUnicodeString.Buffer )
{
/*
* NTKERNELAPI VOID ExFreePoolWithTag
* (
*     IN  PVOID   P,
*     IN  ULONG   Tag
* );
*
* 需要释放NumberUnicodeString.Buffer
*/
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
/*
* VOID RtlZeroMemory
* (
*     IN  VOID UNALIGNED *Destination,
*     IN  SIZE_T          Length
* );
*/
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
return( STATUS_INSUFFICIENT_RESOURCES );
}
/*
* VOID RtlCopyUnicodeString
* (
*     IN OUT  PUNICODE_STRING DestinationString,
*     IN      PUNICODE_STRING SourceString
* );
*/
RtlCopyUnicodeString
(
&InternalNameUnicodeString,
&DeviceName
);
/*
* NTSTATUS RtlAppendUnicodeStringToString
* (
*     IN OUT  PUNICODE_STRING Destination,
*     IN      PUNICODE_STRING Source
* );
*/
status                                  = RtlAppendUnicodeStringToString
(
&InternalNameUnicodeString,
&NumberUnicodeString
);
/*
* 已经不需要NumberUnicodeString.Buffer,趁早释放它。
*/
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
return( status );
}
InternalNameUnicodeString.Buffer[ InternalNameUnicodeString.Length / 2 ]
= UNICODE_NULL;
/*
* NTSTATUS IoCreateDevice
* (
*     IN  PDRIVER_OBJECT      DriverObject,
*     IN  ULONG               DeviceExtensionSize,
*     IN  PUNICODE_STRING     DeviceName  OPTIONAL,
*     IN  DEVICE_TYPE         DeviceType,
*     IN  ULONG               DeviceCharacteristics,
*     IN  BOOLEAN             Exclusive,
*     OUT PDEVICE_OBJECT     *DeviceObject
* );
*/
status                                  = IoCreateDevice
(
DriverObject,
sizeof( DEVICE_EXTENSION ),
&InternalNameUnicodeString,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
return( status );
}
DeviceObject->Flags                    |= DO_BUFFERED_IO;
/*
* Initialize the Device Extension
*
* 设备扩展的内存空间是由IoCreateDevice给予的
*/
DeviceExtension                         = ( PDEVICE_EXTENSION )DeviceObject->DeviceExtension;
DeviceExtension->DeviceObject           = DeviceObject;
DeviceExtension->DeviceNumber           = DeviceNumber;
DeviceExtension->DeviceBuffer           = NULL;
DeviceExtension->DeviceBufferSize       = 0;
/*
* 下面开始处理SymbolicLink
*/
NumberUnicodeString.Length              = 0;
NumberUnicodeString.MaximumLength       = 16;
/*
* 这里分配了内存,记得在后面释放它。
*/
NumberUnicodeString.Buffer              = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
NumberUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == NumberUnicodeString.Buffer )
{
IoDeleteDevice( DeviceObject );
DeviceObject    = NULL;
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
return( STATUS_INSUFFICIENT_RESOURCES );
}
/*
* 一般内部设备号从0计,外部设备号从1计
*/
status                                  = RtlIntegerToUnicodeString
(
DeviceNumber + 1,
10,
&NumberUnicodeString
);
if ( !NT_SUCCESS( status ) )
{
IoDeleteDevice( DeviceObject );
DeviceObject    = NULL;
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
return( status );
}
RtlInitUnicodeString( &SymbolicLinkName, EXTERNALNAME );
ExternalNameUnicodeString.Length        = SymbolicLinkName.Length + NumberUnicodeString.Length;
ExternalNameUnicodeString.MaximumLength = ExternalNameUnicodeString.Length + 2;
ExternalNameUnicodeString.Buffer        = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
ExternalNameUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == ExternalNameUnicodeString.Buffer )
{
/*
* VOID IoDeleteDevice
* (
*     IN  PDEVICE_OBJECT  DeviceObject
* );
*
* 需要抵消IoCreateDevice
*/
IoDeleteDevice( DeviceObject );
DeviceObject    = NULL;
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
return( STATUS_INSUFFICIENT_RESOURCES );
}
/*
* VOID RtlCopyUnicodeString
* (
*     IN OUT  PUNICODE_STRING DestinationString,
*     IN      PUNICODE_STRING SourceString
* );
*/
RtlCopyUnicodeString
(
&ExternalNameUnicodeString,
&SymbolicLinkName
);
status                                  = RtlAppendUnicodeStringToString
(
&ExternalNameUnicodeString,
&NumberUnicodeString
);
/*
* 已经不需要NumberUnicodeString.Buffer,趁早释放它。
*/
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
ExternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&ExternalNameUnicodeString,
sizeof( ExternalNameUnicodeString )
);
IoDeleteDevice( DeviceObject );
DeviceObject    = NULL;
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
return( status );
}
ExternalNameUnicodeString.Buffer[ ExternalNameUnicodeString.Length / 2 ]
= UNICODE_NULL;
/*
* NTSTATUS IoCreateSymbolicLink
* (
*     IN  PUNICODE_STRING SymbolicLinkName,
*     IN  PUNICODE_STRING DeviceName
* );
*/
status                                  = IoCreateSymbolicLink
(
&ExternalNameUnicodeString,
&InternalNameUnicodeString
);
/*
* 已经不需要InternalNameUnicodeString.Buffer、ExternalNameUnicodeString.Buffer
*/
ExFreePoolWithTag
(
ExternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&ExternalNameUnicodeString,
sizeof( ExternalNameUnicodeString )
);
ExFreePoolWithTag
(
InternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&InternalNameUnicodeString,
sizeof( InternalNameUnicodeString )
);
if ( !NT_SUCCESS( status ) )
{
IoDeleteDevice( DeviceObject );
DeviceObject    = NULL;
}
return( STATUS_SUCCESS );
}  /* end of LoCreateDevice */

/*
* 非Dispatch函数。实际上就是调用两个函数,IoDeleteSymbolicLink与
* IoDeleteDevice。
*/
static VOID LoDeleteDevice
(
IN  PDEVICE_OBJECT  DeviceObject
)
{
NTSTATUS            status;
PDEVICE_EXTENSION   DeviceExtension;
UNICODE_STRING      SymbolicLinkName;
UNICODE_STRING      NumberUnicodeString;
UNICODE_STRING      ExternalNameUnicodeString;

KdPrint((  "Entering LoDeleteDevice()/n" ));
DeviceExtension                         = ( PDEVICE_EXTENSION )DeviceObject->DeviceExtension;
NumberUnicodeString.Length              = 0;
NumberUnicodeString.MaximumLength       = 16;
/*
* 这里分配了内存,记得在后面释放它。
*/
NumberUnicodeString.Buffer              = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
NumberUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == NumberUnicodeString.Buffer )
{
/*
* 考虑输出一些调试信息
*
* This routine is defined in ntddk.h, wdm.h, and ndis.h.
* A call to this macro requires double parentheses.
*/
KdPrint((  "ExAllocatePoolWithTag() for NumberUnicodeString.Buffer failed/n" ));
return;
}
/*
* 一般内部设备号从0计,外部设备号从1计
*/
status                                  = RtlIntegerToUnicodeString
(
DeviceExtension->DeviceNumber + 1,
10,
&NumberUnicodeString
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
KdPrint((  "RtlIntegerToUnicodeString() failed/n" ));
return;
}
RtlInitUnicodeString( &SymbolicLinkName, EXTERNALNAME );
ExternalNameUnicodeString.Length        = SymbolicLinkName.Length + NumberUnicodeString.Length;
ExternalNameUnicodeString.MaximumLength = ExternalNameUnicodeString.Length + 2;
ExternalNameUnicodeString.Buffer        = ( PWSTR )ExAllocatePoolWithTag
(
PagedPool,
ExternalNameUnicodeString.MaximumLength,
PRIVATETAG
);
if ( NULL == ExternalNameUnicodeString.Buffer )
{
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
KdPrint((  "ExAllocatePoolWithTag() for ExternalNameUnicodeString.Buffer failed/n" ));
return;
}
/*
* VOID RtlCopyUnicodeString
* (
*     IN OUT  PUNICODE_STRING DestinationString,
*     IN      PUNICODE_STRING SourceString
* );
*/
RtlCopyUnicodeString
(
&ExternalNameUnicodeString,
&SymbolicLinkName
);
status                                  = RtlAppendUnicodeStringToString
(
&ExternalNameUnicodeString,
&NumberUnicodeString
);
/*
* 已经不需要NumberUnicodeString.Buffer,趁早释放它。
*/
ExFreePoolWithTag
(
NumberUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&NumberUnicodeString,
sizeof( NumberUnicodeString )
);
if ( !NT_SUCCESS( status ) )
{
ExFreePoolWithTag
(
ExternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&ExternalNameUnicodeString,
sizeof( ExternalNameUnicodeString )
);
KdPrint((  "RtlAppendUnicodeStringToString() failed/n" ));
return;
}
ExternalNameUnicodeString.Buffer[ ExternalNameUnicodeString.Length / 2 ]
= UNICODE_NULL;
/*
* NTSTATUS IoDeleteSymbolicLink
* (
*     IN  PUNICODE_STRING SymbolicLinkName
* );
*/
status                                  = IoDeleteSymbolicLink
(
&ExternalNameUnicodeString
);
/*
* 已经不需要ExternalNameUnicodeString.Buffer
*/
ExFreePoolWithTag
(
ExternalNameUnicodeString.Buffer,
PRIVATETAG
);
RtlZeroMemory
(
&ExternalNameUnicodeString,
sizeof( ExternalNameUnicodeString )
);
if ( !NT_SUCCESS( status ) )
{
KdPrint((  "IoDeleteSymbolicLink() failed/n" ));
return;
}
/*
* VOID IoDeleteDevice
* (
*     IN  PDEVICE_OBJECT  DeviceObject
* );
*/
IoDeleteDevice( DeviceObject );
return;
}  /* end of LoDeleteDevice */

/*
* Handles call from Win32 CloseHandle request. For loopback driver, frees
* any buffer.
*/
static NTSTATUS LoDispatchClose
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
)
{
/*
* Dig out the Device Extension from the Device Object
*/
PDEVICE_EXTENSION   DeviceExtension;

KdPrint((  "Entering LoDispatchClose()/n" ));
DeviceExtension             = ( PDEVICE_EXTENSION )DeviceObject->DeviceExtension;
if ( DeviceExtension->DeviceBuffer )
{
ExFreePoolWithTag
(
DeviceExtension->DeviceBuffer,
PRIVATETAG
);
DeviceExtension->DeviceBuffer       = NULL;
DeviceExtension->DeviceBufferSize   = 0;
}
Irp->IoStatus.Status        = STATUS_SUCCESS;
Irp->IoStatus.Information   = 0;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return( STATUS_SUCCESS );
}  /* end of LoDispatchClose */

/*
* Handles call from Win32 CreateFile request. For loopback driver, does
* nothing.
*/
static NTSTATUS LoDispatchCreate
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
)
{
KdPrint((  "Entering LoDispatchCreate()/n" ));
/*
* 尽管IRP对于驱动是串行传输的,但是I/O管理器会并行使用IRP。一般而言,
* Dispatch例程需要修改IRP中成员时,应该在栈上或设备扩展中建立一个副本。
*/
Irp->IoStatus.Status        = STATUS_SUCCESS;
/*
* report that no bytes were transfered
*/
Irp->IoStatus.Information   = 0;
/*
* VOID IoCompleteRequest
* (
*     IN  PIRP    Irp,
*     IN  CCHAR   PriorityBoost
* );
*
* IoCompleteRequest indicates the caller has completed all processing
* for a given I/O request and is returning the given IRP to the I/O
* Manager.
*
* PriorityBoost is IO_NO_INCREMENT if the original thread requested
* an operation the driver could complete quickly or if the IRP is
* completed with an error.
*
* Mark the IRP as "complete" - no further processing, no priority
* increment.
*/
IoCompleteRequest( Irp, IO_NO_INCREMENT );
/*
* 调用IoCompleteRequest()之后,I/O管理器可以从非分页池中释放IRP,因此
* 不能出现return( Irp->IoStatus.Status )这样的代码。
*/
return( STATUS_SUCCESS );
}  /* end of LoDispatchCreate */

/*
* Handles call from Win32 ReadFile request. For loopback driver, transfers
* pool buffer to user.
*/
static NTSTATUS LoDispatchRead
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
)
{
PIO_STACK_LOCATION  IrpStackLocation;
PDEVICE_EXTENSION   DeviceExtension;
ULONG               TransferSize;
PVOID               DestinationBuffer;

KdPrint((  "Entering LoDispatchRead()/n" ));
DeviceExtension                     = ( PDEVICE_EXTENSION )DeviceObject->DeviceExtension;
/*
* PIO_STACK_LOCATION IoGetCurrentIrpStackLocation
* (
*     IN  PIRP    Irp
* );
*
* 假设从用户态到最终设备要经过Driver0、Driver1、Driver2,则IRP中头部
* 之后顺序出现IrpStackLocation0、IrpStackLocation1、IrpStackLocation2。
* 这是我的理解,不清楚是否正确。
*/
IrpStackLocation                    = IoGetCurrentIrpStackLocation( Irp );
TransferSize                        = IrpStackLocation->Parameters.Read.Length;
/*
* Irp->AssociatedIrp.SystemBuffer由I/O管理器在非分页池中分配获得,I/O
* 管理器负责该缓冲区与用户态缓冲区之间的传输,Dispatch例程无需关心。
*/
DestinationBuffer                   = Irp->AssociatedIrp.SystemBuffer;
/*
* Don't transfer more than the user's request
*/
TransferSize                        =
( TransferSize < DeviceExtension->DeviceBufferSize ) ?
TransferSize :
DeviceExtension->DeviceBufferSize;
/*
* VOID RtlCopyMemory
* (
*     IN  VOID UNALIGNED         *Destination,
*     IN  CONST VOID UNALIGNED   *Source,
*     IN  SIZE_T                  Length
* );
*
* DestinationBuffer是内核态地址,至于这批数据最终如何到达用户态,由
* I/O管理器负责处理。
*/
RtlCopyMemory
(
DestinationBuffer,
DeviceExtension->DeviceBuffer,
TransferSize
);
ExFreePoolWithTag
(
DeviceExtension->DeviceBuffer,
PRIVATETAG
);
DeviceExtension->DeviceBuffer       = NULL;
DeviceExtension->DeviceBufferSize   = 0;
/*
* Now complete the IRP
*/
Irp->IoStatus.Status                = STATUS_SUCCESS;
Irp->IoStatus.Information           = TransferSize;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return( STATUS_SUCCESS );
}  /* end of LoDispatchRead */

/*
* Handles call from Win32 WriteFile request. For loopback driver,
* allocates new pool buffer then transfers user data to pool buffer
*/
static NTSTATUS LoDispatchWrite
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
)
{
NTSTATUS            status;
PIO_STACK_LOCATION  IrpStackLocation;
PDEVICE_EXTENSION   DeviceExtension;
ULONG               TransferSize;
PVOID               SourceBuffer;

KdPrint((  "Entering LoDispatchWrite()/n" ));
status                              = STATUS_SUCCESS;
DeviceExtension                     = ( PDEVICE_EXTENSION )DeviceObject->DeviceExtension;
IrpStackLocation                    = IoGetCurrentIrpStackLocation( Irp );
TransferSize                        = IrpStackLocation->Parameters.Write.Length;
SourceBuffer                        = Irp->AssociatedIrp.SystemBuffer;
/*
* free up any old buffer
*/
if ( DeviceExtension->DeviceBuffer )
{
ExFreePoolWithTag
(
DeviceExtension->DeviceBuffer,
PRIVATETAG
);
DeviceExtension->DeviceBuffer       = NULL;
DeviceExtension->DeviceBufferSize   = 0;
}
DeviceExtension->DeviceBuffer       = ExAllocatePoolWithTag
(
PagedPool,
TransferSize,
PRIVATETAG
);
if ( NULL == DeviceExtension->DeviceBuffer )
{
TransferSize                        = 0;
status                              = STATUS_INSUFFICIENT_RESOURCES;
}
else
{
DeviceExtension->DeviceBufferSize   = TransferSize;
RtlCopyMemory
(
DeviceExtension->DeviceBuffer,
SourceBuffer,
TransferSize
);
}
/*
* Now complete the IRP
*/
Irp->IoStatus.Status                = status;
Irp->IoStatus.Information           = TransferSize;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
return( status );
}  /* end of LoDispatchWrite */

static VOID LoDriverUnload
(
IN  PDRIVER_OBJECT  DriverObject
)
{
PDEVICE_OBJECT      NextDeviceObject;
PDEVICE_EXTENSION   DeviceExtension;

KdPrint((  "Entering LoDriverUnload()/n" ));
/*
* Loop through each device controlled by driver
*/
NextDeviceObject    = DriverObject->DeviceObject;
/*
* 这是个单向非循环链表
*/
while ( NextDeviceObject )
{
/*
* Dig out the Device Extension from the Device Object
*/
DeviceExtension     = ( PDEVICE_EXTENSION )NextDeviceObject->DeviceExtension;
/*
* Free up any buffer still held by this device
*/
if ( DeviceExtension->DeviceBuffer )
{
ExFreePoolWithTag
(
DeviceExtension->DeviceBuffer,
PRIVATETAG
);
DeviceExtension->DeviceBuffer       = NULL;
DeviceExtension->DeviceBufferSize   = 0;
}
NextDeviceObject    = NextDeviceObject->NextDevice;
LoDeleteDevice( DeviceExtension->DeviceObject );
}  /* end of while */
return;
}  /* end of LoDriverUnload */

/*
* DriverEntry is the first routine called after a driver is loaded, and
* is responsible for initializing the driver.
*
* you'll find a list of NTSTATUS status codes in the DDK header
* ntstatus.h (/WINDDK/2600.1106/inc/ddk/wxp/)
*/
NTSTATUS DriverEntry
(
IN  PDRIVER_OBJECT  DriverObject,
IN  PUNICODE_STRING RegistryPath
)
{
NTSTATUS    status;

/*
* kernel-mode functions and the functions in your driver use the
* __stdcall calling convention when compiled for an x86 computer.
* This shouldn't affect any of your programming, but it's something
* to bear in mind when you're debugging
*
* This routine has no effect if compiled in a free build environment.
* You should compiled in a checked build environment.
*/
KdPrint((  "Entering DriverEntry()/n" ));

/*
* If this driver controlled real hardware, code would be placed here
* to locate it. Using IoReportDetectedDevice, the ports, IRQs, and
* DMA channels would be "marked" as "in use" and under the control of
* this driver. This Loopback driver has no HW, so...
*/

/*
* Announce other driver entry points
*/
DriverObject->DriverUnload                  = LoDriverUnload;
/*
* This includes Dispatch routines for Create, Write & Read
*/
DriverObject->MajorFunction[IRP_MJ_CREATE]  = LoDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_READ  ]  = LoDispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE ]  = LoDispatchWrite;
DriverObject->MajorFunction[IRP_MJ_CLOSE ]  = LoDispatchClose;
/*
* 参<<Programming the Microsoft Windows Driver Model, 2nd Ed>>第三章
* 中"Error Handling"小节。
*
* Which Exceptions Can Be Trapped
*
* Gary Nebbett researched the question of which exceptions can be
* trapped with the structured exception mechanism and reported his
* results in a newsgroup post several years ago. In summary, the
* following exceptions will be caught when they occur at IRQL less
* than or equal to DISPATCH_LEVEL (note that some of these are
* specific to the Intel x86 processor):
*
* a. Anything signaled by ExRaiseStatus and related functions
* b. Attempt to dereference invalid pointer to user-mode memory
* c. Debug or breakpoint exception
* d. Integer overflow (INTO instruction)
* e. Invalid opcode
*
* Note that a reference to an invalid kernel-mode pointer leads
* directly to a bug check and can’t be trapped. Likewise, a
* divide-by-zero exception or a BOUND instruction exception leads to
* a bug check.
*
* 稳妥起见,还是使用SEH机制吧,尽量避免调试时重启。
*/
__try
{
KdPrint((  "You should see this message [0]/n" ));
/*
* For each physical or logical device detected that will be under
* this Driver's control, a new Device Object must be created.
*
* This call would be repeated until all devices are created.
*
* 我们这里只创建了一个设备对象
*/
status  = LoCreateDevice
(
DriverObject,
0
);
KdPrint((  "You should see this message [1]/n" ));
}
__except ( EXCEPTION_EXECUTE_HANDLER )
{
KdPrint((  "__except{}/n" ));
status  = STATUS_UNSUCCESSFUL;
}
KdPrint((  "Exiting DriverEntry()/n" ));
return( status );
}  /* end of DriverEntry */

/************************************************************************/

--------------------------------------------------------------------------

loopback.c源自[2]中1至7章的内容,区别在于我采用纯C语言,抛弃了一个Unicode
相关的C++类。

下面是一些备忘记录:

1) KdPrint

调用KdPrint时应该指定两层圆括号,像这样:

KdPrint((  "Entering DriverEntry()/n" ));

这个宏只在调试版本中有意义,NTDDK.h中定义如下:

#if DBG
#define KdPrint(_x_) DbgPrint _x_
#else
#define KdPrint(_x_)
#endif

最开始我没注意这点,用Win XP Free Build Environment编译,结果死活看不到调
试信息输出。应该用Win XP Checked Build Environment编译。

2) 内部设备名、外部设备名

相关四个函数是IoCreateDevice、IoCreateSymbolicLink、IoDeleteSymbolicLink、
IoDeleteDevice。Win32 API要通用外部设备名访问设备。

一般内部设备号从0计,外部设备号从1计,比如:

/Device/LoopbackInternal0
/??/LoopbackExternal1

3) 池(Pool)

驱动编程中没有常规的malloc、free、new、delete可用,内核态的池就相当于用户
态的堆(Heap),操作函数换成了ExAllocatePoolWithTag、ExFreePoolWithTag等等。

池是有限的内核资源,不再使用时要趁早释放它。

DDK文档中指出,如果ExAllocatePool返回NULL,主调者应该返回
STATUS_INSUFFICIENT_RESOURCES。但是DDK文档中没有指出ExAllocatePoolWithTag
返回NULL时该如何,我只好类比一下,也返回STATUS_INSUFFICIENT_RESOURCES。不
知这里是否可以返回STATUS_NO_MEMORY。

4) CRT/RTL

用户态编程时所用的那些C运行时库函数统统不要想当然地用于内核态,在DDK文档中
查看Rtl*(),这是内核态正经可用的运行时库函数。由于没有wsprintf()可用,程序
中用了很变态的办法构造内部设备名、外部设备名。

5) Unload

假设驱动程序支持动态卸载,当I/O管理器调用DriverObject->DriverUnload时,该
函数"必须"完成正确的卸载相关动作,它不能在被调用之后选择拒绝卸载。一旦该函
数返回,驱动被无条件地卸载。如果该函数没有完成正确的卸载相关动作就返回了,
结果是灾难性的。参[1]中12章的"Dynamic Unloading"小节。

LoDriverUnload的返回值类型是VOID,与上述描述相符。有鉴于此,LoDeleteDevice
中虽然检查了各个函数的返回值,但只能简单输出调试信息,并无其它有意义的针对
性的动作,换句话说,此时只能祈祷不要出错。

6) UNICODE_STRING

驱动编程中大量使用UNICODE_STRING结构,其成员Length不包括结尾的NUL字符(如果
有的话)。

7) 设备扩展

设备扩展的内存空间是由IoCreateDevice给予的,由IoDeleteDevice释放,不要调用
其它函数释放设备扩展。

设备扩展与设备对像都位于非分页池中。参[1]中9章"Device Objects"小节。

8) IRP

尽管IRP对于驱动是串行传输的,但是I/O管理器会并行使用IRP。一般而言,
Dispatch例程需要修改IRP中成员时,应该在栈上或设备扩展中建立一个副本。

假设从用户态到最终设备要经过Driver0、Driver1、Driver2,则IRP中头部之后顺序
出现IrpStackLocation0、IrpStackLocation1、IrpStackLocation2。这是我的理解,
不清楚是否正确。在[7]中3.6.2小节有一个示意图,印证了我的理解。

Irp->AssociatedIrp.SystemBuffer由I/O管理器在非分页池中分配获得,I/O管理器
负责该缓冲区与用户态缓冲区之间的传输,Dispatch例程无需关心。

9) IoCompleteRequest

调用IoCompleteRequest()之后,I/O管理器可以从非分页池中释放IRP,因此不能出
现这样的代码:

--------------------------------------------------------------------------
static NTSTATUS LoDispatchCreate
(
IN  PDEVICE_OBJECT  DeviceObject,
IN  PIRP            Irp
)
{
Irp->IoStatus.Status        = STATUS_SUCCESS;
Irp->IoStatus.Information   = 0;
IoCompleteRequest( Irp, IO_NO_INCREMENT );
/*
* Error!!!
*/
return( Irp->IoStatus.Status )
}  /* end of LoDispatchCreate */
--------------------------------------------------------------------------

10) DRIVER_OBJECT/DEVICE_OBJECT

DRIVER_OBJECT中DeviceObject成员对应一条单向非循环链表,结点由DEVICE_OBJECT
组成。KMD动态卸载时需要遍历该链表:

--------------------------------------------------------------------------
static VOID LoDriverUnload
(
IN  PDRIVER_OBJECT  DriverObject
)
{
PDEVICE_OBJECT      NextDeviceObject;
... ...

NextDeviceObject    = DriverObject->DeviceObject;
while ( NextDeviceObject )
{
... ...
NextDeviceObject    = NextDeviceObject->NextDevice;
... ...
}  /* end of while */
return;
}  /* end of LoDriverUnload */
--------------------------------------------------------------------------

11) 内核态的结构化异常处理

参[3]中第三章"Error Handling"小节。这段我原文照录:

--------------------------------------------------------------------------
Which Exceptions Can Be Trapped

Gary Nebbett researched the question of which exceptions can be trapped
with the structured exception mechanism and reported his results in a
newsgroup post several years ago. In summary, the following exceptions
will be caught when they occur at IRQL less than or equal to
DISPATCH_LEVEL (note that some of these are specific to the Intel x86
processor):

a. Anything signaled by ExRaiseStatus and related functions
b. Attempt to dereference invalid pointer to user-mode memory
c. Debug or breakpoint exception
d. Integer overflow (INTO instruction)
e. Invalid opcode

Note that a reference to an invalid kernel-mode pointer leads directly to
a bug check and can’t be trapped. Likewise, a divide-by-zero exception or
a BOUND instruction exception leads to a bug check.
--------------------------------------------------------------------------

引用一个无效的内核态地址,直接导致KeBugCheck/KeBugCheckEx,无法利用SEH机制
挽救,这与用户态不同。

12) 丢弃初始化例程、控制驱动程序分页

参看[2]中5.2.6、5.2.7小节。INIT、PAGE应该是大小写敏感的,可我居然看到过
init、page,不清楚怎么回事,稳妥起见还是用INIT、PAGE算了。

--------------------------------------------------------------------------
#ifdef ALLOC_PRAGMA

#pragma alloc_text( INIT, LoCreateDevice    )
#pragma alloc_text( PAGE, LoDeleteDevice    )
#pragma alloc_text( PAGE, LoDispatchClose   )
#pragma alloc_text( PAGE, LoDispatchCreate  )
#pragma alloc_text( PAGE, LoDispatchRead    )
#pragma alloc_text( PAGE, LoDispatchWrite   )
#pragma alloc_text( PAGE, LoDriverUnload    )
#pragma alloc_text( INIT, DriverEntry       )

#endif
--------------------------------------------------------------------------

☆ 编写dirs文件

与用户空间编程不同,只有loopback.c不足以方便地产生loopback.sys,一般还需要
三个辅助文件:

dirs、sources、makefile

DDK文档"Running the Build Utility"小节对此有详细解释。build根据dirs文件遍
历目录树,在子目录中发现dirs时继续遍历,发现sources时build开始为调用nmake
做准备。nmake使用makefile,最终调用cl进行真正的编译。

假设将来目录/文件布局如下:

loopback/ --+-- dirs
|
+-- code/ --+-- loopback.c
|
+-- sources
|
+-- makefile

这里只列举了手工创建的目录/文件,不包括编译过程产生的目录/文件。

此时dirs文件内容很简单,就一行:

--------------------------------------------------------------------------
DIRS=code
--------------------------------------------------------------------------

☆ 编写sources文件

--------------------------------------------------------------------------
#
# Use the TARGETNAME macro to specify the name of the library to be built.
# Do not include the file name extension
#
TARGETNAME=loopback

#
# All build products (such as .exe, .dll, and .lib files) will be placed
# in this directory
#
# BUILD_ALT_DIR的值会被追加在TARGETPATH之后,如果你嫌BUILD_ALT_DIR太碍眼,
# 可以删除该环境变量。
#
TARGETPATH=obj

#
# Use the TARGETTYPE macro to specify the type of product being built.
# TARGETTYPE gives the Build utility clues about some of the input files
# that it should expect. You must include this macro in your sources file.
#
TARGETTYPE=DRIVER

#
# Use the USE_PDB macro if your debug symbolic files will use a VC4 PDB.
# This is the default in the Windows XP build environment.
#
USE_PDB=1

#
# Use the INCLUDES macro to indicate the location of the headers to be
# included in your build
#
INCLUDES=

#
# Use the MSC_WARNING_LEVEL macro to set the warning level to use on the
# compiler. The default is /W3.
#
# After your code builds without errors, you might want to change
# MSC_WARNING_LEVEL to /W3 /WX. Setting this value causes warnings to show
# as errors.
#
MSC_WARNING_LEVEL=-W3 -WX

#
# The SOURCES macro specifies the files to be compiled. The SOURCES macro
# is required by the Build utility. This macro must be placed in your
# sources file. All files specified by this macro must reside in the
# directory containing the sources file.
#
SOURCES=loopback.c
--------------------------------------------------------------------------

假设进入了"Win XP Checked Build Environment":

J:/source/driver/loopback> set BUILD_ALT_DIR
BUILD_ALT_DIR=chk_wxp_x86
J:/source/driver/loopback> build -cZ -x86
J:/source/driver/loopback> tree /f /a
loopback
|   buildchk_wxp_x86.log
|   dirs
|
/---code
|   loopback.c
|   loopback.inf
|   makefile
|   sources
|
/---objchk_wxp_x86
|   _objects.mac
|
/---i386
loopback.obj
loopback.pdb
loopback.sys

☆ 编写makefile文件

--------------------------------------------------------------------------
!INCLUDE $(NTMAKEENV)/makefile.def
--------------------------------------------------------------------------

J:/source/driver/loopback> set NTMAKEENV
NTMAKEENV=J:/WINDDK/2600~1.110/bin

☆ 编译产生loopback.sys文件

在dirs文件所在目录里执行"build -cZ -x86":

J:/source/driver/loopback> build -cZ -x86
BUILD: Adding /Y to COPYCMD so xcopy ops won't hang.
BUILD: Object root set to: ==> objchk_wxp_x86
BUILD: Compile and Link for i386
BUILD: Examining j:/source/driver/loopback directory tree for files to compile.
BUILD: Compiling j:/source/driver/loopback/code directory
Compiling - code/loopback.c for i386
BUILD: Linking j:/source/driver/loopback/code directory
Linking Executable - code/objchk_wxp_x86/i386/loopback.sys for i386
BUILD: Done

2 files compiled
1 executable built

☆ 编写loopback.inf文件

; ------------------------------------------------------------------------

;
; Copyright to satisfy the CHKINF utility
;

[Version]
Signature           = "$Windows NT___FCKpd___0quot;
Class               = %ClassName%
;
; For a new device setup class, the INF must specify a newly generated
; ClassGuid value
;
; 用/WINDDK/2600.1106/tools/other/i386/guidgen.exe生成的是
; {5675F8A0-FB7C-40cf-BA53-9233A183BD7E},中间两个字母是小写,不清楚为什么,
; 我换成全大写了。
;
ClassGuid           = {5675F8A0-FB7C-40CF-BA53-9233A183BD7E}
Provider            = %INFCreator%
DriverVer           = 07/06/2004,1.00.1993.9

; ------------------------------------------------------------------------

[ClassInstall32.ntx86]
AddReg              = ClassInstall32Reg

[ClassInstall32Reg]
HKR,,,,%ClassName%

; ------------------------------------------------------------------------

[Manufacturer]
%INFCreator%        = LoopbackSection

; ------------------------------------------------------------------------

[LoopbackSection]
%DESCRIPTION%       = DDInstall,*LOOPBACKDRIVER1993

; ------------------------------------------------------------------------

[SourceDisksNames.x86]
1                   = %DiskDescription%,,,

[SourceDisksFiles.x86]
loopback.sys        = 1

[DestinationDirs]
DefaultDestDir      = 10,system32/drivers
FileList            = 10,system32/drivers

; ------------------------------------------------------------------------

[DDInstall.ntx86]
Copyfiles           = FileList

[FileList]
loopback.sys,,,0x00000002

; ------------------------------------------------------------------------

[DDInstall.ntx86.Services]
AddService          = Loopback,0x00000002,ServiceInstall

[ServiceInstall]
DisplayName         = %FriendlyName%                   ; friendly name
ServiceType         = 0x00000001                       ; SERVICE_KERNEL_DRIVER
StartType           = 0x3                              ; SERVICE_DEMAND_START
ErrorControl        = 0x1                              ; SERVICE_ERROR_NORMAL
ServiceBinary       = %10%/system32/drivers/loopback.sys

; ------------------------------------------------------------------------

[DDInstall.ntx86.LOOPBACK]
AddReg              = DDInstallRegLOOPBACK

[DDInstallRegLOOPBACK]
HKR,,LoopbackInfo,,%DESCRIPTION%

; ------------------------------------------------------------------------

[Strings]
ClassName           = "LoopbackClass"
INFCreator          = "The Loopback Software"
DESCRIPTION         = "The Loopback Driver"
DiskDescription     = "The Loopback Software Disk"
FriendlyName        = "Loopback"

; ------------------------------------------------------------------------

如果安装loopback.inf文件时出了问题,请先查看%systemroot%/setupapi.log。更
多关于INF文件的讨论参看<<MSDN系列(2)--学习写一个Hello World驱动>>。

☆ 安装loopback.inf文件

->控制面板
->添加硬件(或者直接在cmd中运行hdwwiz.cpl)
->是,硬件已联接好
->添加新的硬件设备
->安装我手动从列表选择的硬件(高级)
->显示所有设备
->从磁盘安装
->完成

hello.c最后故意失败返回,安装虽然完成,但加载驱动必然失败。在设备管理器中(
直接在cmd中运行devmgmt.msc)可以看到黄色惊叹号,表示无法成功加载驱动程序。
但loopback.sys不同,可以成功加载,设备管理器中无异常。

这次安装过程导致如下一些结果:

1)

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------

2)

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
"ActiveService"="Loopback"
--------------------------------------------------------------------------

3)

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="ROOT//UNKNOWN//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------

安装loopback.inf文件之后,无论是否重启,注册表中都只有这三处与loopback.sys
相关。

4)

复制loopback.sys到%systemroot%/system32/drivers/目录下。查看setupapi.log文
件:

--------------------------------------------------------------------------
[2004/07/09 14:31:09 1592.430]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I140 正在安装设备类别: "LoopbackClass" {5675F8A0-FB7C-40CF-BA53-9233A183BD7E}。
#I141 类别安装已结束,没有出错。
[2004/07/09 14:30:20 1592.427 Driver Install]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I393 更改过的 INF 缓存 "X:/WINDOWS/inf/INFCACHE.1"。
#-124 正在做“仅复制”安装 "ROOT/UNKNOWN/0000"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#W187 安装失败,试图还原源文件。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-024 正在将文件 "c:/onlytemp/loopback.sys" 复制到 "X:/WINDOWS/system32/drivers/loopback.sys"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-166 设备安装函数: DIF_REGISTER_COINSTALLERS。
#I056 注册了共同安装程序。
#-166 设备安装函数: DIF_INSTALLINTERFACES。
#-011 正在从 "x:/loopback.inf" 安装段 [DDInstall.NTx86.Interfaces]。
#I054 安装接口。
#-166 设备安装函数: DIF_INSTALLDEVICE。
#I123 进行 "ROOT/UNKNOWN/0000" 的完整安装。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#I121 "ROOT/UNKNOWN/0000" 的设备安装成功完成。
--------------------------------------------------------------------------

不要在资源管理器中右键选中loopback.inf,点击菜单里的"安装/Install"。

HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet只是到ControlSet<nnn>的符号链接,
HKEY_LOCAL_MACHINE/SYSTEM/Select用于具体确定是到谁的符号链接。启动时F8菜单
有一个选项对应最近一次成功引导所用设置,就是由LastKnownGood决定的。参看[4]
的5.2.6小节。

☆ 卸载loopback.sys及相关设置

1)

完全手工卸载,从注册表中删除前述三处内容,从drivers中删除loopback.sys。

2)

执行"sc delete Loopback":

> sc delete Loopback
[SC] DeleteService SUCCESS

此时设备管理器中仍有相应显示,"重启后"注册表中还有如下内容,需要手工删除。

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
--------------------------------------------------------------------------

drivers/loopback.sys也未删除,需要手工删除。对比卸载hello.sys时的情形,区
别较大,不过当时hello.sys加载失败,这回loopback.sys加载成功。

"sc delete"只处理了"Services/Loopback"子键!

3)

通过设备管理器(devmgmt.msc)卸载,系统提示重启,之后注册表中还有如下内容,
需要手工删除。

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
--------------------------------------------------------------------------

> del %systemroot%/system32/drivers/loopback.sys

☆ installdriver.c

通过INF文件安装loopback.sys之后,有很多不爽的事情。

> drivers.exe | find "loopback"
loopback.sys     128       0       0    1536    1920  Fri Jul 09 13:10:00 2004

执行上述命令确认loopback.sys已经成功加载。但是不能动态卸载:

> net stop Loopback
The requested pause or stop is not valid for this service.
> net start Loopback
The requested service has already been started.

在devmgmt.msc中对"The Loopback Driver"选择停用、启用、卸载均提示要重启才行,
显然这与我初始愿望相违背。最终从设备管理器中卸载了"The Loopback Driver",
重启后从注册表中删除残留内容,手工删除drivers/loopback.sys文件,恢复到初始
状态。

参<<MSDN系列(8)--学习写一个Hello World服务(Service)>>。installservice.c用
于安装loopback.sys再好不过,不过得作一处小改动,上次是针对服务,这次是针对
驱动。

--------------------------------------------------------------------------
/*
* Version  : 1.02
* Compile  : For x86/EWindows XP SP1 & VC 7
*          : cl installdriver.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*          :
* Create   : 2003-12-08 16:47
* Modify   : 2004-07-09 17:33
*/

/************************************************************************
*                                                                      *
*                               Head File                              *
*                                                                      *
************************************************************************/

/*
* #define _WIN32_WINNT 0x0501
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

/************************************************************************
*                                                                      *
*                               Macro                                  *
*                                                                      *
************************************************************************/

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )
#pragma comment( lib,    "kernel32.lib"       )
#pragma comment( lib,    "advapi32.lib"       )

#define VERSION "1.00"

/************************************************************************
*                                                                      *
*                            Function Prototype                        *
*                                                                      *
************************************************************************/

static void PrintWin32ErrorCLI
(
char   *message,
DWORD   dwMessageId
);
static void usage
(
char   *arg
);

/************************************************************************
*                                                                      *
*                            Static Global Var                         *
*                                                                      *
************************************************************************/

/************************************************************************/

static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char   *errMsg;

FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
}  /* end of PrintWin32ErrorCLI */

static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-t target] [-s servicename] [-d displayname] [-c cmdline]/n",
arg
);
exit( EXIT_FAILURE );
}  /* end of usage */

int __cdecl main ( int argc, char * argv[] )
{
SC_HANDLE       scm         = ( SC_HANDLE )NULL,
sc_handle   = ( SC_HANDLE )NULL;
unsigned char  *target      = NULL,
*servicename = NULL,
*displayname = NULL,
*cmdline     = NULL;
int             c,
ret         = EXIT_FAILURE;

if ( 1 == argc )
{
usage( argv[0] );
}
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'c':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
cmdline     = argv[++c];
break;
case 'd':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
displayname = argv[++c];
break;
case 's':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
servicename = argv[++c];
break;
case 't':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
target      = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
}  /* end of switch */
}
}  /* end of for */
/*
* 检查参数有效性
*/
if ( NULL == servicename )
{
fprintf( stderr, "Checking your [-s servicename]/n" );
return( EXIT_FAILURE );
}
if ( NULL == displayname )
{
fprintf( stderr, "Checking your [-d displayname]/n" );
return( EXIT_FAILURE );
}
if ( NULL == cmdline )
{
fprintf( stderr, "Checking your [-c cmdline]/n" );
return( EXIT_FAILURE );
}
/*
* Header : Declared in Winsvc.h; include Windows.h.
* Library: Use Advapi32.lib.
*
* SC_HANDLE OpenSCManager
* (
*     LPCTSTR lpMachineName,      // computer name
*     LPCTSTR lpDatabaseName,     // SCM database name
*     DWORD   dwDesiredAccess     // access type
* );
*
* 第一形参可以用target,也可用//<target>。还应该尝试unicodeserver。
*/
scm         = OpenSCManager
(
target,
SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CREATE_SERVICE
);
if ( NULL == scm )
{
PrintWin32ErrorCLI( "OpenSCManager() failed", GetLastError() );
goto main_exit;
}
/*
* SC_HANDLE CreateService
* (
*     SC_HANDLE   hSCManager,         // handle to SCM database
*     LPCTSTR     lpServiceName,      // name of service to start
*     LPCTSTR     lpDisplayName,      // display name
*     DWORD       dwDesiredAccess,    // type of access to service
*     DWORD       dwServiceType,      // type of service
*     DWORD       dwStartType,        // when to start service
*     DWORD       dwErrorControl,     // severity of service failure
*     LPCTSTR     lpBinaryPathName,   // name of binary file
*     LPCTSTR     lpLoadOrderGroup,   // name of load ordering group
*     LPDWORD     lpdwTagId,          // tag identifier
*     LPCTSTR     lpDependencies,     // array of dependency names
*     LPCTSTR     lpServiceStartName, // account name
*     LPCTSTR     lpPassword          // account password
* );
*/
sc_handle   = CreateService
(
scm,
servicename,
displayname,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
cmdline,
NULL,
NULL,
NULL,
NULL,
NULL
);
if ( NULL == sc_handle )
{
PrintWin32ErrorCLI( "CreateService() failed", GetLastError() );
goto main_exit;
}
ret         = EXIT_SUCCESS;

main_exit:

if ( NULL != sc_handle )
{
CloseServiceHandle( sc_handle );
sc_handle = ( SC_HANDLE )NULL;
}
if ( NULL != scm )
{
CloseServiceHandle( scm );
scm = ( SC_HANDLE )NULL;
}
return( ret );
}  /* end of main */

/************************************************************************/

--------------------------------------------------------------------------

参看<<MSDN系列(8)--学习写一个Hello World服务(Service)>>了解更多讨论。这次
在本地使用installdriver.exe,注意要指定loopback.sys的绝对路径,观察注册表
中ImagePath键值的数据,一般是system32/drivers/loopback.sys。

> installdriver.exe -s Loopback -d Loopback -c <Absolute Path>/loopback.sys
> net start Loopback
The Loopback service was started successfully.
> drivers.exe | findstr /I loopback
loopback.sys       0       0       0       0       0

此时注册表中新增了两处内容:

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK]
"NextInstance"=dword:00000001

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000]
"Service"="Loopback"
"Legacy"=dword:00000001
"ConfigFlags"=dword:00000000
"Class"="LegacyDriver"
"ClassGUID"="{8ECC055D-047F-11D1-A537-0000F8753ED1}"
"DeviceDesc"="Loopback"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000/Control]
"*NewlyCreated*"=dword:00000000
"ActiveService"="Loopback"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):5c,00,3f,00,3f,00,5c,00,63,00,3a,00,5c,00,6f,00,6e,00,6c,00,/
79,00,74,00,65,00,6d,00,70,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,/
00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="Root//LEGACY_LOOPBACK//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------

ImagePath键值的数据现在是"/??/c:/onlytemp/loopback.sys"。

另有一处一直存在非新增但相关的内容:

--------------------------------------------------------------------------
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}]
"Class"="LegacyDriver"
@="Non-Plug and Play Drivers"
"NoDisplayClass"="1"
"SilentInstall"="1"
"NoInstallClass"="1"
"EnumPropPages32"="SysSetup.Dll,LegacyDriverPropPageProvider"
"Icon"="-19"

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}/0000]
--------------------------------------------------------------------------

> net stop Loopback
The Loopback service was stopped successfully.
> sc delete Loopback
[SC] DeleteService SUCCESS

这样省事得多,不过前面的loopback.inf文件还是保留备忘,那是WDM驱动正宗安装
方案,我们这次是KMD。

☆ 察看KdPrint输出

启动DebugView([5]),在Capture菜单里勾中Capture kernel、Pass-Through、
Capture Events即可,扔到一边别理它。

也可以启动DriverStudio所带DriverMonitor。

☆ loopbacktest.c

--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7
* cl loopbacktest.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*/

/************************************************************************
*                                                                      *
*                               Head File                              *
*                                                                      *
************************************************************************/

/*
* #define _WIN32_WINNT 0x0501
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>

/************************************************************************
*                                                                      *
*                               Macro                                  *
*                                                                      *
************************************************************************/

#pragma comment( linker, "/INCREMENTAL:NO"    )
#pragma comment( linker, "/subsystem:console" )

#define VERSION         "1.00"
#define DEFAULTMESSAGE  "LOOPBACK"
#define EXTERNALNAME    "////.//LoopbackExternal1"

/************************************************************************
*                                                                      *
*                            Function Prototype                        *
*                                                                      *
************************************************************************/

static void PrintWin32ErrorCLI
(
char   *message,
DWORD   dwMessageId
);
static void usage
(
char   *arg
);

/************************************************************************
*                                                                      *
*                            Static Global Var                         *
*                                                                      *
************************************************************************/

/************************************************************************/

static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char   *errMsg;

FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
}  /* end of PrintWin32ErrorCLI */

static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-m message]/n",
arg
);
exit( EXIT_FAILURE );
}  /* end of usage */

int __cdecl main ( int argc, char * argv[] )
{
unsigned char  *message = DEFAULTMESSAGE;
unsigned int    length;
unsigned char  *buf     = NULL;
int             c,
ret     = EXIT_FAILURE;
HANDLE          Device  = INVALID_HANDLE_VALUE;
DWORD           NumberOfBytes;

#if 0
if ( 1 == argc )
{
usage( argv[0] );
}
#endif
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'm':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
message = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
}  /* end of switch */
}
}  /* end of for */
/*
* HANDLE CreateFile
* (
*     LPCTSTR                 lpFileName,             // file name
*     DWORD                   dwDesiredAccess,        // access mode
*     DWORD                   dwShareMode,            // share mode
*     LPSECURITY_ATTRIBUTES   lpSecurityAttributes,   // SD
*     DWORD                   dwCreationDisposition,  // how to create
*     DWORD                   dwFlagsAndAttributes,   // file attributes
*     HANDLE                  hTemplateFile           // handle to template file
* );
*
* If the function fails, the return value is INVALID_HANDLE_VALUE. To
* get extended error information, call GetLastError.
*/
Device  = CreateFile
(
EXTERNALNAME,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,           // If lpSecurityAttributes is NULL, the handle cannot be inherited
OPEN_EXISTING,  // The function fails if the file does not exist
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( INVALID_HANDLE_VALUE == Device )
{
PrintWin32ErrorCLI( "CreateFile() failed", GetLastError() );
goto main_exit;
}
length  = strlen( message ) + 1;
/*
* BOOL WriteFile
* (
*     HANDLE          hFile,                  // handle to file
*     LPCVOID         lpBuffer,               // data buffer
*     DWORD           nNumberOfBytesToWrite,  // number of bytes to write
*     LPDWORD         lpNumberOfBytesWritten, // number of bytes written
*     LPOVERLAPPED    lpOverlapped            // overlapped buffer
* );
*/
if
(
FALSE == WriteFile
(
Device,
message,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "WriteFile() failed", GetLastError() );
goto main_exit;
}
/*
* 分配缓冲区
*
* HANDLE GetProcessHeap ( VOID );
*
* The handle obtained by calling this function should not be used in
* calls to the HeapDestroy function.
*
* Header : Declared in Winnls.h; include Windows.h.
* Library: Use Kernel32.lib.
*
* LPVOID HeapAlloc
* (
*     HANDLE hHeap,    // handle to private heap block
*     DWORD  dwFlags,  // heap allocation control
*     SIZE_T dwBytes   // number of bytes to allocate
* );
*
* If the function fails, it does not call SetLastError. An
* application cannot call GetLastError for extended error information.
*/
buf     = ( unsigned char * )HeapAlloc
(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
length
);
if ( NULL == buf )
{
PrintWin32ErrorCLI( "HeapAlloc() failed for buf", ERROR_NOT_ENOUGH_MEMORY );
goto main_exit;
}
/*
* BOOL ReadFile
* (
*     HANDLE       hFile,                 // handle to file
*     LPVOID       lpBuffer,              // data buffer
*     DWORD        nNumberOfBytesToRead,  // number of bytes to read
*     LPDWORD      lpNumberOfBytesRead,   // number of bytes read
*     LPOVERLAPPED lpOverlapped           // overlapped buffer
* );
*/
if
(
FALSE == ReadFile
(
Device,
buf,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "ReadFile() failed", GetLastError() );
goto main_exit;
}
if ( length == NumberOfBytes )
{
printf( "[%s]/n", buf );
}
ret     = EXIT_SUCCESS;

main_exit:

if ( NULL != buf )
{
HeapFree( GetProcessHeap(), 0, buf );
buf     = NULL;
}
if ( INVALID_HANDLE_VALUE != Device )
{
CloseHandle( Device );
Device  = INVALID_HANDLE_VALUE;
}
return( ret );
}  /* end of main */

/************************************************************************/

--------------------------------------------------------------------------

> loopbacktest.exe -m "NSFOCUS"
[NSFOCUS]

☆ 用SoftICE对loopback.sys进行源码级调试

下述步骤是充分非必要的,换句话说,我是这样做的,并且成功了,但不是必须这样
操作。

一定要用"Win XP Checked Build Environment"编译。编译前先确认两个环境变量的
设置,第二个可以设置成"NTDEBUGTYPE=windbg",也可以保持不变。

> set NTDEBUG
NTDEBUG=ntsd
NTDEBUGTYPE=both

我的DDK开发环境在VMware Host上,调试环境则在VMware Guest上。将loopback.c、
loopback.pdb、loopback.sys复制到VMware Guest上同一目录下,比如onlytemp下。
用installdriver.exe安装loopback.sys。VMware Guest上PATH环境变量已经包含
"C:/Program Files/Compuware/DriverStudio/SoftICE",其下含有nmsym.exe。

--------------------------------------------------------------------------
onlytemp> installdriver.exe -s Loopback -d Loopback -c c:/onlytemp/loopback.sys

onlytemp> nmsym.exe /translate:always,source,package /output:loopback.nms loopback.sys

Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware  Technologies, 1996-2001

MODULE=loopback.sys
OUTPUT=loopback.nms
PROMPT=OFF
Translation of C:/onlytemp/loopback.sys successfully completed

onlytemp> nmsym.exe /symload:loopback.nms

Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware  Technologies, 1996-2001

SYMLOAD=loopback.nms
PROMPT=OFF
Symbols for loopback.nms successfully loaded

onlytemp> nmsym.exe /unload:loopback.nms

Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware  Technologies, 1996-2001

UNLOAD=loopback.nms
PROMPT=OFF
Error removing symbol table loopback.nms: Item not found (status)
--------------------------------------------------------------------------

在命令行上用nmsym.exe卸载loopback.nms时会看到一条错误信息,不要理会它,进
SoftICE用table命令确认已经卸载成功。重新加载loopback.nms,在DriverEntry处
设置断点"bpx loopback!DriverEntry",在VMware Guest中加载loopback.sys,远程
SoftICE弹出。

onlytemp> net start Loopback

在SoftICE中执行如下操作:

:file *
loopback.c
:file loopback.c
:wc
:.

现在代码窗口中是loopback.c源代码,光条停留在DriverEntry的左花括号上。用src
命令或F3快捷键可以切换代码窗口的显示方式,有三种方式,纯源代码显示、混合显
示、汇编显示。有时切换显示方式时无法回到纯源代码显示,可再次使用"file .c"
恢复正常。顺便说一句,远程SoftICE的代码窗口可以看到中文注释,只要siremote
这边可以显示中文即可,本地SoftICE就没这好事了。

至此可以用SoftICE对loopback.sys进行源码级调试。KdPrint输出直接在SoftICE命
令窗口中显示,可以用F10、F8这些快捷键。

onlytemp> loopbacktest -m NSFOCUS

上述测试程序将激活LoDispatchCreate(),在SoftICE中下断点:

:bpx loopback!LoDispatchCreate
:bl
00)   BPX DriverEntry
01)   BPX LoDispatchCreate
:g
Break due to BP 01: BPX LoDispatchCreate
:p

最后这个P命令不可缺少,否则EBP尚未设置好,而形参、局部变量靠EBP定位,在混
合显示中很明显。现在可以用watch命令观察形参DeviceObject、Irp:

:watch Irp
:watch DeviceObject

选中那些带+号的条目,用回车展开后查看更详细的内容。不想观察时可用DEL删除相
应条目。

loopback.sys本身没有什么好调试的,只是通过这个例子感性地说明如何用SoftICE
对驱动进行源码级调试。loopback.sys是手工加载的,可以这样调试。对于那些启动
时加载的驱动程序,需要动用Initialization设置以及icepack.exe,没这需求,就
不去测试了。

☆ 后记

驱动相关结构的描述未出现在文中,那不是重点。工欲善其事必先利其器,重点是建
立感性认识、介绍调试步骤。有了这个基础,剩下的事属于八仙过海了。至少可在本
文基础上编译别人的源代码、进行常规测试以及简单调试。否则给你源代码,也是老
虎吃天的架势。

估计我适合做一些面向与我一样低智商的人群的计算机科普工作。比如以前没干过某
事,又一直很想感性地接触一下,需要一些"Step By Step"教程。记录这个过程,因
为想到肯定有人一样不务正业之余想折腾一下这个方向,但又找不到一条龙式的教程。
我承认这篇水文没有技术含量,但所写下的每一个字都是急于入门时切身体会下的字,
想必还是对另一些人有些微用处。如果真是那样,我将非常开心。

在Google中可以找到大量收费的驱动开发学习班,但甚少入门级教程。驱动开发的门
槛是人为抬高的,这么说,肯定有人不高兴了,觉得我连边都没摸着就狂得下这个结
论,嘿嘿。真是不好意思,我虽然没吃过天鹅肉,但肯定在广州动物园看见过天鹅。
所以对某些人的心性还是有所了解的。再说我也相信rain说的话,都是死框架加硬件
规范。话分两头,那些不是业余折腾驱动的人,不妨有骨气些,别个掖着不露就自己
看文档呗,瞎子摸象摸十年也该有个轮廓了,求人不如自力更生。当然,有得可求教
之人,不求教是白不求教。

下面这些参考资源是上周写本文时用到的。一般有URL的,绝不会故意漏去,只是这
次比较特殊,有四本是纸版书([1]、[2]、[4]、[7]),无从列出URL啊。

结语是这样的,在"三个代表"的指引下,我编写驱动的能力有了很大提高,!@#$%^&*

☆ 参考资源

[ 1] <<Windows NT Device Driver Development>> - P. Viscarola, W. Mason

[ 2] <<The Windows 2000 Device Driver Book, Second Edition>> - Art Baker, Jerry Lozano

[ 3] <<Programming the Microsoft Windows Driver Model, 2nd Ed>> - Walter Oney

[ 4] <<Inside Microsoft Windows 2000 Third Edition>> - David A. Solomon, Mark E. Russinovich

[ 5] DebugView http://www.sysinternals.com 
[ 6] <<MSDN系列(2)--学习写一个Hello World驱动>>
<<MSDN系列(8)--学习写一个Hello World服务(Service)>>

[ 7] <<Writing Windows WDM Device Drivers>> - Chris Cant

[ 8] Using SoftICE.pdf
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: