Alter.Org.UA  
 << Back Home EN en   Donate Donate www/www1/www2

NT Debug message support

Kernel KdPrint()/DebugPrint() behavior

В NT есть возможность генерировать и складывать текстовые отладочные сообщения (Debug Messages). В kernel-mode это обычно делается вызовом KdPrint(), в user-mode - OutputDebugString().

KdPrint() на самом деле является макросом (объявление в ntddk.h):

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

DbgPrint() в свою очередь объявляется все в том же ntddk.h так:

extern "C"  // если C++ project
ULONG
_cdecl
DbgPrint(
    PCH Format,
    ...
    );

Экспортируется DbgPrint() из ntoskrnl.exe. В некоторых драйверах (например в ScsiPort/ScsiMiniPort) DbgPrint() переопределена. Но в любом случае форматированная строка с параметрами загоняется vsnprintf()'ом в буфер UCHAR[512]. Буфер выделен на стеке, размер везде, где видел - 512 байт. В NT 3.51, NT 4 и 2000 это происходит прямо в теле DbgPrint(). Далее тем или иным путем (см. дальше) буфер передается в DebugPrint() в виде PSTRING'а, а начиная с XP - в DebugPrintEx().

#define BREAKPOINT_PRINT 1

NTSTATUS
DebugPrint(
    IN PSTRING Msg
    )
{
    return DebugService( BREAKPOINT_PRINT,
                   Msg, 0 );
}
 
NTSTATUS DebugPrintEx(
    IN PSTRING Msg,
    IN ULONG ComponentId,
    IN ULONG Level)
{
    return DebugServiceEx( BREAKPOINT_PRINT,
                   Msg->Buffer, Msg->Length,
                   ComponentId, Level );
}

Исследования показали, что DbgPrint() доступна не только в kernel-mode, но и в user-mode (Native, Win32). В этом случае экспорт происходит из ntdll.dll.

При использовании Numega SoftIce этот вызов перехвачен и управление попадает в драйвер DbgMsg.sys, предназначеный для отлова и складирования Debug Messages. По всей видимости SoftIce патчит вызов этой функции (заменяет адрес в CALL XXXXXXXX).

В w2k функция DbgPrint() содержит собственный exception handler. Кроме того после vsnprintf()'а делается проверка, поместилась ли строка в буфер. Если нет - выход.

В XP еще хитрее. Там появилась ф-ция vDbgPrintExWithPrefix() и вся функциональность DbgPrint() переместилась в нее. А функции отладочного вывода DbgPrint(), DbgPrintEx() и vKdPrintEx() стали обертками для нее.

ULONG
vDbgPrintExWithPrefix(
    IN PCH Prefix,
    IN ULONG ComponentId,
    IN ULONG Level,
    IN PCH Format,
    IN va_list arglist // va_list == PCH[]*
    );

В XP также стало больше параметров у DebugPrint(), я ее обозвал DebugPrintEx(). но я не знаю, как дальше происходит обработка. По идее там уже предусмотрена возможность установки фильтров на источники сообщений, а читать об этом нужно в документации на DbgSetDebugFilterState()/DbgQueryDebugFilterState().

A в 2003-R2 совсем стало весело. Там появилась еще некая внутреняя (неэкспортируемая) ф-ция, обзовем ее DebugDispatch(), которая делает все то же, плюс еще умеет вызывать отладчик. Возможность вызова отладчика используется функцией DbgPrintReturnControlC(). И все ранее упомянутые функции, а также vDbgPrintExWithPrefix() теперь дергают ее, а она уже в свою очередь вызывает DebugPrint().

NTSTATUS DebugDispatch(
    IN PCH Prefix,
    IN ULONG ComponentId,
    IN ULONG Level,
    IN PCH Format,
    IN va_list arglist, // va_list == PCH[]*
    IN BOOLEAN ContinueExecution);

В любом случае после формироваия PSTRING'а управление передается в DebugPrint()/DebugPrintEx(), которая вызывает DebugService()/DebugServiceEx(). А устроены эти функции так:

NTSTATUS DebugService(
    ULONG   Type,
    PVOID   Param1,
    PVOID   Param2)
{
    mov     eax, Type
    mov     ecx, Param1
    mov     edx, Param2

    int     2dh
    int     3

    mov     RetValue, eax
}


 
NTSTATUS DebugServiceEx(
    ULONG   Type,
    PVOID   Param1, ULONG   Param2,
    ULONG   Param3, ULONG   Param4)
{
    mov     eax, Type
    mov     ecx, Param1
    mov     edx, Param2
    mov     ebx, Param3
    mov     edi, Param4

    int     2dh
    int     3

    mov     RetValue, eax
}

int 2d вызывает exception. В обработчике KdpTrap() делаются след. проверки:

  1. Если KdDebuggerNotPresent && Type != BREAKPOINT_PROMPT, то выход
  2. проверяется, откуда был сделан вызов. Если из UserMode, то делается дополнительная проверка буфера на читаемость - ProbeForRead().

После проверок вызывается KdpPrintString():

BOOLEAN
KdpPrintString (
    IN PSTRING Output
    );

Далее буфер передается в KdpSendPacket() для отправки в remote debugger. Внутри этой ф-ции формируется заголовок пакета и блок данных. Заголовок и данные отправляются поотдельности ф-цией KdpSendString(), которая в свою очередь использует экспортируемую ф-цию KdPortPutByte().

А вот для наглядности картинка, показывающая кто кого вызывает. Жирным отмечены экспортируемые функции.

NT 3.51, NT4, 2000
XP, 2003
2003-R2
DbgPrint()

  

  
    DebugPrint()
      DebugService()
        KdpTrap()
          KdpPrintString()
            KdpSendPacket()
              KdpSendString()
                KdPortPutByte()
DbgPrint()
DbgPrintEx()
vDbgPrintEx()
  vDbgPrintExWithPrefix()

    DebugPrintEx()
      DebugServiceEx()
        KdpTrap()
          KdpPrintString()
            KdpSendPacket()
              KdpSendString()
                KdPortPutByte()
DbgPrint()
DbgPrintEx()
vDbgPrintEx()
vDbgPrintExWithPrefix()
  DebugDispatch()
    DebugPrintEx()
      DebugServiceEx()
        KdpTrap()
          KdpPrintString()
            KdpSendPacket()
              KdpSendString()
                KdPortPutByte()
Win32 OutputDebugString() behavior

Unicode-версия OutputDebugStringW() преобразовавает Unicode символы в ANSI и вызывает OutputDebugStringA(). Внутри OutputDebugStringA() регистрируется собственный exception handler и делается RaiseException():

WINBASEAPI
VOID
WINAPI
RaiseException(
    DWORD dwExceptionCode,
    DWORD dwExceptionFlags,
    DWORD nNumberOfArguments,
    CONST DWORD *lpArguments
    );

dwExceptionCode    = 0x40010006
dwExceptionFlags   = 0
nNumberOfArguments = 2
lpArguments        = PSTRING

а вслед за ним - JMP к выходу из ф-ции. PSTRING формируется на основе входной строки. До описаных выше kernel-функций управление не доходит.

Если в системе есть зарегистрированный debugger, то он обработает данный exception. Если debugger'а нет (или установлен SoftIce), используется exception handler, зарегистрированный при входе в OutputDebugString(). В этом случае OutputDebugString() пытается открыть Named Events 'DBWIN_DATA_READY' и 'DBWIN_BUFFER_READY', а также Named File Mapping 'DBWIN_BUFFER'. Если это не удалось, сообщение теряется.

'DBWIN_BUFFER_READY' устанавливается программой, собирающей сообщения от OutputDebugString() и сигнализирует готовность принять сообщение. 'DBWIN_DATA_READY' устанавливается в самом OutputDebugString() и сигнализирует наличие нового сообщения в 'DBWIN_BUFFER'. Размер буфера сообщений - 4k (1 page). Он имеет следующий формат:

typedef struct _DBG_OUTPUT_DEBUG_STRING_BUFFER {
    ULONG ProcessId;
    UCHAR Msg[4096-sizeof(ULONG)];
} DBG_OUTPUT_DEBUG_STRING_BUFFER, *PDBG_OUTPUT_DEBUG_STRING_BUFFER;

Строка Msg всегда NULL-терминированная.


См. также

<< Back Автор: Alter (Александр А. Телятников) Сервер: Apache+PHP под FBSD © 2002-2017