创建: 2004-07-06 20:17
更新: 2004-07-12 14:47
链接: http://www.opencjk.org/~scz/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编译。
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文件的讨论参看<<MSDN系列(2)--学习写一个Hello World驱动>>。
☆ 安装loopback.inf文件
->控制面板
->添加硬件(或者直接在cmd中运行hdwwiz.cpl)
->是,硬件已联接好
->添加新的硬件设备
->安装我手动从列表选择的硬件(高级)
->显示所有设备
->从磁盘安装
->完成
hello.c最后故意失败返回,安装虽然完成,但加载驱动必然失败。在设备管理器中(
直接在cmd中运行devmgmt.msc)可以看到黄色惊叹号,表示无法成功加载驱动程序。
但loopback.sys不同,可以成功加载,设备管理器中无异常。
这次安装过程导致如下一些结果:
1)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------
2)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
"ActiveService"="Loopback"
--------------------------------------------------------------------------
3)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="ROOT//UNKNOWN//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------
安装loopback.inf文件之后,无论是否重启,注册表中都只有这三处与loopback.sys
相关。
4)
复制loopback.sys到%systemroot%/system32/drivers/目录下。查看setupapi.log文
件:
--------------------------------------------------------------------------
[2004/07/09 14:31:09 1592.430]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I140 正在安装设备类别: "LoopbackClass" {5675F8A0-FB7C-40CF-BA53-9233A183BD7E}。
#I141 类别安装已结束,没有出错。
[2004/07/09 14:30:20 1592.427 Driver Install]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I393 更改过的 INF 缓存 "X:/WINDOWS/inf/INFCACHE.1"。
#-124 正在做“仅复制”安装 "ROOT/UNKNOWN/0000"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#W187 安装失败,试图还原源文件。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-024 正在将文件 "c:/onlytemp/loopback.sys" 复制到 "X:/WINDOWS/system32/drivers/loopback.sys"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-166 设备安装函数: DIF_REGISTER_COINSTALLERS。
#I056 注册了共同安装程序。
#-166 设备安装函数: DIF_INSTALLINTERFACES。
#-011 正在从 "x:/loopback.inf" 安装段 [DDInstall.NTx86.Interfaces]。
#I054 安装接口。
#-166 设备安装函数: DIF_INSTALLDEVICE。
#I123 进行 "ROOT/UNKNOWN/0000" 的完整安装。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#I121 "ROOT/UNKNOWN/0000" 的设备安装成功完成。
--------------------------------------------------------------------------
不要在资源管理器中右键选中loopback.inf,点击菜单里的"安装/Install"。
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet只是到ControlSet<nnn>的符号链接,
HKEY_LOCAL_MACHINE/SYSTEM/Select用于具体确定是到谁的符号链接。启动时F8菜单
有一个选项对应最近一次成功引导所用设置,就是由LastKnownGood决定的。参看[4]
的5.2.6小节。
☆ 卸载loopback.sys及相关设置
1)
完全手工卸载,从注册表中删除前述三处内容,从drivers中删除loopback.sys。
2)
执行"sc delete Loopback":
> sc delete Loopback
[SC] DeleteService SUCCESS
此时设备管理器中仍有相应显示,"重启后"注册表中还有如下内容,需要手工删除。
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
--------------------------------------------------------------------------
drivers/loopback.sys也未删除,需要手工删除。对比卸载hello.sys时的情形,区
别较大,不过当时hello.sys加载失败,这回loopback.sys加载成功。
"sc delete"只处理了"Services/Loopback"子键!
3)
通过设备管理器(devmgmt.msc)卸载,系统提示重启,之后注册表中还有如下内容,
需要手工删除。
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
--------------------------------------------------------------------------
> del %systemroot%/system32/drivers/loopback.sys
☆ installdriver.c
通过INF文件安装loopback.sys之后,有很多不爽的事情。
> drivers.exe | find "loopback"
loopback.sys 128 0 0 1536 1920 Fri Jul 09 13:10:00 2004
执行上述命令确认loopback.sys已经成功加载。但是不能动态卸载:
> net stop Loopback
The requested pause or stop is not valid for this service.
> net start Loopback
The requested service has already been started.
在devmgmt.msc中对"The Loopback Driver"选择停用、启用、卸载均提示要重启才行,
显然这与我初始愿望相违背。最终从设备管理器中卸载了"The Loopback Driver",
重启后从注册表中删除残留内容,手工删除drivers/loopback.sys文件,恢复到初始
状态。
参<<MSDN系列(8)--学习写一个Hello World服务(Service)>>。installservice.c用
于安装loopback.sys再好不过,不过得作一处小改动,上次是针对服务,这次是针对
驱动。
--------------------------------------------------------------------------
/*
* Version : 1.02
* Compile : For x86/EWindows XP SP1 & VC 7
* : cl installdriver.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
* :
* Create : 2003-12-08 16:47
* Modify : 2004-07-09 17:33
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
/*
* #define _WIN32_WINNT 0x0501
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/INCREMENTAL:NO" )
#pragma comment( linker, "/subsystem:console" )
#pragma comment( lib, "kernel32.lib" )
#pragma comment( lib, "advapi32.lib" )
#define VERSION "1.00"
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void PrintWin32ErrorCLI
(
char *message,
DWORD dwMessageId
);
static void usage
(
char *arg
);
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char *errMsg;
FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
} /* end of PrintWin32ErrorCLI */
static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-t target] [-s servicename] [-d displayname] [-c cmdline]/n",
arg
);
exit( EXIT_FAILURE );
} /* end of usage */
int __cdecl main ( int argc, char * argv[] )
{
SC_HANDLE scm = ( SC_HANDLE )NULL,
sc_handle = ( SC_HANDLE )NULL;
unsigned char *target = NULL,
*servicename = NULL,
*displayname = NULL,
*cmdline = NULL;
int c,
ret = EXIT_FAILURE;
if ( 1 == argc )
{
usage( argv[0] );
}
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'c':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
cmdline = argv[++c];
break;
case 'd':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
displayname = argv[++c];
break;
case 's':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
servicename = argv[++c];
break;
case 't':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
target = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
} /* end of switch */
}
} /* end of for */
/*
* 检查参数有效性
*/
if ( NULL == servicename )
{
fprintf( stderr, "Checking your [-s servicename]/n" );
return( EXIT_FAILURE );
}
if ( NULL == displayname )
{
fprintf( stderr, "Checking your [-d displayname]/n" );
return( EXIT_FAILURE );
}
if ( NULL == cmdline )
{
fprintf( stderr, "Checking your [-c cmdline]/n" );
return( EXIT_FAILURE );
}
/*
* Header : Declared in Winsvc.h; include Windows.h.
* Library: Use Advapi32.lib.
*
* SC_HANDLE OpenSCManager
* (
* LPCTSTR lpMachineName, // computer name
* LPCTSTR lpDatabaseName, // SCM database name
* DWORD dwDesiredAccess // access type
* );
*
* 第一形参可以用target,也可用//<target>。还应该尝试unicodeserver。
*/
scm = OpenSCManager
(
target,
SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CREATE_SERVICE
);
if ( NULL == scm )
{
PrintWin32ErrorCLI( "OpenSCManager() failed", GetLastError() );
goto main_exit;
}
/*
* SC_HANDLE CreateService
* (
* SC_HANDLE hSCManager, // handle to SCM database
* LPCTSTR lpServiceName, // name of service to start
* LPCTSTR lpDisplayName, // display name
* DWORD dwDesiredAccess, // type of access to service
* DWORD dwServiceType, // type of service
* DWORD dwStartType, // when to start service
* DWORD dwErrorControl, // severity of service failure
* LPCTSTR lpBinaryPathName, // name of binary file
* LPCTSTR lpLoadOrderGroup, // name of load ordering group
* LPDWORD lpdwTagId, // tag identifier
* LPCTSTR lpDependencies, // array of dependency names
* LPCTSTR lpServiceStartName, // account name
* LPCTSTR lpPassword // account password
* );
*/
sc_handle = CreateService
(
scm,
servicename,
displayname,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
cmdline,
NULL,
NULL,
NULL,
NULL,
NULL
);
if ( NULL == sc_handle )
{
PrintWin32ErrorCLI( "CreateService() failed", GetLastError() );
goto main_exit;
}
ret = EXIT_SUCCESS;
main_exit:
if ( NULL != sc_handle )
{
CloseServiceHandle( sc_handle );
sc_handle = ( SC_HANDLE )NULL;
}
if ( NULL != scm )
{
CloseServiceHandle( scm );
scm = ( SC_HANDLE )NULL;
}
return( ret );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
参看<<MSDN系列(8)--学习写一个Hello World服务(Service)>>了解更多讨论。这次
在本地使用installdriver.exe,注意要指定loopback.sys的绝对路径,观察注册表
中ImagePath键值的数据,一般是system32/drivers/loopback.sys。
> installdriver.exe -s Loopback -d Loopback -c <Absolute Path>/loopback.sys
> net start Loopback
The Loopback service was started successfully.
> drivers.exe | findstr /I loopback
loopback.sys 0 0 0 0 0
此时注册表中新增了两处内容:
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK]
"NextInstance"=dword:00000001
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000]
"Service"="Loopback"
"Legacy"=dword:00000001
"ConfigFlags"=dword:00000000
"Class"="LegacyDriver"
"ClassGUID"="{8ECC055D-047F-11D1-A537-0000F8753ED1}"
"DeviceDesc"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000/Control]
"*NewlyCreated*"=dword:00000000
"ActiveService"="Loopback"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):5c,00,3f,00,3f,00,5c,00,63,00,3a,00,5c,00,6f,00,6e,00,6c,00,/
79,00,74,00,65,00,6d,00,70,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,/
00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="Root//LEGACY_LOOPBACK//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------
ImagePath键值的数据现在是"/??/c:/onlytemp/loopback.sys"。
另有一处一直存在非新增但相关的内容:
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}]
"Class"="LegacyDriver"
@="Non-Plug and Play Drivers"
"NoDisplayClass"="1"
"SilentInstall"="1"
"NoInstallClass"="1"
"EnumPropPages32"="SysSetup.Dll,LegacyDriverPropPageProvider"
"Icon"="-19"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}/0000]
--------------------------------------------------------------------------
> net stop Loopback
The Loopback service was stopped successfully.
> sc delete Loopback
[SC] DeleteService SUCCESS
这样省事得多,不过前面的loopback.inf文件还是保留备忘,那是WDM驱动正宗安装
方案,我们这次是KMD。
☆ 察看KdPrint输出
启动DebugView([5]),在Capture菜单里勾中Capture kernel、Pass-Through、
Capture Events即可,扔到一边别理它。
也可以启动DriverStudio所带DriverMonitor。
☆ loopbacktest.c
--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7
* cl loopbacktest.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
/*
* #define _WIN32_WINNT 0x0501
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/INCREMENTAL:NO" )
#pragma comment( linker, "/subsystem:console" )
#define VERSION "1.00"
#define DEFAULTMESSAGE "LOOPBACK"
#define EXTERNALNAME ".//LoopbackExternal1"
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void PrintWin32ErrorCLI
(
char *message,
DWORD dwMessageId
);
static void usage
(
char *arg
);
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char *errMsg;
FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
} /* end of PrintWin32ErrorCLI */
static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-m message]/n",
arg
);
exit( EXIT_FAILURE );
} /* end of usage */
int __cdecl main ( int argc, char * argv[] )
{
unsigned char *message = DEFAULTMESSAGE;
unsigned int length;
unsigned char *buf = NULL;
int c,
ret = EXIT_FAILURE;
HANDLE Device = INVALID_HANDLE_VALUE;
DWORD NumberOfBytes;
#if 0
if ( 1 == argc )
{
usage( argv[0] );
}
#endif
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'm':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
message = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
} /* end of switch */
}
} /* end of for */
/*
* HANDLE CreateFile
* (
* LPCTSTR lpFileName, // file name
* DWORD dwDesiredAccess, // access mode
* DWORD dwShareMode, // share mode
* LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
* DWORD dwCreationDisposition, // how to create
* DWORD dwFlagsAndAttributes, // file attributes
* HANDLE hTemplateFile // handle to template file
* );
*
* If the function fails, the return value is INVALID_HANDLE_VALUE. To
* get extended error information, call GetLastError.
*/
Device = CreateFile
(
EXTERNALNAME,
GENERIC_READ | GENERIC_WRITE,
0,
NULL, // If lpSecurityAttributes is NULL, the handle cannot be inherited
OPEN_EXISTING, // The function fails if the file does not exist
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( INVALID_HANDLE_VALUE == Device )
{
PrintWin32ErrorCLI( "CreateFile() failed", GetLastError() );
goto main_exit;
}
length = strlen( message ) + 1;
/*
* BOOL WriteFile
* (
* HANDLE hFile, // handle to file
* LPCVOID lpBuffer, // data buffer
* DWORD nNumberOfBytesToWrite, // number of bytes to write
* LPDWORD lpNumberOfBytesWritten, // number of bytes written
* LPOVERLAPPED lpOverlapped // overlapped buffer
* );
*/
if
(
FALSE == WriteFile
(
Device,
message,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "WriteFile() failed", GetLastError() );
goto main_exit;
}
/*
* 分配缓冲区
*
* HANDLE GetProcessHeap ( VOID );
*
* The handle obtained by calling this function should not be used in
* calls to the HeapDestroy function.
*
* Header : Declared in Winnls.h; include Windows.h.
* Library: Use Kernel32.lib.
*
* LPVOID HeapAlloc
* (
* HANDLE hHeap, // handle to private heap block
* DWORD dwFlags, // heap allocation control
* SIZE_T dwBytes // number of bytes to allocate
* );
*
* If the function fails, it does not call SetLastError. An
* application cannot call GetLastError for extended error information.
*/
buf = ( unsigned char * )HeapAlloc
(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
length
);
if ( NULL == buf )
{
PrintWin32ErrorCLI( "HeapAlloc() failed for buf", ERROR_NOT_ENOUGH_MEMORY );
goto main_exit;
}
/*
* BOOL ReadFile
* (
* HANDLE hFile, // handle to file
* LPVOID lpBuffer, // data buffer
* DWORD nNumberOfBytesToRead, // number of bytes to read
* LPDWORD lpNumberOfBytesRead, // number of bytes read
* LPOVERLAPPED lpOverlapped // overlapped buffer
* );
*/
if
(
FALSE == ReadFile
(
Device,
buf,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "ReadFile() failed", GetLastError() );
goto main_exit;
}
if ( length == NumberOfBytes )
{
printf( "[%s]/n", buf );
}
ret = EXIT_SUCCESS;
main_exit:
if ( NULL != buf )
{
HeapFree( GetProcessHeap(), 0, buf );
buf = NULL;
}
if ( INVALID_HANDLE_VALUE != Device )
{
CloseHandle( Device );
Device = INVALID_HANDLE_VALUE;
}
return( ret );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
> loopbacktest.exe -m "NSFOCUS"
[NSFOCUS]
☆ 用SoftICE对loopback.sys进行源码级调试
下述步骤是充分非必要的,换句话说,我是这样做的,并且成功了,但不是必须这样
操作。
一定要用"Win XP Checked Build Environment"编译。编译前先确认两个环境变量的
设置,第二个可以设置成"NTDEBUGTYPE=windbg",也可以保持不变。
> set NTDEBUG
NTDEBUG=ntsd
NTDEBUGTYPE=both
我的DDK开发环境在VMware Host上,调试环境则在VMware Guest上。将loopback.c、
loopback.pdb、loopback.sys复制到VMware Guest上同一目录下,比如onlytemp下。
用installdriver.exe安装loopback.sys。VMware Guest上PATH环境变量已经包含
"C:/Program Files/Compuware/DriverStudio/SoftICE",其下含有nmsym.exe。
--------------------------------------------------------------------------
onlytemp> installdriver.exe -s Loopback -d Loopback -c c:/onlytemp/loopback.sys
onlytemp> nmsym.exe /translate:always,source,package /output:loopback.nms loopback.sys
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
MODULE=loopback.sys
OUTPUT=loopback.nms
PROMPT=OFF
Translation of C:/onlytemp/loopback.sys successfully completed
onlytemp> nmsym.exe /symload:loopback.nms
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
SYMLOAD=loopback.nms
PROMPT=OFF
Symbols for loopback.nms successfully loaded
onlytemp> nmsym.exe /unload:loopback.nms
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
UNLOAD=loopback.nms
PROMPT=OFF
Error removing symbol table loopback.nms: Item not found (status)
--------------------------------------------------------------------------
在命令行上用nmsym.exe卸载loopback.nms时会看到一条错误信息,不要理会它,进
SoftICE用table命令确认已经卸载成功。重新加载loopback.nms,在DriverEntry处
设置断点"bpx loopback!DriverEntry",在VMware Guest中加载loopback.sys,远程
SoftICE弹出。
onlytemp> net start Loopback
在SoftICE中执行如下操作:
:file *
loopback.c
:file loopback.c
:wc
:.
现在代码窗口中是loopback.c源代码,光条停留在DriverEntry的左花括号上。用src
命令或F3快捷键可以切换代码窗口的显示方式,有三种方式,纯源代码显示、混合显
示、汇编显示。有时切换显示方式时无法回到纯源代码显示,可再次使用"file .c"
恢复正常。顺便说一句,远程SoftICE的代码窗口可以看到中文注释,只要siremote
这边可以显示中文即可,本地SoftICE就没这好事了。
至此可以用SoftICE对loopback.sys进行源码级调试。KdPrint输出直接在SoftICE命
令窗口中显示,可以用F10、F8这些快捷键。
onlytemp> loopbacktest -m NSFOCUS
上述测试程序将激活LoDispatchCreate(),在SoftICE中下断点:
:bpx loopback!LoDispatchCreate
:bl
00) BPX DriverEntry
01) BPX LoDispatchCreate
:g
Break due to BP 01: BPX LoDispatchCreate
:p
最后这个P命令不可缺少,否则EBP尚未设置好,而形参、局部变量靠EBP定位,在混
合显示中很明显。现在可以用watch命令观察形参DeviceObject、Irp:
:watch Irp
:watch DeviceObject
选中那些带+号的条目,用回车展开后查看更详细的内容。不想观察时可用DEL删除相
应条目。
loopback.sys本身没有什么好调试的,只是通过这个例子感性地说明如何用SoftICE
对驱动进行源码级调试。loopback.sys是手工加载的,可以这样调试。对于那些启动
时加载的驱动程序,需要动用Initialization设置以及icepack.exe,没这需求,就
不去测试了。
☆ 后记
驱动相关结构的描述未出现在文中,那不是重点。工欲善其事必先利其器,重点是建
立感性认识、介绍调试步骤。有了这个基础,剩下的事属于八仙过海了。至少可在本
文基础上编译别人的源代码、进行常规测试以及简单调试。否则给你源代码,也是老
虎吃天的架势。
估计我适合做一些面向与我一样低智商的人群的计算机科普工作。比如以前没干过某
事,又一直很想感性地接触一下,需要一些"Step By Step"教程。记录这个过程,因
为想到肯定有人一样不务正业之余想折腾一下这个方向,但又找不到一条龙式的教程。
我承认这篇水文没有技术含量,但所写下的每一个字都是急于入门时切身体会下的字,
想必还是对另一些人有些微用处。如果真是那样,我将非常开心。
在Google中可以找到大量收费的驱动开发学习班,但甚少入门级教程。驱动开发的门
槛是人为抬高的,这么说,肯定有人不高兴了,觉得我连边都没摸着就狂得下这个结
论,嘿嘿。真是不好意思,我虽然没吃过天鹅肉,但肯定在广州动物园看见过天鹅。
所以对某些人的心性还是有所了解的。再说我也相信rain说的话,都是死框架加硬件
规范。话分两头,那些不是业余折腾驱动的人,不妨有骨气些,别个掖着不露就自己
看文档呗,瞎子摸象摸十年也该有个轮廓了,求人不如自力更生。当然,有得可求教
之人,不求教是白不求教。
下面这些参考资源是上周写本文时用到的。一般有URL的,绝不会故意漏去,只是这
次比较特殊,有四本是纸版书([1]、[2]、[4]、[7]),无从列出URL啊。
结语是这样的,在"三个代表"的指引下,我编写驱动的能力有了很大提高,!@#$%^&*
☆ 参考资源
[ 1] <<Windows NT Device Driver Development>> - P. Viscarola, W. Mason
[ 2] <<The Windows 2000 Device Driver Book, Second Edition>> - Art Baker, Jerry Lozano
[ 3] <<Programming the Microsoft Windows Driver Model, 2nd Ed>> - Walter Oney
[ 4] <<Inside Microsoft Windows 2000 Third Edition>> - David A. Solomon, Mark E. Russinovich
[ 5] DebugView
http://www.sysinternals.com
[ 6] <<MSDN系列(2)--学习写一个Hello World驱动>>
<<MSDN系列(8)--学习写一个Hello World服务(Service)>>
[ 7] <<Writing Windows WDM Device Drivers>> - Chris Cant
[ 8] Using SoftICE.pdf
更新: 2004-07-12 14:47
链接: http://www.opencjk.org/~scz/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编译。
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文件的讨论参看<<MSDN系列(2)--学习写一个Hello World驱动>>。
☆ 安装loopback.inf文件
->控制面板
->添加硬件(或者直接在cmd中运行hdwwiz.cpl)
->是,硬件已联接好
->添加新的硬件设备
->安装我手动从列表选择的硬件(高级)
->显示所有设备
->从磁盘安装
->完成
hello.c最后故意失败返回,安装虽然完成,但加载驱动必然失败。在设备管理器中(
直接在cmd中运行devmgmt.msc)可以看到黄色惊叹号,表示无法成功加载驱动程序。
但loopback.sys不同,可以成功加载,设备管理器中无异常。
这次安装过程导致如下一些结果:
1)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------
2)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
"ActiveService"="Loopback"
--------------------------------------------------------------------------
3)
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="ROOT//UNKNOWN//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------
安装loopback.inf文件之后,无论是否重启,注册表中都只有这三处与loopback.sys
相关。
4)
复制loopback.sys到%systemroot%/system32/drivers/目录下。查看setupapi.log文
件:
--------------------------------------------------------------------------
[2004/07/09 14:31:09 1592.430]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I140 正在安装设备类别: "LoopbackClass" {5675F8A0-FB7C-40CF-BA53-9233A183BD7E}。
#I141 类别安装已结束,没有出错。
[2004/07/09 14:30:20 1592.427 Driver Install]
#-198 处理的命令行: "X:/WINDOWS/system32/rundll32.exe" shell32.dll,Control_RunDLL "X:/WINDOWS/System32/hdwwiz.cpl",添加硬件
#I393 更改过的 INF 缓存 "X:/WINDOWS/inf/INFCACHE.1"。
#-124 正在做“仅复制”安装 "ROOT/UNKNOWN/0000"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#W187 安装失败,试图还原源文件。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-024 正在将文件 "c:/onlytemp/loopback.sys" 复制到 "X:/WINDOWS/system32/drivers/loopback.sys"。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#-166 设备安装函数: DIF_REGISTER_COINSTALLERS。
#I056 注册了共同安装程序。
#-166 设备安装函数: DIF_INSTALLINTERFACES。
#-011 正在从 "x:/loopback.inf" 安装段 [DDInstall.NTx86.Interfaces]。
#I054 安装接口。
#-166 设备安装函数: DIF_INSTALLDEVICE。
#I123 进行 "ROOT/UNKNOWN/0000" 的完整安装。
#E360 驱动程序 "The Loopback Driver" 的一个未经过签署或签署不正确的文件 "x:/loopback.inf" 将得到安装(策略=忽略)。 错误 0xe000022f: 第三方 INF 不包含数字签名信息。
#I121 "ROOT/UNKNOWN/0000" 的设备安装成功完成。
--------------------------------------------------------------------------
不要在资源管理器中右键选中loopback.inf,点击菜单里的"安装/Install"。
HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet只是到ControlSet<nnn>的符号链接,
HKEY_LOCAL_MACHINE/SYSTEM/Select用于具体确定是到谁的符号链接。启动时F8菜单
有一个选项对应最近一次成功引导所用设置,就是由LastKnownGood决定的。参看[4]
的5.2.6小节。
☆ 卸载loopback.sys及相关设置
1)
完全手工卸载,从注册表中删除前述三处内容,从drivers中删除loopback.sys。
2)
执行"sc delete Loopback":
> sc delete Loopback
[SC] DeleteService SUCCESS
此时设备管理器中仍有相应显示,"重启后"注册表中还有如下内容,需要手工删除。
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}/0000]
"InfPath"="oem8.inf"
"InfSection"="DDInstall"
"InfSectionExt"=".NTx86"
"ProviderName"="The Loopback Software"
"DriverDateData"=hex:00,40,31,2e,ec,62,c4,01
"DriverDate"="7-6-2004"
"DriverVersion"="1.0.1993.9"
"MatchingDeviceId"="*loopbackdriver1993"
"DriverDesc"="The Loopback Driver"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000]
"ClassGUID"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}"
"ConfigFlags"=dword:00000004
"Driver"="{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}//0000"
"Class"="LoopbackClass"
"Mfg"="The Loopback Software"
"HardwareID"=hex(7):2a,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,00,6b,00,64,/
00,72,00,69,00,76,00,65,00,72,00,31,00,39,00,39,00,33,00,00,00,00,00
"Service"="Loopback"
"DeviceDesc"="The Loopback Driver"
"Capabilities"=dword:00000000
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/LogConf]
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/UNKNOWN/0000/Control]
--------------------------------------------------------------------------
drivers/loopback.sys也未删除,需要手工删除。对比卸载hello.sys时的情形,区
别较大,不过当时hello.sys加载失败,这回loopback.sys加载成功。
"sc delete"只处理了"Services/Loopback"子键!
3)
通过设备管理器(devmgmt.msc)卸载,系统提示重启,之后注册表中还有如下内容,
需要手工删除。
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{5675F8A0-FB7C-40CF-BA53-9233A183BD7E}]
"Class"="LoopbackClass"
@="LoopbackClass"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,64,00,/
72,00,69,00,76,00,65,00,72,00,73,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,/
00,63,00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
--------------------------------------------------------------------------
> del %systemroot%/system32/drivers/loopback.sys
☆ installdriver.c
通过INF文件安装loopback.sys之后,有很多不爽的事情。
> drivers.exe | find "loopback"
loopback.sys 128 0 0 1536 1920 Fri Jul 09 13:10:00 2004
执行上述命令确认loopback.sys已经成功加载。但是不能动态卸载:
> net stop Loopback
The requested pause or stop is not valid for this service.
> net start Loopback
The requested service has already been started.
在devmgmt.msc中对"The Loopback Driver"选择停用、启用、卸载均提示要重启才行,
显然这与我初始愿望相违背。最终从设备管理器中卸载了"The Loopback Driver",
重启后从注册表中删除残留内容,手工删除drivers/loopback.sys文件,恢复到初始
状态。
参<<MSDN系列(8)--学习写一个Hello World服务(Service)>>。installservice.c用
于安装loopback.sys再好不过,不过得作一处小改动,上次是针对服务,这次是针对
驱动。
--------------------------------------------------------------------------
/*
* Version : 1.02
* Compile : For x86/EWindows XP SP1 & VC 7
* : cl installdriver.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
* :
* Create : 2003-12-08 16:47
* Modify : 2004-07-09 17:33
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
/*
* #define _WIN32_WINNT 0x0501
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/INCREMENTAL:NO" )
#pragma comment( linker, "/subsystem:console" )
#pragma comment( lib, "kernel32.lib" )
#pragma comment( lib, "advapi32.lib" )
#define VERSION "1.00"
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void PrintWin32ErrorCLI
(
char *message,
DWORD dwMessageId
);
static void usage
(
char *arg
);
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char *errMsg;
FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
} /* end of PrintWin32ErrorCLI */
static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-t target] [-s servicename] [-d displayname] [-c cmdline]/n",
arg
);
exit( EXIT_FAILURE );
} /* end of usage */
int __cdecl main ( int argc, char * argv[] )
{
SC_HANDLE scm = ( SC_HANDLE )NULL,
sc_handle = ( SC_HANDLE )NULL;
unsigned char *target = NULL,
*servicename = NULL,
*displayname = NULL,
*cmdline = NULL;
int c,
ret = EXIT_FAILURE;
if ( 1 == argc )
{
usage( argv[0] );
}
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'c':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
cmdline = argv[++c];
break;
case 'd':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
displayname = argv[++c];
break;
case 's':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
servicename = argv[++c];
break;
case 't':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
target = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
} /* end of switch */
}
} /* end of for */
/*
* 检查参数有效性
*/
if ( NULL == servicename )
{
fprintf( stderr, "Checking your [-s servicename]/n" );
return( EXIT_FAILURE );
}
if ( NULL == displayname )
{
fprintf( stderr, "Checking your [-d displayname]/n" );
return( EXIT_FAILURE );
}
if ( NULL == cmdline )
{
fprintf( stderr, "Checking your [-c cmdline]/n" );
return( EXIT_FAILURE );
}
/*
* Header : Declared in Winsvc.h; include Windows.h.
* Library: Use Advapi32.lib.
*
* SC_HANDLE OpenSCManager
* (
* LPCTSTR lpMachineName, // computer name
* LPCTSTR lpDatabaseName, // SCM database name
* DWORD dwDesiredAccess // access type
* );
*
* 第一形参可以用target,也可用//<target>。还应该尝试unicodeserver。
*/
scm = OpenSCManager
(
target,
SERVICES_ACTIVE_DATABASE,
SC_MANAGER_CREATE_SERVICE
);
if ( NULL == scm )
{
PrintWin32ErrorCLI( "OpenSCManager() failed", GetLastError() );
goto main_exit;
}
/*
* SC_HANDLE CreateService
* (
* SC_HANDLE hSCManager, // handle to SCM database
* LPCTSTR lpServiceName, // name of service to start
* LPCTSTR lpDisplayName, // display name
* DWORD dwDesiredAccess, // type of access to service
* DWORD dwServiceType, // type of service
* DWORD dwStartType, // when to start service
* DWORD dwErrorControl, // severity of service failure
* LPCTSTR lpBinaryPathName, // name of binary file
* LPCTSTR lpLoadOrderGroup, // name of load ordering group
* LPDWORD lpdwTagId, // tag identifier
* LPCTSTR lpDependencies, // array of dependency names
* LPCTSTR lpServiceStartName, // account name
* LPCTSTR lpPassword // account password
* );
*/
sc_handle = CreateService
(
scm,
servicename,
displayname,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
cmdline,
NULL,
NULL,
NULL,
NULL,
NULL
);
if ( NULL == sc_handle )
{
PrintWin32ErrorCLI( "CreateService() failed", GetLastError() );
goto main_exit;
}
ret = EXIT_SUCCESS;
main_exit:
if ( NULL != sc_handle )
{
CloseServiceHandle( sc_handle );
sc_handle = ( SC_HANDLE )NULL;
}
if ( NULL != scm )
{
CloseServiceHandle( scm );
scm = ( SC_HANDLE )NULL;
}
return( ret );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
参看<<MSDN系列(8)--学习写一个Hello World服务(Service)>>了解更多讨论。这次
在本地使用installdriver.exe,注意要指定loopback.sys的绝对路径,观察注册表
中ImagePath键值的数据,一般是system32/drivers/loopback.sys。
> installdriver.exe -s Loopback -d Loopback -c <Absolute Path>/loopback.sys
> net start Loopback
The Loopback service was started successfully.
> drivers.exe | findstr /I loopback
loopback.sys 0 0 0 0 0
此时注册表中新增了两处内容:
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK]
"NextInstance"=dword:00000001
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000]
"Service"="Loopback"
"Legacy"=dword:00000001
"ConfigFlags"=dword:00000000
"Class"="LegacyDriver"
"ClassGUID"="{8ECC055D-047F-11D1-A537-0000F8753ED1}"
"DeviceDesc"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Enum/Root/LEGACY_LOOPBACK/0000/Control]
"*NewlyCreated*"=dword:00000000
"ActiveService"="Loopback"
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback]
"Type"=dword:00000001
"Start"=dword:00000003
"ErrorControl"=dword:00000001
"ImagePath"=hex(2):5c,00,3f,00,3f,00,5c,00,63,00,3a,00,5c,00,6f,00,6e,00,6c,00,/
79,00,74,00,65,00,6d,00,70,00,5c,00,6c,00,6f,00,6f,00,70,00,62,00,61,00,63,/
00,6b,00,2e,00,73,00,79,00,73,00,00,00
"DisplayName"="Loopback"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Security]
"Security"=hex:01,00,14,80,90,00,00,00,9c,00,00,00,14,00,00,00,30,00,00,00,02,/
00,1c,00,01,00,00,00,02,80,14,00,ff,01,0f,00,01,01,00,00,00,00,00,01,00,00,/
00,00,02,00,60,00,04,00,00,00,00,00,14,00,fd,01,02,00,01,01,00,00,00,00,00,/
05,12,00,00,00,00,00,18,00,ff,01,0f,00,01,02,00,00,00,00,00,05,20,00,00,00,/
20,02,00,00,00,00,14,00,8d,01,02,00,01,01,00,00,00,00,00,05,0b,00,00,00,00,/
00,18,00,fd,01,02,00,01,02,00,00,00,00,00,05,20,00,00,00,23,02,00,00,01,01,/
00,00,00,00,00,05,12,00,00,00,01,01,00,00,00,00,00,05,12,00,00,00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/Loopback/Enum]
"0"="Root//LEGACY_LOOPBACK//0000"
"Count"=dword:00000001
"NextInstance"=dword:00000001
--------------------------------------------------------------------------
ImagePath键值的数据现在是"/??/c:/onlytemp/loopback.sys"。
另有一处一直存在非新增但相关的内容:
--------------------------------------------------------------------------
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}]
"Class"="LegacyDriver"
@="Non-Plug and Play Drivers"
"NoDisplayClass"="1"
"SilentInstall"="1"
"NoInstallClass"="1"
"EnumPropPages32"="SysSetup.Dll,LegacyDriverPropPageProvider"
"Icon"="-19"
[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Class/{8ECC055D-047F-11D1-A537-0000F8753ED1}/0000]
--------------------------------------------------------------------------
> net stop Loopback
The Loopback service was stopped successfully.
> sc delete Loopback
[SC] DeleteService SUCCESS
这样省事得多,不过前面的loopback.inf文件还是保留备忘,那是WDM驱动正宗安装
方案,我们这次是KMD。
☆ 察看KdPrint输出
启动DebugView([5]),在Capture菜单里勾中Capture kernel、Pass-Through、
Capture Events即可,扔到一边别理它。
也可以启动DriverStudio所带DriverMonitor。
☆ loopbacktest.c
--------------------------------------------------------------------------
/*
* For x86/EWindows XP SP1 & VC 7
* cl loopbacktest.c /nologo /Os /G6 /Gz /Gs65536 /W3 /WX /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /link /RELEASE
*/
/************************************************************************
* *
* Head File *
* *
************************************************************************/
/*
* #define _WIN32_WINNT 0x0501
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/************************************************************************
* *
* Macro *
* *
************************************************************************/
#pragma comment( linker, "/INCREMENTAL:NO" )
#pragma comment( linker, "/subsystem:console" )
#define VERSION "1.00"
#define DEFAULTMESSAGE "LOOPBACK"
#define EXTERNALNAME ".//LoopbackExternal1"
/************************************************************************
* *
* Function Prototype *
* *
************************************************************************/
static void PrintWin32ErrorCLI
(
char *message,
DWORD dwMessageId
);
static void usage
(
char *arg
);
/************************************************************************
* *
* Static Global Var *
* *
************************************************************************/
/************************************************************************/
static void PrintWin32ErrorCLI ( char *message, DWORD dwMessageId )
{
char *errMsg;
FormatMessage
(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dwMessageId,
MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ),
( LPTSTR )&errMsg,
0,
NULL
);
fprintf( stderr, "%s: %s", message, errMsg );
LocalFree( errMsg );
return;
} /* end of PrintWin32ErrorCLI */
static void usage ( char *arg )
{
fprintf
(
stderr,
"Usage: %s [-h] [-v] [-m message]/n",
arg
);
exit( EXIT_FAILURE );
} /* end of usage */
int __cdecl main ( int argc, char * argv[] )
{
unsigned char *message = DEFAULTMESSAGE;
unsigned int length;
unsigned char *buf = NULL;
int c,
ret = EXIT_FAILURE;
HANDLE Device = INVALID_HANDLE_VALUE;
DWORD NumberOfBytes;
#if 0
if ( 1 == argc )
{
usage( argv[0] );
}
#endif
/*
* 从argv[1]开始循环处理命令行参数
*/
for ( c = 1; c < argc; c++ )
{
/*
* 同时支持-和/两种引入命令行参数的方式
*/
if ( ( ( argv[c][0] != '-' ) && ( argv[c][0] != '/' ) ) || ( strlen( argv[c] ) < 2 ) )
{
usage( argv[0] );
}
else
{
/*
* 在这个字节上,大小写不敏感
*/
switch ( tolower( argv[c][1] ) )
{
case 'm':
if ( ( c + 1 ) >= argc )
{
usage( argv[0] );
}
message = argv[++c];
break;
case 'v':
fprintf( stderr, "%s ver "VERSION"/n", argv[0] );
return( EXIT_SUCCESS );
case 'h':
case '?':
default:
usage( argv[0] );
break;
} /* end of switch */
}
} /* end of for */
/*
* HANDLE CreateFile
* (
* LPCTSTR lpFileName, // file name
* DWORD dwDesiredAccess, // access mode
* DWORD dwShareMode, // share mode
* LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
* DWORD dwCreationDisposition, // how to create
* DWORD dwFlagsAndAttributes, // file attributes
* HANDLE hTemplateFile // handle to template file
* );
*
* If the function fails, the return value is INVALID_HANDLE_VALUE. To
* get extended error information, call GetLastError.
*/
Device = CreateFile
(
EXTERNALNAME,
GENERIC_READ | GENERIC_WRITE,
0,
NULL, // If lpSecurityAttributes is NULL, the handle cannot be inherited
OPEN_EXISTING, // The function fails if the file does not exist
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( INVALID_HANDLE_VALUE == Device )
{
PrintWin32ErrorCLI( "CreateFile() failed", GetLastError() );
goto main_exit;
}
length = strlen( message ) + 1;
/*
* BOOL WriteFile
* (
* HANDLE hFile, // handle to file
* LPCVOID lpBuffer, // data buffer
* DWORD nNumberOfBytesToWrite, // number of bytes to write
* LPDWORD lpNumberOfBytesWritten, // number of bytes written
* LPOVERLAPPED lpOverlapped // overlapped buffer
* );
*/
if
(
FALSE == WriteFile
(
Device,
message,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "WriteFile() failed", GetLastError() );
goto main_exit;
}
/*
* 分配缓冲区
*
* HANDLE GetProcessHeap ( VOID );
*
* The handle obtained by calling this function should not be used in
* calls to the HeapDestroy function.
*
* Header : Declared in Winnls.h; include Windows.h.
* Library: Use Kernel32.lib.
*
* LPVOID HeapAlloc
* (
* HANDLE hHeap, // handle to private heap block
* DWORD dwFlags, // heap allocation control
* SIZE_T dwBytes // number of bytes to allocate
* );
*
* If the function fails, it does not call SetLastError. An
* application cannot call GetLastError for extended error information.
*/
buf = ( unsigned char * )HeapAlloc
(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
length
);
if ( NULL == buf )
{
PrintWin32ErrorCLI( "HeapAlloc() failed for buf", ERROR_NOT_ENOUGH_MEMORY );
goto main_exit;
}
/*
* BOOL ReadFile
* (
* HANDLE hFile, // handle to file
* LPVOID lpBuffer, // data buffer
* DWORD nNumberOfBytesToRead, // number of bytes to read
* LPDWORD lpNumberOfBytesRead, // number of bytes read
* LPOVERLAPPED lpOverlapped // overlapped buffer
* );
*/
if
(
FALSE == ReadFile
(
Device,
buf,
length,
&NumberOfBytes,
NULL
)
)
{
PrintWin32ErrorCLI( "ReadFile() failed", GetLastError() );
goto main_exit;
}
if ( length == NumberOfBytes )
{
printf( "[%s]/n", buf );
}
ret = EXIT_SUCCESS;
main_exit:
if ( NULL != buf )
{
HeapFree( GetProcessHeap(), 0, buf );
buf = NULL;
}
if ( INVALID_HANDLE_VALUE != Device )
{
CloseHandle( Device );
Device = INVALID_HANDLE_VALUE;
}
return( ret );
} /* end of main */
/************************************************************************/
--------------------------------------------------------------------------
> loopbacktest.exe -m "NSFOCUS"
[NSFOCUS]
☆ 用SoftICE对loopback.sys进行源码级调试
下述步骤是充分非必要的,换句话说,我是这样做的,并且成功了,但不是必须这样
操作。
一定要用"Win XP Checked Build Environment"编译。编译前先确认两个环境变量的
设置,第二个可以设置成"NTDEBUGTYPE=windbg",也可以保持不变。
> set NTDEBUG
NTDEBUG=ntsd
NTDEBUGTYPE=both
我的DDK开发环境在VMware Host上,调试环境则在VMware Guest上。将loopback.c、
loopback.pdb、loopback.sys复制到VMware Guest上同一目录下,比如onlytemp下。
用installdriver.exe安装loopback.sys。VMware Guest上PATH环境变量已经包含
"C:/Program Files/Compuware/DriverStudio/SoftICE",其下含有nmsym.exe。
--------------------------------------------------------------------------
onlytemp> installdriver.exe -s Loopback -d Loopback -c c:/onlytemp/loopback.sys
onlytemp> nmsym.exe /translate:always,source,package /output:loopback.nms loopback.sys
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
MODULE=loopback.sys
OUTPUT=loopback.nms
PROMPT=OFF
Translation of C:/onlytemp/loopback.sys successfully completed
onlytemp> nmsym.exe /symload:loopback.nms
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
SYMLOAD=loopback.nms
PROMPT=OFF
Symbols for loopback.nms successfully loaded
onlytemp> nmsym.exe /unload:loopback.nms
Compuware NM32 Symbol Translator/Loader version 1.24
(C) Compuware Technologies, 1996-2001
UNLOAD=loopback.nms
PROMPT=OFF
Error removing symbol table loopback.nms: Item not found (status)
--------------------------------------------------------------------------
在命令行上用nmsym.exe卸载loopback.nms时会看到一条错误信息,不要理会它,进
SoftICE用table命令确认已经卸载成功。重新加载loopback.nms,在DriverEntry处
设置断点"bpx loopback!DriverEntry",在VMware Guest中加载loopback.sys,远程
SoftICE弹出。
onlytemp> net start Loopback
在SoftICE中执行如下操作:
:file *
loopback.c
:file loopback.c
:wc
:.
现在代码窗口中是loopback.c源代码,光条停留在DriverEntry的左花括号上。用src
命令或F3快捷键可以切换代码窗口的显示方式,有三种方式,纯源代码显示、混合显
示、汇编显示。有时切换显示方式时无法回到纯源代码显示,可再次使用"file .c"
恢复正常。顺便说一句,远程SoftICE的代码窗口可以看到中文注释,只要siremote
这边可以显示中文即可,本地SoftICE就没这好事了。
至此可以用SoftICE对loopback.sys进行源码级调试。KdPrint输出直接在SoftICE命
令窗口中显示,可以用F10、F8这些快捷键。
onlytemp> loopbacktest -m NSFOCUS
上述测试程序将激活LoDispatchCreate(),在SoftICE中下断点:
:bpx loopback!LoDispatchCreate
:bl
00) BPX DriverEntry
01) BPX LoDispatchCreate
:g
Break due to BP 01: BPX LoDispatchCreate
:p
最后这个P命令不可缺少,否则EBP尚未设置好,而形参、局部变量靠EBP定位,在混
合显示中很明显。现在可以用watch命令观察形参DeviceObject、Irp:
:watch Irp
:watch DeviceObject
选中那些带+号的条目,用回车展开后查看更详细的内容。不想观察时可用DEL删除相
应条目。
loopback.sys本身没有什么好调试的,只是通过这个例子感性地说明如何用SoftICE
对驱动进行源码级调试。loopback.sys是手工加载的,可以这样调试。对于那些启动
时加载的驱动程序,需要动用Initialization设置以及icepack.exe,没这需求,就
不去测试了。
☆ 后记
驱动相关结构的描述未出现在文中,那不是重点。工欲善其事必先利其器,重点是建
立感性认识、介绍调试步骤。有了这个基础,剩下的事属于八仙过海了。至少可在本
文基础上编译别人的源代码、进行常规测试以及简单调试。否则给你源代码,也是老
虎吃天的架势。
估计我适合做一些面向与我一样低智商的人群的计算机科普工作。比如以前没干过某
事,又一直很想感性地接触一下,需要一些"Step By Step"教程。记录这个过程,因
为想到肯定有人一样不务正业之余想折腾一下这个方向,但又找不到一条龙式的教程。
我承认这篇水文没有技术含量,但所写下的每一个字都是急于入门时切身体会下的字,
想必还是对另一些人有些微用处。如果真是那样,我将非常开心。
在Google中可以找到大量收费的驱动开发学习班,但甚少入门级教程。驱动开发的门
槛是人为抬高的,这么说,肯定有人不高兴了,觉得我连边都没摸着就狂得下这个结
论,嘿嘿。真是不好意思,我虽然没吃过天鹅肉,但肯定在广州动物园看见过天鹅。
所以对某些人的心性还是有所了解的。再说我也相信rain说的话,都是死框架加硬件
规范。话分两头,那些不是业余折腾驱动的人,不妨有骨气些,别个掖着不露就自己
看文档呗,瞎子摸象摸十年也该有个轮廓了,求人不如自力更生。当然,有得可求教
之人,不求教是白不求教。
下面这些参考资源是上周写本文时用到的。一般有URL的,绝不会故意漏去,只是这
次比较特殊,有四本是纸版书([1]、[2]、[4]、[7]),无从列出URL啊。
结语是这样的,在"三个代表"的指引下,我编写驱动的能力有了很大提高,!@#$%^&*
☆ 参考资源
[ 1] <<Windows NT Device Driver Development>> - P. Viscarola, W. Mason
[ 2] <<The Windows 2000 Device Driver Book, Second Edition>> - Art Baker, Jerry Lozano
[ 3] <<Programming the Microsoft Windows Driver Model, 2nd Ed>> - Walter Oney
[ 4] <<Inside Microsoft Windows 2000 Third Edition>> - David A. Solomon, Mark E. Russinovich
[ 5] DebugView
http://www.sysinternals.com
[ 6] <<MSDN系列(2)--学习写一个Hello World驱动>>
<<MSDN系列(8)--学习写一个Hello World服务(Service)>>
[ 7] <<Writing Windows WDM Device Drivers>> - Chris Cant
[ 8] Using SoftICE.pdf