开发 KDNET 传输扩展性模块

本主题介绍如何通过使用单独的硬件驱动程序扩展性模块 dll 将 KDNET 传输扩展到任何硬件上运行。 KDNET 传输扩展性模块由网卡供应商开发,用于为特定网卡添加内核调试支持。

KDNET 概述

KDNET 是一种内核调试传输工具,可通过网络对窗口进行内核调试。 最初,它用于支持以太网 NIC 的内核调试。 在设计上,硬件支持层内置于网络数据包处理和内核接口层的单独模块中。 此硬件驱动程序支持层被称为 KDNET 扩展性模块。

KDNET 是唯一可以通过使用单独的硬件驱动程序扩展性模块 dll 在任意硬件上运行的传输。 目标是通过 KDNET 和 KDNET 扩展性模块来支持 Windows 的所有调试。 所有其他内核传输 DLL(kdcom.dll、kd1394.dll、kdusb.dll 等)最终都将被弃用并从 Windows 中删除。

KDNET 用于与 KDNET 扩展性模块通信的接口有两个。 一个是基于数据包的接口,用于网卡、USB 和无线硬件;另一个是基于字节的接口,用于支持串行硬件上的 KDNET。

KDNET 扩展性模块必须符合非常严格的要求才能正常运行。 由于它们用于内核调试,因此当系统暂缓进一步执行代码时,它们将被调用和执行。 一般来说,除了通过内核调试传输与主机上运行的调试器应用程序通信的处理器外,系统中的所有处理器都被锁定在 IPI 中旋转。 该处理器通常在完全禁用中断的情况下运行,基本上是在调试传输硬件上旋转,等待来自调试器的命令。

KDNET 扩展性模块只有一个显式导出 – KdInitializeLibrary。 它们也没有显式导入。 KDNET 在调用 KdInitializeLibrary 时,会将一个指向结构的指针传递给 KDNET 扩展性模块,而该结构中包含 KDNET 允许它们调用的例程列表。 无法调用其他例程。 时段。 具有任何导入的 KDNET 扩展性模块设计不正确,将不受支持。

如果使用 link /dump /exports 和 link /dump /imports 转储 KDNET 扩展性模块的导入和导出,就会发现它们只有一个导出 (KdInitializeLibrary) 并且没有导入。 KDNET 扩展性模块向 KDNET 报告它们的额外导出,方法是在导出函数结构中填入函数指针,而 KDNET 会在调用 KdInitializeLibrary 时将指针传递给该结构。 然后,KDNET 使用该结构中的函数指针来调用扩展性模块,并使用模块支持的硬件进行数据传输。 KDNET 通过查看模块在结构中的导出函数表中填写了哪些特定函数来确定模块是基于数据包的模块还是基于字节的模块。 其中一些功能用于支持基于数据包的硬件,另一些则用于支持基于串行的硬件。 表中的某些函数既适用于串行硬件,也适用于基于数据包的硬件(KdInitializeController、KdShutdownController、KdGetHardwareContextSize)。

KDNET 扩展性模块应编写为单线程代码。 它们不应执行任何同步操作。 所有内核调试传输都依赖于 Windows 内核在调试器进入时进行适当的同步。 内核在进入内核调试器时会锁定一个调试器锁,而在进入调试器时也会通过 IPI 锁定系统中的其他处理器。 只有当主机上运行的内核调试器告诉目标计算机允许继续执行时,这些处理器才会被释放。 由于内核会进行同步,因此 KDNET 扩展性模块绝对不能在其代码中使用任何自旋锁、互斥体、门或任何其他 Windows 同步机制。 编写这些程序时,应直接对各自的硬件进行编程,以发送和接收数据包或字节。

KDNET 扩展性模块代码应尽可能简单。 这将有助于确保尽可能不出现错误,因为如果不使用硬件调试器,目前无法在计算机上实时调试 KDNET 扩展性模块代码。 不能使用内核调试器调试内核调试传输代码。 尝试这样做会导致内核堆栈损坏(通常以双重故障和重启结束)或死锁而重启计算机,或者导致重新输入传输程序,在大多数情况下会导致传输程序无法正常工作。

内核调试传输模块必须遵循 KDNET 扩展性模块的两种命名约定之一。 如果模块用于支持 PCI 硬件,则必须命名为 kd_YY_XXXX.dll,其中 XXXX 是硬件的 PCI 供应商 ID(十六进制),YY 是硬件的 PCI 类别。 在支持基于 PCI 的硬件的 Windows 框中提供了多个 KDNET 扩展性模块。 例如,Intel 的 kd_02_8086.dll、Broadcom 的 kd_02_14e4.dll 以及 Realtek 的 kd_02_10ec.dll。 可以在 https://www.pcisig.com/membership/member-companies 查找已注册的 PCI 供应商 ID。所有基于 PCI 的 KDNET 扩展性模块都将其支持的硬件的供应商 VID 用十六进制表示,作为模块名称的最后 4 个字符。 大多数盒模块的类代码为 02,因为它们是网络类设备,因此在 PCI 配置空间中的 PCI 类为 0x02。 Winload.exe 通过从 PCI 配置空间读取所选调试设备的 PCI 设备类和 PCI VID 来生成基于 PCI 的 KDNET 扩展性模块名称,并尝试加载名称中包含这些标识符的模块。 如果设备的 PCI 类别代码不是网络 0x02 类别,则必须在 KDNET 扩展性模块名称中使用设备的正确 PCI 类别代码(十六进制)。 否则,winload 将无法正确加载模块。 每个名称中的 _02_ 是网络类设备的 PCI 类代码,以十六进制表示。 此代码还可从调试设备的 PCI 配置空间中找到并读取。

如果设备有 DBG2 表条目并且不是基于 PCI 的设备,那么模块的命名约定将有所不同。 DBG2 表调试设备的命名约定是 kd_XXXX_YYYY.dll,其中 XXXX 是十六进制 DBG2 表 PortType,YYYY 是 DBG2 表条目中的十六进制 DBG2 表 PortSubtype。 Kd_8003_5143.dll 是一个收件箱 DLL,用于支持子类型为 0x5143 的 net (0x8003) PortType。 在本例中,5143 是 Qualcomm PCI vid,因为它用于支持 Qualcomm USB 控制器上的 KDNET,而对于 Net DBG2 表条目,PortSubtype 被定义为硬件供应商的 PCI VID。 请注意,可以使用此命名约定来支持串行、USB 和其他 DBG2 表设备。 以下是当前支持的 PortType 十六进制值:8000(串行设备)、8001(1394 设备)、8002(USB 设备)、8003(NET 设备)。 请注意,串行和 USB 设备的子类型必须在 Microsoft 中保留。 Microsoft 维护着一份已分配串行和 USB 子类型的列表。 如果现有的支持类型无法与硬件配合使用,请发送邮件至 kdnet@microsoft.com 以保留串行或 USB 子类型。

KDNET 扩展性导入

以下是可以从 KDNET 扩展性模块调用的例程列表。 请注意,所有这些例程都会传递给 KdInitializeLibrary 例程,而 kdnetextensibility.h 标头会重新映射对这些例程的正常调用,使其通过导入表。 代码必须通过导入表调用这些导入,这样模块就没有导入。 不得调用内核、HAL 或任何其他内核模块导出的任何其他例程。 只能调用这些例程。 事实证明,这组例程足以开发 KDNET 的所有盒扩展性模块,并且应该足以满足正常所需。 如果需要内核导出的其他例程未不在此列表中,请发送邮件至 kdnet@microsoft.com 解释相关情况,以及需要的其他例程和原因。 请注意,只有在主要 Windows 版本发布周期(如有)才会添加此列表。 请注意,这些例程大多都直接对应于内核或 HAL 支持的 Windows 内核 API。 有一两个是 KDNET 的专用例程。

必须在标头中正确包含 kdnetextensibility.h,以便通过导入表对例程进行正确重新映射。 如果未这样做,模块将有导入,并且将不受支持。

应使用以下例程来读写内存映射设备内存。 这些例程具有相同的调用约定,并映射到相应的内核例程:READ_REGISTER_UCHAR、READ_REGISTER_USHORT、READ_REGISTER_ULONG、WRITE_REGISTER_UCHAR、WRITE_REGISTER_USHORT、WRITE_REGISTER_ULONG,在 64 位平台上只有 READ_REGISTER_ULONG64 和 WRITE_REGISTER_ULONG64。 所有设备内存访问都应通过这些例程完成,因为它们能确保读写操作不会被处理器重新排序。 请注意,msdn.microsoft.com 记录了在调用约定中对应于这些例程的 Windows CE Compact 2013 例程。 遗憾的是,NT 的例程似乎没有记录,但调用约定是一样的。

应使用以下例程来读写设备 IO 端口。 这些例程具有相同的调用约定,并映射到相应的内核例程: READ_PORT_UCHAR、READ_PORT_USHORT、READ_PORT_ULONG、WRITE_PORT_UCHAR、WRITE_PORT_USHORT 和 WRITE_PORT_ULONG。 所有设备 IO 端口的访问都应通过这些例程来完成。 请注意,msdn.microsoft.com 记录了在调用约定中对应于这些例程的 Windows CE Compact 2013 例程。

可以调用下列其他例程,并应使用指定的参数正常调用。 请注意,这样做虽然正确地包含了 kdnetextensibility.h 标头,但会通过 KDNET 扩展性导入表重新映射函数调用,从而导致模块中没有 KDNET 扩展性模块所需的显式导入。

PHYSICAL_ADDRESS

KdGetPhysicalAddress (

    __in PVOID Va

    );
 

VOID

KeStallExecutionProcessor (

    __in ULONG Microseconds

    );


ULONG

KdGetPciDataByOffset (

    __in ULONG BusNumber,

    __in ULONG SlotNumber,

    __out_bcount(Length) PVOID Buffer,

    __in ULONG Offset,

    __in ULONG Length

    );
 

ULONG

KdSetPciDataByOffset (

    __in ULONG BusNumber,

    __in ULONG SlotNumber,

    __in_bcount(Length) PVOID Buffer,

    __in ULONG Offset,

    __in ULONG Length

    );

 
VOID

KdSetDebuggerNotPresent (

    __in BOOLEAN NotPresent

    );
 

VOID

PoSetHiberRange (

    _In_opt_ PVOID MemoryMap,

    _In_ ULONG     Flags,

    _In_ PVOID     Address,

    _In_ ULONG_PTR Length,

    _In_ ULONG     Tag

    );

 

VOID

KeBugCheckEx (

    __in ULONG BugCheckCode,

    __in ULONG_PTR BugCheckParameter1,

    __in ULONG_PTR BugCheckParameter2,

    __in ULONG_PTR BugCheckParameter3,

    __in ULONG_PTR BugCheckParameter4

    );


PVOID

KdMapPhysicalMemory64 (

    _In_ PHYSICAL_ADDRESS PhysicalAddress,

    _In_ ULONG NumberPages,

    _In_ BOOLEAN FlushCurrentTLB

    );
 

VOID

KdUnmapVirtualAddress (

    _In_ PVOID VirtualAddress,

    _In_ ULONG NumberPages,

    _In_ BOOLEAN FlushCurrentTLB

    );
 

ULONG64

KdReadCycleCounter (

    __out_opt PULONG64 Frequency

    );

请注意,PoSetHiberRange 函数只能从 KdSetHibernateRange 例程中调用。 此外,大多数 KDNET 扩展性模块应该不需要调用 KeBugCheckEx、KdMapPhysicalMemory64 和 KdUnmapVirtualAddress。 另一方面,基本上所有 KDNET 扩展性模块都需要调用 KdGetPhysicalAddress 来获取设备 DMA 引擎编程所需的物理内存地址,而且许多模块还需要调用 KeStallExecutionProcessor、KdGetPciDataByOffset 和 KdSetPciDataByOffset。 最后两个例程用于访问设备 PCI 配置空间。

KDNET 扩展性导出

下面简要介绍了 KDNET 的每个扩展例程。 必须实现基于数据包的 KDNET 扩展性模块或基于串行的 KDNET 扩展性模块所需的所有例程。 以下是 KDNET 扩展性模块导出的数据包。

KdInitializeLibrary

NTSTATUS

KdInitializeLibrary (

    __in PKDNET_EXTENSIBILITY_IMPORTS ImportTable,

    __in_opt PCHAR LoaderOptions,

    __inout PDEBUG_DEVICE_DESCRIPTOR Device

    )

/*++

Routine Description:

    This routine validates that the ImportTable is a supported version.  Makes
    a copy of the ImportTable in its own global memory, and writes pointers to
    functions that it exports into the Exports pointer also located in that
    table.

    This routine also writes the size in bytes of the Memory it needs into
    the Length field of the Memory structure contained in the debug device
    descriptor passed to this routine.

    When kernel debugging is enabled, this routine will be called twice during
    boot.  The first time by winload to determine how much memory to allocate
    for KDNET and its extensibility module, and the second time by KDNET when
    the kernel first initializes the kernel debugging subsystem.

Arguments:

    ImportTable - Supplies a pointer to the KDNET_EXTENSIBILITY_IMPORT
        structure.

    LoaderOptions - Supplies a pointer to the LoaderOptions passed to the
        kernel.  This allows settings to be passed to the KDNET extensibility
        module using the loadoptions BCD setting.

    Device - Supplies a pointer to the debug device descriptor.

Return Value:

    STATUS_INVALID_PARAMETER if the version of the import or export table is
        incorrect.

    STATUS_SUCCESS if initialization succeeds.
--*/

调用此例程是为了在 KDNET 和此 KDNET 扩展性模块之间传递导入和导出例程。 此例程应验证导入表和导出表的版本是否符合预期并受支持,如果不符合则会失败。 它应该在自己的全局内存中复制一份导入表。 它应将导出的例程写入导入表中“Exports”字段指向的结构中。 它还必须将传给此例程的调试设备描述符指针中的内存结构“Length”字段设置为支持硬件设备所需的内存字节数。

请注意,设备将使用为调试器选择的硬件。 如果需要,此例程应根据设备自定义所需的内存量。 例如,同时支持 1Gig 和 10Gig 硬件的扩展性模块可以增加为 10Gig 设备请求的内存大小。 它们可以通过检查调试设备描述符的 DeviceID 字段来确定正在使用的设备。

请注意,winload 和 KDNET 在调用 KdInitSystem 时都会调用此例程。 请注意,这是 KDNET 扩展性模块导出的唯一例程。 它是放置在 .def 文件中的唯一例程。 KDNET 扩展性模块只有一个显式导出(本例程)而没有导入。

KdSetHibernateRange

VOID

KdSetHibernateRange (

    VOID

    )

/*++
Routine Description:

    This routine is called to mark the code in the KDNET extensiblity module
    so that it can be properly handled during hibernate and resume from
    hibernate.

Arguments:
    None.

Return Value:
    None.
--*/

系统会在休眠前调用此例程,以便向系统正确注册 KDNET 扩展性模块使用的代码。 这样,系统就能在休眠和从休眠状态恢复时正确管理内存。 (内存的保存时间较晚,加载时间较早,因为它在恢复过程中很早就会被调用。)

KdInitializeController

NTSTATUS

KdInitializeController(

    __in PVOID Adapter

    )

/*++
Routine Description:

    This function initializes the Network controller.  The controller is setup
    to send and recieve packets at the fastest rate supported by the hardware
    link.  Packet send and receive will be functional on successful exit of
    this routine.  The controller will be initialized with Interrupts masked
    since all debug devices must operate without interrupt support.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

Return Value:
    STATUS_SUCCESS on successful initialization.  Appropriate failure code if
    initialization fails.
--*/

调用此例程是为了初始化硬件。 当系统初始化时,以及当系统从低功耗状态唤醒时,都会调用 KdShutdownController。 此例程必须确保硬件已完全完成初始化,并在返回前做好发送数据包的准备。 此例程应等待 PHY 启动并建立链接。 请注意,如果没有连接电缆,则此例程不应无限期停止。 此例程在 KDNET 与此扩展性模块共享的 KDNET 共享数据结构中设置链路速度和双工。 它还会将硬件使用的 MAC 地址写入 KDNET 共享数据结构中 TargetMacAddress 指向的位置。

KdShutdownController

VOID

KdShutdownController (

    __in PVOID Adapter

    )

/*++
Routine Description:

    This function shuts down the Network controller.  No further packets can
    be sent or received until the controller is reinitialized.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

Return Value:

    None.
--*/

至关重要的是,此个例程必须等待,直到所有待处理的发送数据包都在线路上发送完毕。 此例程需要等到所有发送数据包都已从主存储器中 DMA 出来并传输到线路上之后,才会关闭硬件上的发送功能。 一旦发送完所有待处理的发送数据包,此例程就会完全关闭硬件。 当系统关闭时,以及当系统决定将调试传输器电源管理到低功耗状态时,都会调用此例程。 除了在系统关闭时调用外,在系统进入待机、休眠、睡眠和连接待机时也可以调用。

KdGetHardwareContextSize

ULONG

KdGetHardwareContextSize (

    __in PDEBUG_DEVICE_DESCRIPTOR Device

    )
 

/*++
Routine Description:

    This function returns the required size of the hardware context in bytes.

Arguments:

    Device - Supplies a pointer to the debug device descriptor.

Return Value:

    None.

--*/

此例程将返回支持硬件所需的全部内存的字节数。 这包括上下文结构、所有用于接收和发送的数据包缓冲区,以及任何硬件数据包描述符和其他结构。 需要在此处报告所需的所有内存的大小。 包括因硬件对数据包、数据包描述符或其他结构的对齐限制而需要的额外内存。

请注意,KdInitializeLibrary 例程在设置调试设备描述符中的内存长度字段时,应调用此例程。

KdGetRxPacket

NTSTATUS

KdGetRxPacket (

    __in PVOID Adapter,

    __out PULONG Handle,

    __out PVOID *Packet,

    __out PULONG Length

)

/*++

Routine Description:

    This function returns the next available received packet to the caller.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

    Handle - Supplies a pointer to the handle for this packet.  This handle
        will be used to release the resources associated with this packet back
        to the hardware.

    Packet - Supplies a pointer that will be written with the address of the
        start of the packet.

    Length - Supplies a pointer that will be written with the length of the
        received packet.

Return Value:

    STATUS_SUCCESS when a packet has been received.
    STATUS_IO_TIMEOUT otherwise.

--*/

此例程获取下一个已接收但尚未处理的可用数据包。 它会返回该数据包的句柄。 该句柄将用于调用 KdGetPacketAddress 获取数据包的地址,以及调用 KdGetPacketLength 获取数据包长度。 在调用 KdReleaseRxPacket 释放数据包之前,数据包和句柄必须保持可用和有效。 此例程还直接向调用方返回数据包地址和长度。

如果当前没有可用的数据包,则此例程必须立即返回 STATUS_IO_TIMEOUT。 此例程不得等待接收数据包。 请注意,句柄的前 2 位是保留位。 TRANSMIT_HANDLE 和 TRANSMIT_ASYNC 都必须保持明确。

KdReleaseRxPacket

VOID

KdReleaseRxPacket (

    __in PVOID Adapter,

    ULONG Handle

)

/*++

Routine Description:

    This function reclaims the hardware resources used for the packet
    associated with the passed Handle.  It reprograms the hardware to use those
    resources to receive another packet.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.
    Handle - Supplies the handle of the packet whose resources should be
        reclaimed to receive another packet.

Return Value:

    None.

--*/

此例程将与数据包句柄相关的资源释放回硬件,以便用于接收另一个数据包。 每次成功调用 KdGetRxPacket 后,都会使用 KdGetRxPacket 返回的句柄再次调用 KdReleaseRxPacket。 请注意,不保证在 KdGetRxPacket 成功后会立即调用 KdReleaseRxPacket。 可能会先调用另一个 KdGetRxPacket。 不过,每次成功的 KdGetRxPacket 调用都会通过 KdReleaseRxPacket 调用释放其资源。

此例程应正确对硬件进行编程,以便释放的资源可用于接收另一个数据包。

KdGetTxPacket

NTSTATUS

KdGetTxPacket (

    __in PVOID Adapter,

    __out PULONG Handle

)

/*++

Routine Description:

    This function acquires the hardware resources needed to send a packet and
    returns a handle to those resources.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

    Handle - Supplies a pointer to the handle for the packet for which hardware
        resources have been reserved.

Return Value:

    STATUS_SUCCESS when hardware resources have been successfully reserved.
    STATUS_IO_TIMEOUT if the hardware resources could not be reserved.
    STATUS_INVALID_PARAMETER if an invalid Handle pointer or Adapter is passed.

--*/

此例程获取下一个可用的传输资源,并返回一个句柄。 此句柄将用于调用 KdGetPacketAddress 和 KdGetPacketLength。 KdGetPacketAddress 返回的数据包地址将用于直接写入数据包的内容。 数据包地址必须是数据包的起始地址,长度应是可写入数据包的最大字节数。 请注意,如果没有可用的硬件资源,因为它们都已被获取但尚未被传输,则此例程应立即返回 STATUS_IO_TIMEOUT。

必须在返回的句柄中设置 TRANSMIT_HANDLE。 请注意,句柄的前两位是为 TRANSMIT_ASYNC 和 TRANSMIT_HANDLE 标志保留的。

KdSendTxPacket

NTSTATUS

KdSendTxPacket (

    __in PVOID Adapter,

    ULONG Handle,

    ULONG Length

)

/*++

Routine Description:

    This function sends the packet associated with the passed Handle out to the
    network.  It does not return until the packet has been sent.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

    Handle - Supplies the handle of the packet to send.

    Length - Supplies the length of the packet to send.

Return Value:

    STATUS_SUCCESS when a packet has been successfully sent.

    STATUS_IO_TIMEOUT if the packet could not be sent within 100ms.

    STATUS_INVALID_PARAMETER if an invalid Handle or Adapter is passed.

--*/

此例程将与已传递句柄相关的数据包发送到线路上。 请注意,句柄中可能有一个额外的设置位,用于指示发送是否为异步传输。 如果在句柄中设置了 TRANSMIT_ASYNC 标志,则该例程应编程让硬件发送数据包,然后立即返回,而无需等待硬件完成发送。 这意味着传输过程中出现的任何错误都将丢失。 这没有问题,而且是设计好的,因为数据包无论如何都可能在线路上丢失。 如果句柄中未设置 TRANSMIT_ASYNC 标志,则此例程必须等待数据包在线路上发送完毕,并返回发送过程中出现的任何错误(如有)。 请注意,在向调试器主机发送转储文件或通过 KDNET 从 KDNIC 发送 Windows 网络数据包时,将设置 TRANSMIT_ASYNC。 在发送所有其他调试器数据包时,TRANSMIT_ASYNC 将被明确。

如果一组数据包的 TRANSMIT_ASYNC 设置为 TRUE,随后又发送了一个未设置 TRANSMIT_ASYNC 的数据包,那么硬件必须等到未设置标记的数据包实际发送完毕,即使这意味着它必须等待前面的异步数据包也发送完毕。

KdGetPacketAddress

PVOID

KdGetPacketAddress (

    __in PVOID Adapter,

    ULONG Handle

)

/*++

Routine Description:

    This function returns a pointer to the first byte of a packet associated
    with the passed handle.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

    Handle - Supplies a handle to the packet for which to return the
        starting address.

Return Value:

    Pointer to the first byte of the packet.

--*/

此例程返回一个指针,指向与传递的句柄相关联的数据包的第一个字节。 请注意,句柄的 TRANSMIT_HANDLE 位将用于发送数据包,而 TRANSMIT_HANDLE 位将用于接收数据包。 返回的指针应该是一个 Windows 虚拟地址,可以被处理器读取或写入。 此地址应位于为 KDNET 扩展性模块预留的内存块内,而该内存块会在调试设备描述符内存结构中传递。 (请注意,KDNET 扩展性模块在访问内存时,绝对不能使用超过它在 KdInitializeLibrary 中请求的内存大小。块末尾的任何额外内存都将保留给 KDNET 使用,KDNET 扩展性模块不得触及。)

KdGetPacketLength

ULONG

KdGetPacketLength (

    __in PVOID Adapter,

    ULONG Handle

)

/*++

Routine Description:

    This function returns the length of the packet associated with the passed
    handle.

Arguments:

    Adapter - Supplies a pointer to the debug adapter object.

    Handle - Supplies a handle to the packet for which to return the
        length.

Return Value:

    The length of the packet.

--*/

此例程以字节为单位返回与传递的句柄相关的数据包长度。 请注意,句柄的 TRANSMIT_HANDLE 位将用于发送数据包,而 TRANSMIT_HANDLE 位将用于接收数据包。 对于发送数据包,此长度应为可写入数据包的最大字节数。 对于接收数据包,此长度应为接收数据包的实际字节数。

调试 KDNET 扩展性模块

要调试 KDNET 扩展性模块,需要在目标计算机上的高级命令提示符下运行以下 bcdedit 命令。

首先,也是最重要的一点 - 需要运行以下两条命令,以确保 Winload 允许重复启动失败,而不会进入特殊的失败路径,即进入调试器并阻止正常启动。 在运行这些命令后,就可以用新的位反复重启机器,并顺利调试这些新位。

Bcdedit -set {current} BootStatusPolicy IgnoreAllFailures

Bcdedit -set {current} RecoveryEnabled No

假设要使用目标计算机 com1 上的串行调试功能来调试扩展模块,请执行以下操作。

bcdedit -dbgsettings serial debugport:1 baudrate:115200

将默认调试传输设置为 com1 上的串行端口,波特率为 115200。 这些设置也将用于启动调试。

bcdedit -debug on

这样就可以进行内核调试。

bcdedit -bootdebug on

这样就可以在 winload.exe 上进行启动调试,可以用它来调试包括 KDNET 扩展模块在内的早期内核初始化。

bcdedit -set kerneldebugtype net

这样将强制内核调试类型为 net,与默认调试传输设置无关。 这将导致 winload.exe 将 kdnet.dll 作为内核调试传输载入。

bcdedit -set kernelbusparams b.d.f

其中 b 是总线号,d 是设备号,f 是正在为其编写 KDNET 扩展模块的硬件的函数号(全部以十进制为单位)。 这些数字取决于硬件所在的 PCI 插槽。 可以在 Windows 设备管理器的网络设备属性页面中找到位置字符串。 打开 Windows 设备管理器,双击网络设备,找到设备并双击它,在打开的窗口中应该有一个位置:其中包含 PCI 总线上硬件的总线、设备和功能的字段。 如果总线驱动程序导致该信息被屏蔽,则必须通过总线或其他方法来确定位置。

这样将强制将内核总线参数设置为 b.d.f,从而强制选择特定设备作为内核调试设备。

bcdedit -set kernelhostip N

其中 N 由以下公式确定。 如果调试器主机的 IPv4 地址为 w.x.y.z,则 N = (w0x01000000) + (x0x00010000) + (y0x00000100) + (z0x00000001)。 N 需要在命令行中指定为十进制,而不是十六进制。 实际上,需要将 IPv4 地址的每个字节连接起来(十六进制),以十六进制建立一个 32 位数字,然后将其转换为十进制。

bcdedit -set kernelport N

其中 N 为 50000 或其他不会在内部网络中被屏蔽的端口。

这样将强制 KDNET 使用端口 N 作为网络调试端口。

bcdedit -set kernelkey 1.2.3.4

这样将把 KDNET 调试密钥强制设为 1.2.3.4。 1.2.3.4 不是安全或唯一的网络密钥。 为了使目标计算机保持安全,在主机和目标计算机之间传输的数据包必须加密。 强烈建议使用自动生成的加密密钥。 有关详细信息,请参阅设置 KDNET 网络内核自动调试

bcdedit -set kerneldhcp on

这将强制 KDNET 内核 dhcp 设置为开启。

假设在主机上使用 com1 作为串行调试端口,请在调试器主机上使用以下命令行运行调试器:

windbg -d -k com:port=com1,baud=115200

这样将运行调试器,并导致它在 windbg 启动调试器首次与主机通信时中断。

然后运行以下命令来重新启动目标计算机

shutdown -r -t 0

当调试器切换到 windbg 时,确保为 winload 加载了符号。 (可能需要设置 .sympath 并进行 .reload)。 然后运行 x winload!*deb*tra*。 列出的其中一个符号将类似于 BdDebugTransitions。

然后运行 ed winload!BdDebugTransitions 1,但要确保使用正确的符号名称。

然后运行 bu winload!blbdstop 以设置断点。

然后点击 g 开始。

应该在 winload!BlBdStop 处中断。

然后运行以下命令。

bu nt!KdInitSystem

bu kdnet!KdInitialize

bu kdstub!KdInitializeLibrary

请注意,在 KDNET 扩展模块中设置断点时,很可能会使用 kdstub;如果不起作用,请使用

bu kd_YY_XXXX!KdInitializeLibrary

其中,YY 是 PCI 类别,XXXX 是 PCI VID。 (即:使用 KDNET 扩展性模块的名称。)

通常在调试器中需要使用 kdstub,而不是扩展性模块的实际名称。

然后运行 bl 以列出断点。 确保断点已就位(它们旁边都应该有一个 e)。

然后点击 g。 应该点击 nt!KdInitSystem 断点。

再次点击 g,就会看到 kdnet!KdInitialize

再次点击 g,应该会在自己模块的 KdInitializeLibrary 处遇到一个断点。

然后就可以在 InitializeController 例程和所有其他例程上设置断点,并逐步完成代码。

一旦单步执行 KdInitializeLibrary,点击 g,如果在 InitializeController 例程上设置了断点,则下一个断点就会出现。

完成后,确保在 KdGetTxPacket、KdSendTxPacket、KdGetRxPacket 和 KdReleaseRxPacket 例程上设置了断点,然后再次点击 g,这些例程将在 KDNET 启动时作为网络初始化的一部分运行。

可能需要在 KdInitializeLibrary 或 KdInitializeController 例程中添加临时代码,以确保所有例程都被调用,这样就可以逐步完成所有代码。 (例如,KdShutdownController 在启动时不会被正常调用,因此需要从临时代码中明确调用它,这样就可以逐步检查并确保其正确性。)

在单步执行所有代码并确信无误后,重新启动目标计算机,但不要将 winload!BdDebugTransitions 标记设置为 true(默认为零)。

然后在调试器主计算机上运行另一个内核调试器实例。

Windbg -d -k net:port=50000,key=1.2.3.4

启动目标计算机,它就会通过网络连接到内核调试器的另一个实例。

然后在内核调试器中运行命令,确保它能正常工作,然后让目标继续启动,并确保你以后可以中断并运行命令。

注意

在 winload 中设置调试转换标志可确保 Windows 不会启动。 如果在设置该标志后试图让 Windows 完成启动,Windows 就会直接崩溃或挂起。 如果希望 Windows 成功启动,就不能设置调试转换标志。 设置该标志后,就可以在调试器中调试代码,并通过单步执行来验证代码是否正确,但最终还是不需要该标志,以便在正常启动时验证调试是否正常。 这意味着,在正常启动系统时,无法单步执行的代码;而事实上,当 Windows 正常运行时,在硬件上启用了调试功能,KDNET 扩展模块是无法调试的。 任何使用内核调试器对其进行调试的尝试都会导致计算机崩溃。 (不能在内核调试路径中运行的代码中设置断点,否则会导致无限重新进入、堆栈损坏和重新启动。)

多个物理函数 - 2PF

除了 KDNET 的扩展性外,KDNET 还支持通过分割 PCI 配置空间,在支持的网卡上使用多个物理功能 (PF) 进行内核调试。 鼓励网络卡供应商启用对此功能的支持。 有关详细信息,请参阅调试器 2PF KDNET 微型端口网络驱动程序支持

另请参阅

自动设置 KDNET 网络内核调试

手动设置 KDNET 网络内核调试

调试器 2PF KDNET 微型端口网络驱动程序支持