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() делаются след. проверки:
- Если KdDebuggerNotPresent && Type != BREAKPOINT_PROMPT, то выход
- проверяется, откуда был сделан вызов. Если из UserMode, то делается
дополнительная проверка буфера на читаемость - ProbeForRead().
После проверок вызывается KdpPrintString():
BOOLEAN
KdpPrintString (
IN PSTRING Output
);
|
Далее буфер передается в KdpSendPacket() для отправки в remote debugger.
Внутри этой ф-ции формируется заголовок пакета и блок данных. Заголовок
и данные отправляются поотдельности ф-цией 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-терминированная.
См. также
|