标题: MSDN系列(12)--KMD(Kernel Mode Driver)的编写安装与调试 创建: 2004-07-06 20:17 更新: 2004-07-12 14:47 链接: https://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进行源码级调试 ☆ 后记 ☆ 参考资源 -------------------------------------------------------------------------- ☆ 概述 在<>中已经介绍过编写驱动的基本步骤。 当时那个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 */ #include /************************************************************************ * * * 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 ); /* * 参看<>中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; /* * 参<>第三章 * 中"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$" 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文件的讨论参看<>。 ☆ 安装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的符号链接, 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文件,恢复到初始 状态。 参<>。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 #include #include #include /************************************************************************ * * * 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,也可用\\。还应该尝试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 */ /************************************************************************/ -------------------------------------------------------------------------- 参看<>了解更多讨论。这次 在本地使用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 #include #include #include /************************************************************************ * * * 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] <> - P. Viscarola, W. Mason [ 2] <> - Art Baker, Jerry Lozano [ 3] <> - Walter Oney [ 4] <> - David A. Solomon, Mark E. Russinovich [ 5] DebugView http://www.sysinternals.com [ 6] <> <> [ 7] <> - Chris Cant [ 8] Using SoftICE.pdf