[NTOS:KDBG] Reintroduce the capability of KdbpCliInit() to interpret the KDBinit file (#4917)

Addendum to commit baa47fa5e.

Similarly to what was originally done, have KdbpCliInterpretInitFile()
parse the KDBinit file by breaking back into the debugger.
But contrary to before, replace the deprecated call to KdbEnter() by
a standard DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C) . This allows
KdbEnterDebuggerException() to do the KdbpCliInterpretInitFile() call.

Additional fixes and improvements:

- Run KdbpCliInterpretInitFile() in full KDBG environment (interrupts
  disabled, modified IRQL, own stack), like the usual interactive loop.

- The KDBinit data buffer must be in non-paged pool.

- Demote the "Could not open KDBinit" error to a DPRINT, so that it
  doesn't pollute the debug log when the KDBG init function is called
  early (before the storage stack is initialized), or if the file
  doesn't exist -- since this is an optional feature.
This commit is contained in:
Hermès Bélusca-Maïto 2022-11-28 03:13:31 +01:00
parent b86c4bd522
commit b15963abb8
No known key found for this signature in database
GPG key ID: 3B2539C65E7B93D0
3 changed files with 63 additions and 46 deletions

View file

@ -1141,20 +1141,29 @@ KdbpAttachToProcess(
return KdbpAttachToThread(Thread->Cid.UniqueThread); return KdbpAttachToThread(Thread->Cid.UniqueThread);
} }
/*!\brief Calls the main loop ... /**
*/ * @brief Calls the main interactive debugger loop.
**/
static VOID static VOID
KdbpCallMainLoop(VOID) KdbpCallMainLoop(VOID)
{ {
KdbpCliMainLoop(KdbEnteredOnSingleStep); KdbpCliMainLoop(KdbEnteredOnSingleStep);
} }
/*!\brief Internal function to enter KDB. /**
* @brief
* Internal function to enter KDBG and run the specified procedure.
* *
* Disables interrupts, releases display ownership, ... * Disables interrupts, releases display ownership, ...
*/ *
* @param[in] Procedure
* The procedure to execute under the KDBG environment.
* Either execute the main interactive debugger loop (KdbpCallMainLoop)
* or run the KDBinit file (KdbpCliInterpretInitFile).
**/
static VOID static VOID
KdbpInternalEnter(VOID) KdbpInternalEnter(
_In_ VOID (*Procedure)(VOID))
{ {
PETHREAD Thread; PETHREAD Thread;
PVOID SavedInitialStack, SavedStackBase, SavedKernelStack; PVOID SavedInitialStack, SavedStackBase, SavedKernelStack;
@ -1166,7 +1175,7 @@ KdbpInternalEnter(VOID)
if (KdpDebugMode.Screen) if (KdpDebugMode.Screen)
KdpScreenAcquire(); KdpScreenAcquire();
/* Call the interface's main loop on a different stack */ /* Call the specified debugger procedure on a different stack */
Thread = PsGetCurrentThread(); Thread = PsGetCurrentThread();
SavedInitialStack = Thread->Tcb.InitialStack; SavedInitialStack = Thread->Tcb.InitialStack;
SavedStackBase = Thread->Tcb.StackBase; SavedStackBase = Thread->Tcb.StackBase;
@ -1179,7 +1188,7 @@ KdbpInternalEnter(VOID)
// KdbPrintf("Switching to KDB stack 0x%08x-0x%08x (Current Stack is 0x%08x)\n", // KdbPrintf("Switching to KDB stack 0x%08x-0x%08x (Current Stack is 0x%08x)\n",
// Thread->Tcb.StackLimit, Thread->Tcb.StackBase, Esp); // Thread->Tcb.StackLimit, Thread->Tcb.StackBase, Esp);
KdbpStackSwitchAndCall(KdbStack + KDB_STACK_SIZE - KDB_STACK_RESERVE, KdbpCallMainLoop); KdbpStackSwitchAndCall(KdbStack + KDB_STACK_SIZE - KDB_STACK_RESERVE, Procedure);
Thread->Tcb.InitialStack = SavedInitialStack; Thread->Tcb.InitialStack = SavedInitialStack;
Thread->Tcb.StackBase = SavedStackBase; Thread->Tcb.StackBase = SavedStackBase;
@ -1276,6 +1285,7 @@ KdbEnterDebuggerException(
ULONG OldEflags; ULONG OldEflags;
KIRQL OldIrql; KIRQL OldIrql;
NTSTATUS ExceptionCode; NTSTATUS ExceptionCode;
VOID (*EntryPoint)(VOID) = KdbpCallMainLoop;
ExceptionCode = (ExceptionRecord ? ExceptionRecord->ExceptionCode : STATUS_BREAKPOINT); ExceptionCode = (ExceptionRecord ? ExceptionRecord->ExceptionCode : STATUS_BREAKPOINT);
@ -1481,11 +1491,15 @@ KdbEnterDebuggerException(
} }
else if (ExceptionCode == STATUS_BREAKPOINT) else if (ExceptionCode == STATUS_BREAKPOINT)
{ {
/* Do the condition check and banner display only if we enter
* from a true code breakpoint. We skip those when running the
* KDBinit file, because it is done via an artificial breakpoint. */
if (KdbInitFileBuffer) if (KdbInitFileBuffer)
{ {
KdbpCliInterpretInitFile(); EntryPoint = KdbpCliInterpretInitFile;
EnterConditionMet = FALSE; goto EnterKdbg;
} }
if (!EnterConditionMet) if (!EnterConditionMet)
{ {
return kdHandleException; return kdHandleException;
@ -1493,6 +1507,7 @@ KdbEnterDebuggerException(
KdbPrintf("\nEntered debugger on embedded INT3 at 0x%04x:0x%p.\n", KdbPrintf("\nEntered debugger on embedded INT3 at 0x%04x:0x%p.\n",
Context->SegCs & 0xffff, KeGetContextPc(Context)); Context->SegCs & 0xffff, KeGetContextPc(Context));
EnterKdbg:;
} }
else else
{ {
@ -1543,8 +1558,8 @@ KdbEnterDebuggerException(
return kdHandleException; return kdHandleException;
} }
/* Call the main loop */ /* Enter KDBG proper and run either the main loop or the KDBinit file */
KdbpInternalEnter(); KdbpInternalEnter(EntryPoint);
/* Check if we should single step */ /* Check if we should single step */
if (KdbNumSingleSteps > 0) if (KdbNumSingleSteps > 0)

View file

@ -61,7 +61,7 @@ typedef enum _KD_CONTINUE_TYPE
/* GLOBALS *******************************************************************/ /* GLOBALS *******************************************************************/
extern PCHAR KdbInitFileBuffer; extern volatile PCHAR KdbInitFileBuffer;
extern PEPROCESS KdbCurrentProcess; extern PEPROCESS KdbCurrentProcess;
extern PETHREAD KdbCurrentThread; extern PETHREAD KdbCurrentThread;

View file

@ -134,7 +134,7 @@ static ULONG KdbNumberOfColsPrinted = 0;
static BOOLEAN KdbOutputAborted = FALSE; static BOOLEAN KdbOutputAborted = FALSE;
static BOOLEAN KdbRepeatLastCommand = FALSE; static BOOLEAN KdbRepeatLastCommand = FALSE;
PCHAR KdbInitFileBuffer = NULL; /* Buffer where KDBinit file is loaded into during initialization */ volatile PCHAR KdbInitFileBuffer = NULL; /* Buffer where KDBinit file is loaded into during initialization */
BOOLEAN KdbpBugCheckRequested = FALSE; BOOLEAN KdbpBugCheckRequested = FALSE;
/* Variables for Dmesg */ /* Variables for Dmesg */
@ -3330,20 +3330,28 @@ KdbpCliMainLoop(
} }
} }
/*!\brief This function is called by KdbEnterDebuggerException... /**
* @brief
* Interprets the KDBinit file from the \SystemRoot\System32\drivers\etc
* directory, that has been loaded by KdbpCliInit().
* *
* Used to interpret the init file in a context with a trapframe setup * This function is used to interpret the init file in the debugger context
* (KdbpCliInit call KdbEnter which will call KdbEnterDebuggerException which will * with a trap frame set up. KdbpCliInit() enters the debugger by calling
* call this function if KdbInitFileBuffer is not NULL. * DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C). In turn, this will call
*/ * KdbEnterDebuggerException() which will finally call this function if
* KdbInitFileBuffer is not NULL.
**/
VOID VOID
KdbpCliInterpretInitFile(VOID) KdbpCliInterpretInitFile(VOID)
{ {
PCHAR p1, p2; PCHAR p1, p2;
p1 = InterlockedExchangePointer((PVOID*)&KdbInitFileBuffer, NULL);
if (!p1)
return;
/* Execute the commands in the init file */ /* Execute the commands in the init file */
DPRINT("KDB: Executing KDBinit file...\n"); KdbPuts("KDB: Executing KDBinit file...\n");
p1 = KdbInitFileBuffer;
while (p1[0] != '\0') while (p1[0] != '\0')
{ {
size_t i = strcspn(p1, "\r\n"); size_t i = strcspn(p1, "\r\n");
@ -3360,11 +3368,12 @@ KdbpCliInterpretInitFile(VOID)
if (strncmp(p2, "break", sizeof("break")-1) == 0 && if (strncmp(p2, "break", sizeof("break")-1) == 0 &&
(p2[sizeof("break")-1] == '\0' || isspace(p2[sizeof("break")-1]))) (p2[sizeof("break")-1] == '\0' || isspace(p2[sizeof("break")-1])))
{ {
/* break into the debugger */ /* Run the interactive debugger loop */
KdbpCliMainLoop(FALSE); KdbpCliMainLoop(FALSE);
} }
else if (p2[0] != '#' && p2[0] != '\0') /* Ignore empty lines and comments */ else if (p2[0] != '#' && p2[0] != '\0') /* Ignore empty lines and comments */
{ {
/* Invoke the command */
KdbpDoCommand(p1); KdbpDoCommand(p1);
} }
@ -3375,14 +3384,14 @@ KdbpCliInterpretInitFile(VOID)
while (p1[0] == '\r' || p1[0] == '\n') while (p1[0] == '\r' || p1[0] == '\n')
p1++; p1++;
} }
DPRINT("KDB: KDBinit executed\n"); KdbPuts("KDB: KDBinit executed\n");
} }
/** /**
* @brief Called when KDB is initialized. * @brief Called when KDB is initialized.
* *
* Reads the KDBinit file from the SystemRoot\System32\drivers\etc directory * Loads the KDBinit file from the \SystemRoot\System32\drivers\etc
* and executes it. * directory and interprets it, by calling back into the debugger.
**/ **/
NTSTATUS NTSTATUS
KdbpCliInit(VOID) KdbpCliInit(VOID)
@ -3393,9 +3402,8 @@ KdbpCliInit(VOID)
IO_STATUS_BLOCK Iosb; IO_STATUS_BLOCK Iosb;
FILE_STANDARD_INFORMATION FileStdInfo; FILE_STANDARD_INFORMATION FileStdInfo;
HANDLE hFile = NULL; HANDLE hFile = NULL;
INT FileSize; ULONG FileSize;
PCHAR FileBuffer; PCHAR FileBuffer;
ULONG OldEflags;
/* Don't load the KDBinit file if its buffer is already lying around */ /* Don't load the KDBinit file if its buffer is already lying around */
if (KdbInitFileBuffer) if (KdbInitFileBuffer)
@ -3416,7 +3424,7 @@ KdbpCliInit(VOID)
FILE_NO_INTERMEDIATE_BUFFERING); FILE_NO_INTERMEDIATE_BUFFERING);
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
{ {
DPRINT1("Could not open \\SystemRoot\\System32\\drivers\\etc\\KDBinit (Status 0x%lx)\n", Status); DPRINT("Could not open %wZ (Status 0x%lx)\n", &FileName, Status);
return Status; return Status;
} }
@ -3427,22 +3435,23 @@ KdbpCliInit(VOID)
if (!NT_SUCCESS(Status)) if (!NT_SUCCESS(Status))
{ {
ZwClose(hFile); ZwClose(hFile);
DPRINT1("Could not query size of \\SystemRoot\\System32\\drivers\\etc\\KDBinit (Status 0x%lx)\n", Status); DPRINT1("Could not query size of %wZ (Status 0x%lx)\n", &FileName, Status);
return Status; return Status;
} }
FileSize = FileStdInfo.EndOfFile.u.LowPart; FileSize = FileStdInfo.EndOfFile.u.LowPart;
/* Allocate memory for the file */ /* Allocate memory for the file (add 1 byte for terminating NUL) */
FileBuffer = ExAllocatePool(PagedPool, FileSize + 1); /* add 1 byte for terminating '\0' */ FileBuffer = ExAllocatePool(NonPagedPool, FileSize + 1);
if (!FileBuffer) if (!FileBuffer)
{ {
ZwClose(hFile); ZwClose(hFile);
DPRINT1("Could not allocate %d bytes for KDBinit file\n", FileSize); DPRINT1("Could not allocate %lu bytes for KDBinit file\n", FileSize);
return Status; return Status;
} }
/* Load file into memory */ /* Load file into memory */
Status = ZwReadFile(hFile, NULL, NULL, NULL, &Iosb, FileBuffer, FileSize, NULL, NULL); Status = ZwReadFile(hFile, NULL, NULL, NULL, &Iosb,
FileBuffer, FileSize, NULL, NULL);
ZwClose(hFile); ZwClose(hFile);
if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE)) if (!NT_SUCCESS(Status) && (Status != STATUS_END_OF_FILE))
@ -3452,20 +3461,13 @@ KdbpCliInit(VOID)
return Status; return Status;
} }
FileSize = min(FileSize, (INT)Iosb.Information); FileSize = min(FileSize, (ULONG)Iosb.Information);
FileBuffer[FileSize] = '\0'; FileBuffer[FileSize] = ANSI_NULL;
/* Enter critical section */ /* Interpret the KDBinit file by calling back into the debugger */
OldEflags = __readeflags(); InterlockedExchangePointer((PVOID*)&KdbInitFileBuffer, FileBuffer);
_disable(); DbgBreakPointWithStatus(DBG_STATUS_CONTROL_C);
InterlockedExchangePointer((PVOID*)&KdbInitFileBuffer, NULL);
/* Interpret the init file... */
KdbInitFileBuffer = FileBuffer;
//KdbEnter(); // FIXME, see commit baa47fa5e
KdbInitFileBuffer = NULL;
/* Leave critical section */
__writeeflags(OldEflags);
ExFreePool(FileBuffer); ExFreePool(FileBuffer);
@ -3607,7 +3609,7 @@ KdbInitialize(
if (BootPhase >= 2) if (BootPhase >= 2)
{ {
/* I/O is now set up for disk access: Read KDB Data */ /* I/O is now set up for disk access: load the KDBinit file */
NTSTATUS Status = KdbpCliInit(); NTSTATUS Status = KdbpCliInit();
/* Schedule an I/O reinitialization if needed */ /* Schedule an I/O reinitialization if needed */