Skip to content

Latest commit

 

History

History
2615 lines (2313 loc) · 86.2 KB

200407121605.txt.md

File metadata and controls

2615 lines (2313 loc) · 86.2 KB

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

创建: 2004-07-06 20:17 更新: 2004-07-12 14:47 链接: http://scz.617.cn/windows/200407121605.txt


☆ 概述
☆ 编写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编译。

  1. 内部设备名、外部设备名

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

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

\Device\LoopbackInternal0 ??\LoopbackExternal1

  1. 池(Pool)

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

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

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

  1. CRT/RTL

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

  1. Unload

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

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

  1. UNICODE_STRING

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

  1. 设备扩展

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

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

  1. 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例程无需关心。

  1. 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 */

  1. 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 */

  1. 内核态的结构化异常处理

参[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机制 挽救,这与用户态不同。

  1. 丢弃初始化例程、控制驱动程序分页

参看[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$" 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不同,可以成功加载,设备管理器中无异常。

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


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] "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):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 相关。

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

☆ 卸载loopback.sys及相关设置

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

执行"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"子键!

通过设备管理器(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 \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

  1. 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] <> - 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] <> - Chris Cant

[ 8] Using SoftICE.pdf