现在的位置: 首页 > 综合 > 正文

KD extension DLLs & KDCOM protocol

2012年04月18日 ⁄ 综合 ⁄ 共 11980字 ⁄ 字号 评论关闭
文章目录

KD extension DLLs & KDCOM protocol

WinDbg debugger allows you to debug all modern versions of Windows using a
built-in kernel debugger and either COM or IEEE1394 port. Let's see how is it
implemented. To start windows in Kernel Debugging mode, you specify additional
parameters in boot.ini
file that look like this:

multi(0)disk(0)rdisk(0)partition(1)/WINDOWS="Microsoft
Windows XP Professional" /noexecute=optin /fastdetect /DEBUG
/DEBUGPORT=1394

Let's now see what actually happens when NTOSKRNL detects that it was started
with /DEBUG
parameter. First of all, it analyzes the /DEBUGPORT
parameter from boot.ini
and determines what packet-level plugin (KD
extension DLL

in Microsoft terminology) to load. For COM-based debugging the
plugin DLL is called KDCOM.DLL, for IEEE1394-based debugging it is called
KD1394.DLL. Fortunately, when you specify something like /DEBUGPORT=FOO
,
NTOSKRNL will try to load KDFOO.DLL and use it as a KD extension DLL. As we are
providing our own DLL making a fast interface to WinDbg, that expects a named
pipe from a virtual COM port, we need to solve two problems here:

  • The problem of creating a valid KD extension DLL, i.e. providing the
    same set of exported functions working in an expected way.
  • The problem of understanding and reimplementing KDCOM protocol, i.e.
    providing the same data at the end of our fast pipe, as the original
    KDCOM.DLL provides.

Note that in Windows Vista the kernel debugging flags are specified using
bcdedit.exe

utility and cannot specify a non-standard KD extension DLL. The
only way to load KDVMWare DLL to kernel is to replace a standard one, for
example, KD1394.DLL.

KD extension DLLs

Let's explore the structure of a typical KD extension DLL. A quick
analysis of KDCOM.DLL shows that it exports the following functions:

Exports from KDCOM.dll
8 exported name(s), 8 export addresse(s). Ordinal base is 1.
Sorted by Name:
RVA Ord. Hint Name
-------- ---- ---- ----
00000386 1 0000 KdD0Transition
00000386 2 0001 KdD3Transition
000003A6 3 0002 KdDebuggerInitialize0
0000044C 4 0003 KdDebuggerInitialize1
00000F4C 5 0004 KdReceivePacket
00000460 6 0005 KdRestore
00000456 7 0006 KdSave
000011B2 8 0007 KdSendPacket

Let's analyze how this functions work.

Initialization

As it is evident from their names, two functions are used to initialize a KD
extension DLL: KdDebuggerInitialize0()
and
KdDebuggerInitialize1()
. Fortunately, Microsoft
provides PDB file for WinXP version of KDCOM.DLL. Additionally, some of the
functions are described by Ken Johnson (http://www.nynaeve.net/?p=169
). Let's use
the PDB to recover
declarations for the initialization functions:

NTSTATUS NTAPI KdDebuggerInitialize0(PLOADER_PARAMETER_BLOCK

lpLoaderParameterBlock);
NTSTATUS NTAPI KdDebuggerInitialize1(PLOADER_PARAMETER_BLOCK

lpLoaderParameterBlock);

The first function performs initial initialization of a KD extension DLL. For
example, it can read the parameters specified in BOOT.INI using the


LOADER_PARAMETER_BLOCK

::LoadOptions

field. For example, KDCOM.DLL can
get determine the COM port number to use and its baud rate. Both initialization
functions return a NTSTATUS
value with
STATUS_SUCCESS
corresponding to successful
completion. Note that if an initialization function such as
KdDebuggerInitialize0()
returns an unsuccessful
status, kernel is started without debugging support and the DLL is not actually
used any more.

Sending and receiving packets

All communication between kernel and a kernel debugger is packet-based. The
following rules describe the packet behavior:

  • All KD extension DLL calls are called synchronously, i.e. should not
    leave any executing code after they return.
  • A KD extension DLL guarantees successful packet delivery, i.e. retries
    sending a packet when KdSendPacket()
    was
    called until debugger acknowledges it.
  • Kernel uses polling model to check whether new packets are available.
    For that purpose it calls KdReceivePacket()

    with a special parameter.

  • When kernel wants to receive a packet, it knows the type of the packet
    to receive. It passes this type to KdReceivePacket()

    that drops all packets of other types (and sends corresponding resend
    requests).

  • Each send/receive operation uses 2 buffers for data transfer. Typically,
    the first buffer contains some fixed-size message header and the second one
    contains variable-sized message body. However, this is a typical use case,
    not the only one. Basically, in a send operation the two buffers are simply
    sent one after another as a single data block with no additional indication
    where the first one ends and the second one starts. When a receive function
    is called with first buffer having a size of N bytes, the first N bytes of
    the message are put to it, while the rest is put to the second buffer.

Each buffer is represented by a STRING

structure defined in NTDEF.H
(on x64 systems the data
pointer is aligned at 8-byte boundary):

typedef struct
_STRING {
    USHORT Length;
    USHORT MaximumLength;
    PCHAR Buffer;
} STRING, *PKD_BUFFER;

In KDVMWare this structure is redefined as KD_BUFFER
.

A special structure called KD_CONTEXT

maintains the global state for KD packet layer:

typedef struct
_KD_CONTEXT
{
    ULONG RetryCount;
    BOOLEAN BreakInRequested;
} KD_CONTEXT, *PKD_CONTEXT;
 

The RetryCount
member is set before a call to
KdSendPacket()
and specifies the number of
retries for a droppable packet to set. A droppable packet is a packet that can
be simply dropped if no acknowledgment comes from WinDbg after some number of
retries (KdSendPacket()
will just return). The
BreakInRequested
is set to
TRUE
by KdReceivePacket()
if WinDbg has requested a kernel breakpoint (ctrl+break was
pressed, or WinDbg was just started). The break-in request is not a part of a
packet and is transferred separately (see KDCOM protocol description below).

Here are the definitions for packet sending and receiving functions:

void
NTAPI KdSendPacket(__in

ULONG PacketType,
                       
__in
PKD_BUFFER FirstBuffer,
                       
__in_opt
PKD_BUFFER SecondBuffer,
                       
__inout
PKD_CONTEXT KdContext);

KD_RECV_CODE
NTAPI KdReceivePacket(__in
ULONG PacketType,
                                  
__inout_opt
PKD_BUFFER FirstBuffer,
                                  
__inout_opt
PKD_BUFFER SecondBuffer,
                                  
__out_opt
PULONG PayloadBytes,
                                  
__inout_opt
PKD_CONTEXT KdContext);

The KdReceivePacket()
return value can be
defined as a following enumeration:

typedef enum
_KD_RECV_CODE
{
    KD_RECV_CODE_OK = 0,
    KD_RECV_CODE_TIMEOUT = 1,
    KD_RECV_CODE_FAILED = 2
} KD_RECV_CODE, *PKD_RECV_CODE;

The PacketType
parameter specifies the type
of the packet being sent or being received (all packets with other types should
be ignored), however there is one exception. When
PacketType

is set to 8 in a KdReceivePacket()

call, the function checks whether there is any data available (for example,
whether the COM port buffer is non-empty), and returns immediately
either KD_RECV_CODE_OK
or
KD_RECV_CODE_TIMEOUT
.

Additional support functions

A KD extension DLL exports some additional functions that are not directly
involved in packet sending/receiving and can simply return
STATUS_SUCCESS
in most of implementations:

NTSTATUS NTAPI KdD0Transition();   
//Called when the debug port device should be powered
on

NTSTATUS NTAPI KdD3Transition();   
//Called when the debug port device should be powered
off

NTSTATUS NTAPI KdSave(BOOL SleepTransaction);   
//Saves the debug port state before standby or
hibernation

NTSTATUS NTAPI KdRestore(BOOL SleepTransaction);
//Restores originally saved debug port state

The information about these functions was taken from
RectOS documentation
pages

. Although Microsoft implementation can be different from ReactOS one,
just returning STATUS_SUCCESS
from these
functions should work.

KDCOM protocol

Another problem to be solved in order to connect kernel and WinDbg using a
custom KD extension DLL is the protocol that KDCOM.DLL uses to transfer packets
over a COM port. As WinDbg receives and sends KDCOM packets when connected to a
kernel using a named pipe, our tool should be able to produce and to parse such
packets. In KDVMWare these packets  are processed in KDCLIENT.DLL on host
side, however, in Microsoft implementation, all packet processing logic is
implemented inside KDCOM.DLL. Let's see, how it works.

First of all, there are two kinds of packets: control packets and data
packets. Data packets directly transfer KdSendPacket()/KdReceivePacket()

data, while control packets signalize receive acknowledgment, retry requests,
resync requests, etc. Each control packet consists of a packet header, a data
block, and a terminating byte (0xAA
). A data
block contains contents of two buffers, one after another, with no indication of
where one ends and another starts. Moreover, sender and receiver can use split
the packet data in different ways:

Let's define a C structure describing the packet header:

typedef struct
_KD_PACKET_HEADER
{
    ULONG Signature;
    USHORT PacketType;
    USHORT TotalDataLength;
    ULONG PacketID;
    ULONG Checksum;
} KD_PACKET_HEADER, *PKD_PACKET_HEADER;

Packet signature
is either 0x30303030
('0000'

)
for data packets, or 0x69696969
for control
packets ('iiii'

).
Packet type
specifies the exact type of the packet. Types for control and data
packets are members of the same enumeration:

enum

{
    KdPacketType3 = 3,
    KdPacketAcknowledge = 4,
    KdPacketRetryRequest = 5,
    KdPacketResynchronize = 6,
    KdPacketType7 = 7,
    KdCheckForAnyPacket = 8,
    KdPacketType11 = 11,
};

As it was described before, packet type 8 is not used as a packet type.
Instead, when KdReceivePacket()
is called with
that value, it checks whether any data can be received from WinDbg and returns
immediately.

Packet ID
is used to detect if a single packet was missed, as the least
significant bit of a packet ID toggles with every new packet sent. The initial
packet ID is 0x80800800
, however, resync command
sets it to 0x80800000
. Checksum
is just
an arithmetic sum of all bytes from the data section of the packet.

As I have discovered after development of KDVMWare, a file named
windbgkd.h

was included in Windows 2000 DDK and contained information about
KDCOM protocol internals. The ReactOS version containing most of the information
from it can be found

here

. According to that file, the following packet types are actually
used:

#define
PACKET_TYPE_UNUSED 0
#define
PACKET_TYPE_KD_STATE_CHANGE32 1
#define
PACKET_TYPE_KD_STATE_MANIPULATE 2
#define
PACKET_TYPE_KD_DEBUG_IO 3
#define
PACKET_TYPE_KD_ACKNOWLEDGE 4
#define
PACKET_TYPE_KD_RESEND 5
#define
PACKET_TYPE_KD_RESET 6
#define
PACKET_TYPE_KD_STATE_CHANGE64 7
#define
PACKET_TYPE_KD_POLL_BREAKIN 8
#define
PACKET_TYPE_KD_TRACE_IO 9
#define
PACKET_TYPE_KD_CONTROL_REQUEST 10
#define
PACKET_TYPE_KD_FILE_IO 11
#define
PACKET_TYPE_MAX 12

Let's discuss the types of control packets and their roles in KDCOM protocol:

  • Acknowledgment packets are sent by both Kernel and WinDbg when a data
    packet was successfully received.
  • Resend packet is sent when Kernel or WinDbg has received a damaged
    packet, a packet with wrong ID, or a packet with unexpected type.
  • Resync packet is sent by WinDbg when it is initially connected to
    kernel. The kernel acknowledges resync operation by sending back another
    resync packet.

To illustrate, how KDCOM packet layer works, let's check out some examples:

  1. Normal operation. Kernel continiously checks for new packets. When a
    packet is found, kernel receives it (assuming it knows the type for the
    packet).
  2. Normal packet sending. Kernel sends a packet to WinDbg.
    KdSendPacket()
    waits for acknowledgment
    packet from WinDbg.
  3. Packet sending with retry. Kernel sends a packet to WinDbg, however the
    latter does not receive it. KDCOM then resends the packet after timeout.
  4. Packet collision. Kernel sends a packet to WinDbg when WinDbg sends a
    packet to kernel. Kernel sends a resend request to WinDbg. The latter gets
    the data packet instead of acknowledgment and buffers it. Then acknowledges
    it, receives a resend request for the first packet and sends it once again.
    KDCOM receives acknowledgement and returns control to kernel, that calls
    KdReceivePacket()
    to get packet from WinDbg
    (if expects one).
  5. Resynchronization. Kernel receives a packet
    and encounters a resync request from WinDbg.

Droppable packets

Some packets are "droppable". It means that
KdSendPacket()

may return control when such a packet was not acknowledged
by WinDbg after some number of retries. KDCOM.DLL treats the following packets
as droppable:

  • Type 3, subtype 0x3230
  • Type 7, subtype 0x3031
  • Type 11, subtype 0x3430

Packet subtype (ApiNumber) is the first DWORD in the packet data block. The
types referenced here is defined in the following way in windbgkd.h
:

#define
DbgKdPrintStringApi        
0x00003230
#define
DbgKdLoadSymbolsStateChange 0x00003031

Resync bounce problem

There is one significant detail in original KDCOM implementation. When the
KDCOM.DLL initializes, it reinitializes the COM port and resets its buffer. A
named pipe implementation should do the same. In other case, the following
scenario is possible:

  1. WinDbg connects to a named pipe with no kernel listening and sends a few
    resync packets.
  2. Kernel loads our KD DLL, it receives first resync packet and replies
    with a resync.
  3. WinDbg resynchronizes and sends some data.
  4. Kernel receives another resync from buffer and resyncs again, replying
    with a resync.
  5. WinDbg receives an unexpected resync, resynchronizes and sends another
    resync packet to acknowledge resynchronization.
  6. The WinDbg/kernel couple will continue producing resync packets till the
    end of time and will never synchronize normally.

To avoid this problem, KDVMWare simply clears the named pipe receive buffer
when it receives a resync packet.

Implementation in KDVMWare

All KDCOM-related functionality is implemented in the
KdComDispatcher

class. Feel free to explore its documentation using the link above.

抱歉!评论已关闭.