NT Debug message support
Kernel KdPrint()/DebugPrint() behavior
NT OSes are capable of generating and collecting text Debug Messages.
In kernel-mode it is usually done via KdPrint(). User-mode applications use
OutputDebugString().
KdPrint() is defined in ntddk.h as follow:
#ifdef DBG
#define KdPrint(_x_) DbgPrint _x_
#else
#define KdPrint(_x_)
#endif
|
DbgPrint() is defined so:
extern "C" // for C++ projects
ULONG
_cdecl
DbgPrint(
PCH Format,
...
);
|
DbgPrint() is exported from ntoskrnl.exe. In some drivers (for example in
ScsiPort/ScsiMiniPort) DbgPrint() is redefined. But in any case it works in same way.
Formatted string with parameters is printed to buffer UCHAR[512] with vsnprintf().
The buffer is allocated on stack and has size of 512 bytes (at least in all systems those I seen).
Under NT 3.51, NT 4 and 2000 it happens in DbgPrint() itself.
Then this buffer is passed in some way (read below) to DebugPrint() as PSTRING.
Since XP DebugPrint() is replaced with 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 );
}
|
Experiments show, that DbgPrint() is available not only in в kernel-mode. You can use it
in user-mode (Native, Win32). In this case DbgPrint() is exported from ntdll.dll.
Numega SoftIce hooks this call and at first this PSTRING comes to DbgMsg.sys driver.
This driver is intended for capturing DbgPrint() messages. Looks like SoftIce patches
call point of DebugPrint() (it changes the address in CALL XXXXXXXX instruction).
Under w2k DbgPrint() has own exception handler. Among this it checks after vsnprintf()
call if the requested string was successfully fit in buffer. If not, the message is dropped.
XP DbgPrint() behavior is more complicated. New function vDbgPrintExWithPrefix() appeared.
DbgPrint(), DbgPrintEx() and vDbgPrintEx() work as wrappers for this function.
ULONG
vDbgPrintExWithPrefix(
IN PCH Prefix,
IN ULONG ComponentId,
IN ULONG Level,
IN PCH Format,
IN va_list arglist // va_list == PCH[]*
);
|
Also, XP has more parameters for DebugPrint(), so I call it
DebugPrintEx().
I don't know exactly what do they mean. But seems that they are involved in
built-id message filtering. And I think we have to RTFM for
DbgSetDebugFilterState()/DbgQueryDebugFilterState().
2003-R2 made me even more happy with new internal function, lets call it DebugDispatch().
Along with sending debug messages it can break execution and invoke debugger.
This additional capability is used by DbgPrintReturnControlC() function.
Now all mentioned above functions and vDbgPrintExWithPrefix() wraps DebugDispatch().
NTSTATUS DebugDispatch(
IN PCH Prefix,
IN ULONG ComponentId,
IN ULONG Level,
IN PCH Format,
IN va_list arglist, // va_list == PCH[]*
IN BOOLEAN ContinueExecution);
|
In all cases PSTRING comes to DebugPrint()/DebugPrintEx() function, which calls DebugService()/DebugServiceEx().
Lets look inside them:
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 raises exception. In KdpTrap() exception handler the following checks are performed:
- if KdDebuggerNotPresent && Type != BREAKPOINT_PROMPT, exit
- Previous execution mode is checked. If call comes from UserMode, an additional
buffer validation is performed with ProbeForRead().
When all checks are passed, KdpPrintString() is invoked:
BOOLEAN
KdpPrintString (
IN PSTRING Output
);
|
After checks buffer is passed to KdpSendPacket() for sending to remote debugger.
Inside this function packet header and packed data blocks are generated.
Header and data are sent by separate calls to KdpSendString().
KdpSendString() uses exported function KdPortPutByte().
Here is a picture that shows call references. Exported functions are printed in bold.
Win32 OutputDebugString() behavior
Unicode-version OutputDebugStringW() converts Unicode characters to ANSI
and calls OutputDebugStringA(). OutputDebugStringA() registers own exception handler
and raises exception via RaiseException():
WINBASEAPI
VOID
WINAPI
RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST DWORD *lpArguments
);
dwExceptionCode = 0x40010006
dwExceptionFlags = 0
nNumberOfArguments = 2
lpArguments = PSTRING
|
Then function JUMPs to exit. PSTRING is generated from input string.
OutputDebugString() doesn't pass control to the kernel debug functions
described above.
If there is registered system debugger, it handles the exception. Otherwise
own OutputDebugString()'s handler is invoked. It does the following: Attempts to
open 'DBWIN_DATA_READY' and 'DBWIN_BUFFER_READY' Named Events, then
Named File Mapping 'DBWIN_BUFFER'. If any of these operations fails, the message
is dropped.
'DBWIN_BUFFER_READY' is set by capturing application. It signals that application is ready
to receive next message. 'DBWIN_DATA_READY' is set by OutputDebugString() and signals that
new message is copied to 'DBWIN_BUFFER'. 'DBWIN_BUFFER' has size of 4k (1 page). It has the
following structure:
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 string is always NULL-terminated.
See also:
|