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

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

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

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

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

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

☆ 概述

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

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

☆ 编写loopback.c文件

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

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

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

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

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

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

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

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

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

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

#endif

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

下面是一些备忘记录:

1) KdPrint

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

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

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

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

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

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

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

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

/Device/LoopbackInternal0
/??/LoopbackExternal1

3) 池(Pool)

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

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

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

4) CRT/RTL

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

5) Unload

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

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

6) UNICODE_STRING

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

7) 设备扩展

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

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

8) IRP

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

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

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

9) IoCompleteRequest

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

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

10) DRIVER_OBJECT/DEVICE_OBJECT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

☆ 编写dirs文件

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

dirs、sources、makefile

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

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

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


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

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

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

☆ 编写sources文件

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

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

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

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

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

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

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

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

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

☆ 编写makefile文件

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

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

☆ 编译产生loopback.sys文件

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

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

    2 files compiled
    1 executable built

☆ 编写loopback.inf文件

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

;
; Copyright to satisfy the CHKINF utility
;

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

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

[ClassInstall32.ntx86]
AddReg              = ClassInstall32Reg

[ClassInstall32Reg]
HKR,,,,%ClassName%

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

[Manufacturer]
%INFCreator%        = LoopbackSection

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

[LoopbackSection]
%DESCRIPTION%       = DDInstall,*LOOPBACKDRIVER1993

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

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

[SourceDisksFiles.x86]
loopback.sys        = 1

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

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

[DDInstall.ntx86]
Copyfiles           = FileList

[FileList]
loopback.sys,,,0x00000002

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

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

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

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

[DDInstall.ntx86.LOOPBACK]
AddReg              = DDInstallRegLOOPBACK

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

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

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

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

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

☆ 安装loopback.inf文件

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

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

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

1)

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

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

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

2)

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

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

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

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

3)

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

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

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

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

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

4)

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

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

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

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

☆ 卸载loopback.sys及相关设置

1)

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

2)

执行"sc delete Loopback":

> sc delete Loopback
[SC] DeleteService SUCCESS

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

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

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

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

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

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

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

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

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

3)

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

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

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

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

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

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

☆ installdriver.c

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

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

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

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

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

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

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

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

/*
 * #define _WIN32_WINNT 0x0501
 */

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

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

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

#define VERSION "1.00"

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

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

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

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

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

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

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

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

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

main_exit:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

☆ 察看KdPrint输出

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

也可以启动DriverStudio所带DriverMonitor。

☆ loopbacktest.c

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

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

/*
 * #define _WIN32_WINNT 0x0501
 */

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

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

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

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

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

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

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

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

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

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

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

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

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

main_exit:

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

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

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

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

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

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

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

> set NTDEBUG
NTDEBUG=ntsd
NTDEBUGTYPE=both

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

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

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

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

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

onlytemp> nmsym.exe /symload:loopback.nms

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

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

onlytemp> nmsym.exe /unload:loopback.nms

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

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

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

onlytemp> net start Loopback

在SoftICE中执行如下操作:

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

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

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

onlytemp> loopbacktest -m NSFOCUS

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

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

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

:watch Irp
:watch DeviceObject

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

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

☆ 后记

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

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

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

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

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

☆ 参考资源

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

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

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

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

[ 5] DebugView
     http://www.sysinternals.com

[ 6] <<MSDN系列(2)--学习写一个Hello World驱动>>
     <<MSDN系列(8)--学习写一个Hello World服务(Service)>>

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

[ 8] Using SoftICE.pdf
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值