Synchronize with trunk revision 59636 (just before Alex's CreateProcess revamp).
[reactos.git] / ntoskrnl / kdbg / kdb_cli.c
index 5d77e55..c40c282 100644 (file)
 #define KEY_SCAN_UP     72
 #define KEY_SCAN_DOWN   80
 
+/* Scan codes of keyboard keys: */
+#define KEYSC_END       0x004f
+#define KEYSC_PAGEUP    0x0049
+#define KEYSC_PAGEDOWN  0x0051
+#define KEYSC_HOME      0x0047
+#define KEYSC_ARROWUP   0x0048
+
 #define KDB_ENTER_CONDITION_TO_STRING(cond)                               \
                    ((cond) == KdbDoNotEnter ? "never" :                   \
                    ((cond) == KdbEnterAlways ? "always" :                 \
@@ -81,9 +88,17 @@ static BOOLEAN KdbpCmdBugCheck(ULONG Argc, PCHAR Argv[]);
 static BOOLEAN KdbpCmdFilter(ULONG Argc, PCHAR Argv[]);
 static BOOLEAN KdbpCmdSet(ULONG Argc, PCHAR Argv[]);
 static BOOLEAN KdbpCmdHelp(ULONG Argc, PCHAR Argv[]);
+static BOOLEAN KdbpCmdDmesg(ULONG Argc, PCHAR Argv[]);
+
+BOOLEAN ExpKdbgExtPool(ULONG Argc, PCHAR Argv[]);
+
+#ifdef __ROS_DWARF__
+static BOOLEAN KdbpCmdPrintStruct(ULONG Argc, PCHAR Argv[]);
+#endif
 
 /* GLOBALS *******************************************************************/
 
+static PKDBG_CLI_ROUTINE KdbCliCallbacks[10];
 static BOOLEAN KdbUseIntelSyntax = FALSE; /* Set to TRUE for intel syntax */
 static BOOLEAN KdbBreakOnModuleLoad = FALSE; /* Set to TRUE to break into KDB when a module is loaded */
 
@@ -95,12 +110,22 @@ static LONG KdbCommandHistoryIndex = 0;
 static ULONG KdbNumberOfRowsPrinted = 0;
 static ULONG KdbNumberOfColsPrinted = 0;
 static BOOLEAN KdbOutputAborted = FALSE;
+static BOOLEAN KdbRepeatLastCommand = FALSE;
 static LONG KdbNumberOfRowsTerminal = -1;
 static LONG KdbNumberOfColsTerminal = -1;
 
 PCHAR KdbInitFileBuffer = NULL; /* Buffer where KDBinit file is loaded into during initialization */
 BOOLEAN KdbpBugCheckRequested = FALSE;
 
+/* Vars for dmesg */
+/* defined in ../kd/kdio.c, declare here: */
+extern volatile BOOLEAN KdbpIsInDmesgMode;
+extern const ULONG KdpDmesgBufferSize;
+extern PCHAR KdpDmesgBuffer;
+extern volatile ULONG KdpDmesgCurrentPosition;
+extern volatile ULONG KdpDmesgFreeBytes;
+extern volatile ULONG KdbDmesgTotalWritten;
+
 static const struct
 {
     PCHAR Name;
@@ -118,6 +143,9 @@ static const struct
     { "sregs", "sregs", "Display status registers.", KdbpCmdRegs },
     { "dregs", "dregs", "Display debug registers.", KdbpCmdRegs },
     { "bt", "bt [*frameaddr|thread id]", "Prints current backtrace or from given frame addr", KdbpCmdBackTrace },
+#ifdef __ROS_DWARF__
+    { "dt", "dt [mod] [type] [addr]", "Print a struct.  Addr is optional.", KdbpCmdPrintStruct },
+#endif
 
     /* Flow control */
     { NULL, NULL, "Flow control", NULL },
@@ -150,7 +178,10 @@ static const struct
     { "bugcheck", "bugcheck", "Bugchecks the system.", KdbpCmdBugCheck },
     { "filter", "filter [error|warning|trace|info|level]+|-[componentname|default]", "Enable/disable debug channels", KdbpCmdFilter },
     { "set", "set [var] [value]", "Sets var to value or displays value of var.", KdbpCmdSet },
-    { "help", "help", "Display help screen.", KdbpCmdHelp }
+    { "dmesg", "dmesg", "Display debug messages on screen, with navigation on pages.", KdbpCmdDmesg },
+    { "kmsg", "kmsg", "Kernel dmesg. Alias for dmesg.", KdbpCmdDmesg },
+    { "help", "help", "Display help screen.", KdbpCmdHelp },
+    { "!pool", "!pool [Address [Flags]]", "Display information about pool allocations.", ExpKdbgExtPool }
 };
 
 /* FUNCTIONS *****************************************************************/
@@ -375,6 +406,24 @@ KdbpEvaluateExpression(
     return Ok;
 }
 
+BOOLEAN
+NTAPI
+KdbpGetHexNumber(
+    IN PCHAR pszNum,
+    OUT ULONG_PTR *pulValue)
+{
+    char *endptr;
+
+    /* Skip optional '0x' prefix */
+    if ((pszNum[0] == '0') && ((pszNum[1] == 'x') || (pszNum[1] == 'X')))
+        pszNum += 2;
+
+    /* Make a number from the string (hex) */
+    *pulValue = strtoul(pszNum, &endptr, 16);
+
+    return (*endptr == '\0');
+}
+
 /*!\brief Evaluates an expression and displays the result.
  */
 static BOOLEAN
@@ -434,6 +483,144 @@ KdbpCmdEvalExpression(
     return TRUE;
 }
 
+#ifdef __ROS_DWARF__
+
+/*!\brief Print a struct
+ */
+static VOID
+KdbpPrintStructInternal
+(PROSSYM_INFO Info,
+ PCHAR Indent,
+ BOOLEAN DoRead,
+ PVOID BaseAddress,
+ PROSSYM_AGGREGATE Aggregate)
+{
+    ULONG i;
+    ULONGLONG Result;
+    PROSSYM_AGGREGATE_MEMBER Member;
+    ULONG IndentLen = strlen(Indent);
+    ROSSYM_AGGREGATE MemberAggregate = {0 };
+
+    for (i = 0; i < Aggregate->NumElements; i++) {
+        Member = &Aggregate->Elements[i];
+        KdbpPrint("%s%p+%x: %s", Indent, ((PCHAR)BaseAddress) + Member->BaseOffset, Member->Size, Member->Name ? Member->Name : "<anoymous>");
+        if (DoRead) {
+            if (!strcmp(Member->Type, "_UNICODE_STRING")) {
+                KdbpPrint("\"%wZ\"\n", ((PCHAR)BaseAddress) + Member->BaseOffset);
+                continue;
+            } else if (!strcmp(Member->Type, "PUNICODE_STRING")) {
+                KdbpPrint("\"%wZ\"\n", *(((PUNICODE_STRING*)((PCHAR)BaseAddress) + Member->BaseOffset)));
+                continue;
+            }
+            switch (Member->Size) {
+            case 1:
+            case 2:
+            case 4:
+            case 8: {
+                Result = 0;
+                if (NT_SUCCESS(KdbpSafeReadMemory(&Result, ((PCHAR)BaseAddress) + Member->BaseOffset, Member->Size))) {
+                    if (Member->Bits) {
+                        Result >>= Member->FirstBit;
+                        Result &= ((1 << Member->Bits) - 1);
+                    }
+                    KdbpPrint(" %lx\n", Result);
+                }
+                else goto readfail;
+                break;
+            }
+            default: {
+                if (Member->Size < 8) {
+                    if (NT_SUCCESS(KdbpSafeReadMemory(&Result, ((PCHAR)BaseAddress) + Member->BaseOffset, Member->Size))) {
+                        ULONG j;
+                        for (j = 0; j < Member->Size; j++) {
+                            KdbpPrint(" %02x", (int)(Result & 0xff));
+                            Result >>= 8;
+                        }
+                    } else goto readfail;
+                } else {
+                    KdbpPrint(" %s @ %p {\n", Member->Type, ((PCHAR)BaseAddress) + Member->BaseOffset);
+                    Indent[IndentLen] = ' ';
+                    if (RosSymAggregate(Info, Member->Type, &MemberAggregate)) {
+                        KdbpPrintStructInternal(Info, Indent, DoRead, ((PCHAR)BaseAddress) + Member->BaseOffset, &MemberAggregate);
+                        RosSymFreeAggregate(&MemberAggregate);
+                    }
+                    Indent[IndentLen] = 0;
+                    KdbpPrint("%s}\n", Indent);
+                } break;
+            }
+            }
+        } else {
+        readfail:
+            if (Member->Size <= 8) {
+                KdbpPrint(" ??\n");
+            } else {
+                KdbpPrint(" %s @ %x {\n", Member->Type, Member->BaseOffset);
+                Indent[IndentLen] = ' ';
+                if (RosSymAggregate(Info, Member->Type, &MemberAggregate)) {
+                    KdbpPrintStructInternal(Info, Indent, DoRead, BaseAddress, &MemberAggregate);
+                    RosSymFreeAggregate(&MemberAggregate);
+                }
+                Indent[IndentLen] = 0;
+                KdbpPrint("%s}\n", Indent);
+            }
+        }
+    }
+}
+
+PROSSYM_INFO KdbpSymFindCachedFile(PUNICODE_STRING ModName);
+
+static BOOLEAN
+KdbpCmdPrintStruct(
+    ULONG Argc,
+    PCHAR Argv[])
+{
+    ULONG i;
+    ULONGLONG Result = 0;
+    PVOID BaseAddress = 0;
+    ROSSYM_AGGREGATE Aggregate = {0};
+    UNICODE_STRING ModName = {0};
+    ANSI_STRING AnsiName = {0};
+    CHAR Indent[100] = {0};
+    PROSSYM_INFO Info;
+
+    if (Argc < 3) goto end;
+    AnsiName.Length = AnsiName.MaximumLength = strlen(Argv[1]);
+    AnsiName.Buffer = Argv[1];
+    RtlAnsiStringToUnicodeString(&ModName, &AnsiName, TRUE);
+    Info = KdbpSymFindCachedFile(&ModName);
+
+    if (!Info || !RosSymAggregate(Info, Argv[2], &Aggregate)) {
+        DPRINT1("Could not get aggregate\n");
+        goto end;
+    }
+
+    // Get an argument for location if it was given
+    if (Argc > 3) {
+        ULONG len;
+        PCHAR ArgStart = Argv[3];
+        DPRINT1("Trying to get expression\n");
+        for (i = 3; i < Argc - 1; i++)
+        {
+            len = strlen(Argv[i]);
+            Argv[i][len] = ' ';
+        }
+
+        /* Evaluate the expression */
+        DPRINT1("Arg: %s\n", ArgStart);
+        if (KdbpEvaluateExpression(ArgStart, strlen(ArgStart), &Result)) {
+            BaseAddress = (PVOID)(ULONG_PTR)Result;
+            DPRINT1("BaseAddress: %p\n", BaseAddress);
+        }
+    }
+    DPRINT1("BaseAddress %p\n", BaseAddress);
+    KdbpPrintStructInternal(Info, Indent, !!BaseAddress, BaseAddress, &Aggregate);
+end:
+    RosSymFreeAggregate(&Aggregate);
+    RtlFreeUnicodeString(&ModName);
+    return TRUE;
+}
+#endif
+
 /*!\brief Display list of active debug channels
  */
 static BOOLEAN
@@ -595,7 +782,7 @@ KdbpCmdDisassembleX(
 
         while (Count > 0)
         {
-            if (!KdbSymPrintAddress((PVOID)Address))
+            if (!KdbSymPrintAddress((PVOID)Address, NULL))
                 KdbpPrint("<%x>:", Address);
             else
                 KdbpPrint(":");
@@ -621,7 +808,7 @@ KdbpCmdDisassembleX(
         /* Disassemble */
         while (Count-- > 0)
         {
-            if (!KdbSymPrintAddress((PVOID)Address))
+            if (!KdbSymPrintAddress((PVOID)Address, NULL))
                 KdbpPrint("<%08x>: ", Address);
             else
                 KdbpPrint(": ");
@@ -699,7 +886,7 @@ KdbpCmdRegs(
     else if (Argv[0][0] == 'c') /* cregs */
     {
         ULONG Cr0, Cr2, Cr3, Cr4;
-        KDESCRIPTOR Gdtr, Idtr;
+        KDESCRIPTOR Gdtr = {0, 0, 0}, Idtr = {0, 0, 0};
         USHORT Ldtr;
         static const PCHAR Cr0Bits[32] = { " PE", " MP", " EM", " TS", " ET", " NE", NULL, NULL,
                                            NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
@@ -783,6 +970,86 @@ KdbpCmdRegs(
     return TRUE;
 }
 
+static BOOLEAN
+KdbpTrapFrameFromPrevTss(
+    PKTRAP_FRAME TrapFrame)
+{
+    ULONG_PTR Eip, Ebp;
+    KDESCRIPTOR Gdtr;
+    KGDTENTRY Desc;
+    USHORT Sel;
+    PKTSS Tss;
+
+    Ke386GetGlobalDescriptorTable(&Gdtr.Limit);
+    Sel = Ke386GetTr();
+
+    if ((Sel & (sizeof(KGDTENTRY) - 1)) ||
+        (Sel < sizeof(KGDTENTRY)) ||
+        (Sel + sizeof(KGDTENTRY) - 1 > Gdtr.Limit))
+        return FALSE;
+
+    if (!NT_SUCCESS(KdbpSafeReadMemory(&Desc,
+                                       (PVOID)(Gdtr.Base + Sel),
+                                       sizeof(KGDTENTRY))))
+        return FALSE;
+
+    if (Desc.HighWord.Bits.Type != 0xB)
+        return FALSE;
+
+    Tss = (PKTSS)(ULONG_PTR)(Desc.BaseLow |
+                             Desc.HighWord.Bytes.BaseMid << 16 |
+                             Desc.HighWord.Bytes.BaseHi << 24);
+
+    if (!NT_SUCCESS(KdbpSafeReadMemory(&Sel,
+                                       (PVOID)&Tss->Backlink,
+                                       sizeof(USHORT))))
+        return FALSE;
+
+    if ((Sel & (sizeof(KGDTENTRY) - 1)) ||
+        (Sel < sizeof(KGDTENTRY)) ||
+        (Sel + sizeof(KGDTENTRY) - 1 > Gdtr.Limit))
+        return FALSE;
+
+    if (!NT_SUCCESS(KdbpSafeReadMemory(&Desc,
+                                       (PVOID)(Gdtr.Base + Sel),
+                                       sizeof(KGDTENTRY))))
+        return FALSE;
+
+    if (Desc.HighWord.Bits.Type != 0xB)
+        return FALSE;
+
+    Tss = (PKTSS)(ULONG_PTR)(Desc.BaseLow |
+                             Desc.HighWord.Bytes.BaseMid << 16 |
+                             Desc.HighWord.Bytes.BaseHi << 24);
+
+    if (!NT_SUCCESS(KdbpSafeReadMemory(&Eip,
+                                       (PVOID)&Tss->Eip,
+                                       sizeof(ULONG_PTR))))
+        return FALSE;
+
+    if (!NT_SUCCESS(KdbpSafeReadMemory(&Ebp,
+                                       (PVOID)&Tss->Ebp,
+                                       sizeof(ULONG_PTR))))
+        return FALSE;
+
+    TrapFrame->Eip = Eip;
+    TrapFrame->Ebp = Ebp;
+    return TRUE;
+}
+
+VOID __cdecl KiTrap02(VOID);
+VOID FASTCALL KiTrap03Handler(IN PKTRAP_FRAME);
+VOID __cdecl KiTrap08(VOID);
+VOID __cdecl KiTrap09(VOID);
+
+static BOOLEAN
+KdbpInNmiOrDoubleFaultHandler(
+    ULONG_PTR Address)
+{
+    return (Address > (ULONG_PTR)KiTrap02 && Address < (ULONG_PTR)KiTrap03Handler) ||
+           (Address > (ULONG_PTR)KiTrap08 && Address < (ULONG_PTR)KiTrap09);
+}
+
 /*!\brief Displays a backtrace.
  */
 static BOOLEAN
@@ -790,11 +1057,11 @@ KdbpCmdBackTrace(
     ULONG Argc,
     PCHAR Argv[])
 {
-    ULONG Count;
     ULONG ul;
     ULONGLONG Result = 0;
     ULONG_PTR Frame = KdbCurrentTrapFrame->Tf.Ebp;
     ULONG_PTR Address;
+    KTRAP_FRAME TrapFrame;
 
     if (Argc >= 2)
     {
@@ -806,7 +1073,6 @@ KdbpCmdBackTrace(
             ul = strtoul(Argv[Argc-1], NULL, 0);
             if (ul > 0)
             {
-                Count = ul;
                 Argc -= 2;
             }
         }
@@ -815,7 +1081,6 @@ KdbpCmdBackTrace(
             ul = strtoul(Argv[Argc-1] + 1, NULL, 0);
             if (ul > 0)
             {
-                Count = ul;
                 Argc--;
             }
         }
@@ -856,15 +1121,19 @@ KdbpCmdBackTrace(
         KdbpPrint("Eip:\n");
 
         /* Try printing the function at EIP */
-        if (!KdbSymPrintAddress((PVOID)KdbCurrentTrapFrame->Tf.Eip))
+        if (!KdbSymPrintAddress((PVOID)KdbCurrentTrapFrame->Tf.Eip, &KdbCurrentTrapFrame->Tf))
             KdbpPrint("<%08x>\n", KdbCurrentTrapFrame->Tf.Eip);
         else
             KdbpPrint("\n");
     }
 
+    TrapFrame = KdbCurrentTrapFrame->Tf;
     KdbpPrint("Frames:\n");
+
     for (;;)
     {
+        BOOLEAN GotNextFrame;
+
         if (Frame == 0)
             break;
 
@@ -874,16 +1143,35 @@ KdbpCmdBackTrace(
             break;
         }
 
+        if ((GotNextFrame = NT_SUCCESS(KdbpSafeReadMemory(&Frame, (PVOID)Frame, sizeof (ULONG_PTR)))))
+            TrapFrame.Ebp = Frame;
+
         /* Print the location of the call instruction */
-        if (!KdbSymPrintAddress((PVOID)(Address - 5)))
+        if (!KdbSymPrintAddress((PVOID)(Address - 5), &TrapFrame))
             KdbpPrint("<%08x>\n", Address);
         else
             KdbpPrint("\n");
 
+        if (KdbOutputAborted) break;
+
         if (Address == 0)
             break;
 
-        if (!NT_SUCCESS(KdbpSafeReadMemory(&Frame, (PVOID)Frame, sizeof (ULONG_PTR))))
+        if (KdbpInNmiOrDoubleFaultHandler(Address))
+        {
+            if ((GotNextFrame = KdbpTrapFrameFromPrevTss(&TrapFrame)))
+            {
+                Address = TrapFrame.Eip;
+                Frame = TrapFrame.Ebp;
+
+                if (!KdbSymPrintAddress((PVOID)Address, &TrapFrame))
+                    KdbpPrint("<%08x>\n", Address);
+                else
+                    KdbpPrint("\n");
+            }
+        }
+
+        if (!GotNextFrame)
         {
             KdbpPrint("Couldn't access memory at 0x%p!\n", Frame);
             break;
@@ -1161,7 +1449,7 @@ KdbpCmdBreakPoint(ULONG Argc, PCHAR Argv[])
         if (strcmp(Argv[i+1], "IF") == 0) /* IF found */
         {
             ConditionArgIndex = i + 2;
-            if (ConditionArgIndex >= Argc)
+            if ((ULONG)ConditionArgIndex >= Argc)
             {
                 KdbpPrint("%s: IF requires condition expression.\n", Argv[0]);
                 return TRUE;
@@ -1888,6 +2176,58 @@ KdbpCmdBugCheck(
     return FALSE;
 }
 
+VOID
+KdbpPager(
+    IN PCHAR Buffer,
+    IN ULONG BufLength);
+
+/*!\brief Display debug messages on screen, with paging.
+ *
+ * Keys for per-page view: Home, End, PageUp, Arrow Up, PageDown,
+ * all others are as PageDown.
+ */
+static BOOLEAN
+KdbpCmdDmesg(
+    ULONG Argc,
+    PCHAR Argv[])
+{
+  ULONG beg, end;
+
+  KdbpIsInDmesgMode = TRUE; /* Toggle logging flag */
+  if (!KdpDmesgBuffer)
+  {
+    KdbpPrint("Dmesg: error, buffer is not allocated! /DEBUGPORT=SCREEN kernel param required for dmesg.\n");
+    return TRUE;
+  }
+
+  KdbpPrint("*** Dmesg *** TotalWritten=%lu, BufferSize=%lu, CurrentPosition=%lu\n",
+            KdbDmesgTotalWritten, KdpDmesgBufferSize, KdpDmesgCurrentPosition);
+
+  // Pass data to the pager:
+  end = KdpDmesgCurrentPosition;
+  beg = (end + KdpDmesgFreeBytes) % KdpDmesgBufferSize;
+
+  // no roll-overs, and overwritten=lost bytes
+  if (KdbDmesgTotalWritten <= KdpDmesgBufferSize)
+  {
+    // show buffer (KdpDmesgBuffer + beg, num)
+    KdbpPager(KdpDmesgBuffer, KdpDmesgCurrentPosition);
+  }
+  else
+  {
+    // show 2 buffers: (KdpDmesgBuffer + beg, KdpDmesgBufferSize - beg)
+    //            and: (KdpDmesgBuffer,       end)
+    KdbpPager(KdpDmesgBuffer + beg, KdpDmesgBufferSize - beg);
+    KdbpPrint("*** Dmesg: buffer rollup ***\n");
+    KdbpPager(KdpDmesgBuffer,       end);
+  }
+  KdbpPrint("*** Dmesg: end of output ***\n");
+
+  KdbpIsInDmesgMode = FALSE; /* Toggle logging flag */
+
+  return TRUE;
+}
+
 /*!\brief Sets or displays a config variables value.
  */
 static BOOLEAN
@@ -2103,6 +2443,7 @@ KdbpCmdHelp(
  *
  * \note Doesn't correctly handle \\t and terminal escape sequences when calculating the
  *       number of lines required to print a single line from the Buffer in the terminal.
+ *       Prints maximum 4096 chars, because of its buffer size.
  */
 VOID
 KdbpPrint(
@@ -2208,12 +2549,12 @@ KdbpPrint(
         if (KdbNumberOfRowsTerminal <= 0)
         {
             /* Set number of rows to the default. */
-            KdbNumberOfRowsTerminal = 24;
+            KdbNumberOfRowsTerminal = 23; //24; //Mna.: 23 for SCREEN debugport
         }
         else if (KdbNumberOfColsTerminal <= 0)
         {
             /* Set number of cols to the default. */
-            KdbNumberOfColsTerminal = 80;
+            KdbNumberOfColsTerminal = 75; //80; //Mna.: 75 for SCREEN debugport
         }
     }
 
@@ -2245,10 +2586,13 @@ KdbpPrint(
         if (KdbNumberOfRowsTerminal > 0 &&
             (LONG)(KdbNumberOfRowsPrinted + RowsPrintedByTerminal) >= KdbNumberOfRowsTerminal)
         {
+            KdbRepeatLastCommand = FALSE;
+
             if (KdbNumberOfColsPrinted > 0)
                 DbgPrint("\n");
 
             DbgPrint("--- Press q to abort, any other key to continue ---");
+            RowsPrintedByTerminal++; /* added by Mna. */
 
             if (KdbDebugState & KD_DEBUG_KDSERIAL)
                 c = KdbpGetCharSerial();
@@ -2330,6 +2674,353 @@ KdbpPrint(
     }
 }
 
+/** memrchr(), explicitly defined, since was absent in MinGW of RosBE. */
+/*
+ * Reverse memchr()
+ * Find the last occurrence of 'c' in the buffer 's' of size 'n'.
+ */
+void *
+memrchr(const void *s, int c, size_t n)
+{
+    const unsigned char *cp;
+
+    if (n != 0)
+    {
+        cp = (unsigned char *)s + n;
+        do
+        {
+            if (*(--cp) == (unsigned char)c)
+                return (void *)cp;
+        } while (--n != 0);
+    }
+    return NULL;
+}
+
+/*!\brief Calculate pointer position for N lines upper of current position.
+ *
+ * \param Buffer     Characters buffer to operate on.
+ * \param BufLength  Buffer size.
+ *
+ * \note Calculate pointer position for N lines upper of current displaying
+ *       position within the given buffer.
+ *
+ * Used by KdbpPager().
+ * Now N lines count is hardcoded to KdbNumberOfRowsTerminal.
+ */
+PCHAR
+CountOnePageUp(PCHAR Buffer, ULONG BufLength, PCHAR pCurPos)
+{
+    PCHAR p;
+    // p0 is initial guess of Page Start
+    ULONG p0len = KdbNumberOfRowsTerminal * KdbNumberOfColsTerminal;
+    PCHAR p0 = pCurPos - p0len;
+    PCHAR prev_p = p0, p1;
+    ULONG j;
+
+    if (pCurPos < Buffer)
+        pCurPos = Buffer;
+    ASSERT(pCurPos <= Buffer + BufLength);
+
+    p = memrchr(p0, '\n', p0len);
+    if (NULL == p)
+        p = p0;
+    for (j = KdbNumberOfRowsTerminal; j--; )
+    {
+        int linesCnt;
+        p1 = memrchr(p0, '\n', p-p0);
+        prev_p = p;
+        p = p1;
+        if (NULL == p)
+        {
+            p = prev_p;
+            if (NULL == p)
+                p = p0;
+            break;
+        }
+        linesCnt = (KdbNumberOfColsTerminal+prev_p-p-2) / KdbNumberOfColsTerminal;
+        if (linesCnt > 1)
+            j -= linesCnt-1;
+    }
+
+    ASSERT(p != 0);
+    ++p;
+    return p;
+}
+
+/*!\brief Prints the given string with, page by page.
+ *
+ * \param Buffer     Characters buffer to print.
+ * \param BufferLen  Buffer size.
+ *
+ * \note Doesn't correctly handle \\t and terminal escape sequences when calculating the
+ *       number of lines required to print a single line from the Buffer in the terminal.
+ *       Maximum length of buffer is limited only by memory size.
+ *
+ * Note: BufLength should be greater then (KdbNumberOfRowsTerminal * KdbNumberOfColsTerminal).
+ *
+ */
+VOID
+KdbpPager(
+    IN PCHAR Buffer,
+    IN ULONG BufLength)
+{
+    static CHAR InBuffer[4096];
+    static BOOLEAN TerminalInitialized = FALSE;
+    static BOOLEAN TerminalConnected = FALSE;
+    static BOOLEAN TerminalReportsSize = TRUE;
+    CHAR c = '\0';
+    PCHAR p, p2;
+    ULONG Length;
+    ULONG i, j;
+    LONG RowsPrintedByTerminal;
+    ULONG ScanCode;
+
+    if( BufLength == 0)
+      return;
+
+    /* Check if the user has aborted output of the current command */
+    if (KdbOutputAborted)
+        return;
+
+    /* Initialize the terminal */
+    if (!TerminalInitialized)
+    {
+        DbgPrint("\x1b[7h");      /* Enable linewrap */
+
+        /* Query terminal type */
+        /*DbgPrint("\x1b[Z");*/
+        DbgPrint("\x05");
+
+        TerminalInitialized = TRUE;
+        Length = 0;
+        KeStallExecutionProcessor(100000);
+
+        for (;;)
+        {
+            c = KdbpTryGetCharSerial(5000);
+            if (c == -1)
+                break;
+
+            InBuffer[Length++] = c;
+            if (Length >= (sizeof (InBuffer) - 1))
+                break;
+        }
+
+        InBuffer[Length] = '\0';
+        if (Length > 0)
+            TerminalConnected = TRUE;
+    }
+
+    /* Get number of rows and columns in terminal */
+    if ((KdbNumberOfRowsTerminal < 0) || (KdbNumberOfColsTerminal < 0) ||
+        (KdbNumberOfRowsPrinted) == 0) /* Refresh terminal size each time when number of rows printed is 0 */
+    {
+        if ((KdbDebugState & KD_DEBUG_KDSERIAL) && TerminalConnected && TerminalReportsSize)
+        {
+            /* Try to query number of rows from terminal. A reply looks like "\x1b[8;24;80t" */
+            TerminalReportsSize = FALSE;
+            KeStallExecutionProcessor(100000);
+            DbgPrint("\x1b[18t");
+            c = KdbpTryGetCharSerial(5000);
+
+            if (c == KEY_ESC)
+            {
+                c = KdbpTryGetCharSerial(5000);
+                if (c == '[')
+                {
+                    Length = 0;
+
+                    for (;;)
+                    {
+                        c = KdbpTryGetCharSerial(5000);
+                        if (c == -1)
+                            break;
+
+                        InBuffer[Length++] = c;
+                        if (isalpha(c) || Length >= (sizeof (InBuffer) - 1))
+                            break;
+                    }
+
+                    InBuffer[Length] = '\0';
+                    if (InBuffer[0] == '8' && InBuffer[1] == ';')
+                    {
+                        for (i = 2; (i < Length) && (InBuffer[i] != ';'); i++);
+
+                        if (Buffer[i] == ';')
+                        {
+                            Buffer[i++] = '\0';
+
+                            /* Number of rows is now at Buffer + 2 and number of cols at Buffer + i */
+                            KdbNumberOfRowsTerminal = strtoul(InBuffer + 2, NULL, 0);
+                            KdbNumberOfColsTerminal = strtoul(InBuffer + i, NULL, 0);
+                            TerminalReportsSize = TRUE;
+                        }
+                    }
+                }
+                /* Clear further characters */
+                while ((c = KdbpTryGetCharSerial(5000)) != -1);
+            }
+        }
+
+        if (KdbNumberOfRowsTerminal <= 0)
+        {
+            /* Set number of rows to the default. */
+            KdbNumberOfRowsTerminal = 24;
+        }
+        else if (KdbNumberOfColsTerminal <= 0)
+        {
+            /* Set number of cols to the default. */
+            KdbNumberOfColsTerminal = 80;
+        }
+    }
+
+    /* Get the string */
+    p = Buffer;
+
+    while (p[0] != '\0')
+    {
+        if ( p > Buffer+BufLength)
+        {
+          DbgPrint("Dmesg: error, p > Buffer+BufLength,d=%d", p - (Buffer+BufLength));
+          return;
+        }
+        i = strcspn(p, "\n");
+
+        // Are we out of buffer?
+        if (p + i > Buffer + BufLength)
+          // Leaving pager function:
+          break;
+
+        /* Calculate the number of lines which will be printed in the terminal
+         * when outputting the current line
+         */
+        if (i > 0)
+            RowsPrintedByTerminal = (i + KdbNumberOfColsPrinted - 1) / KdbNumberOfColsTerminal;
+        else
+            RowsPrintedByTerminal = 0;
+
+        if (p[i] == '\n')
+            RowsPrintedByTerminal++;
+
+        /*DbgPrint("!%d!%d!%d!%d!", KdbNumberOfRowsPrinted, KdbNumberOfColsPrinted, i, RowsPrintedByTerminal);*/
+
+        /* Display a prompt if we printed one screen full of text */
+        if (KdbNumberOfRowsTerminal > 0 &&
+            (LONG)(KdbNumberOfRowsPrinted + RowsPrintedByTerminal) >= KdbNumberOfRowsTerminal)
+        {
+            KdbRepeatLastCommand = FALSE;
+
+            if (KdbNumberOfColsPrinted > 0)
+                DbgPrint("\n");
+
+            DbgPrint("--- Press q to abort, e/End,h/Home,u/PgUp, other key/PgDn ---");
+            RowsPrintedByTerminal++;
+
+            if (KdbDebugState & KD_DEBUG_KDSERIAL)
+                c = KdbpGetCharSerial();
+            else
+                c = KdbpGetCharKeyboard(&ScanCode);
+
+            if (c == '\r')
+            {
+                /* Try to read '\n' which might follow '\r' - if \n is not received here
+                 * it will be interpreted as "return" when the next command should be read.
+                 */
+                if (KdbDebugState & KD_DEBUG_KDSERIAL)
+                    c = KdbpTryGetCharSerial(5);
+                else
+                    c = KdbpTryGetCharKeyboard(&ScanCode, 5);
+            }
+
+            //DbgPrint("\n"); //Consize version: don't show pressed key
+            DbgPrint(" '%c'/scan=%04x\n", c, ScanCode); // Shows pressed key
+
+            if (c == 'q')
+            {
+                KdbOutputAborted = TRUE;
+                return;
+            }
+            if (     ScanCode == KEYSC_END || c=='e')
+            {
+              PCHAR pBufEnd = Buffer + BufLength;
+              p = CountOnePageUp(Buffer, BufLength, pBufEnd);
+              i = strcspn(p, "\n");
+            }
+            else if (ScanCode == KEYSC_PAGEUP  || c=='u')
+            {
+              p = CountOnePageUp(Buffer, BufLength, p);
+              i = strcspn(p, "\n");
+            }
+            else if (ScanCode == KEYSC_HOME || c=='h')
+            {
+              p = Buffer;
+              i = strcspn(p, "\n");
+            }
+            else if (ScanCode == KEYSC_ARROWUP)
+            {
+              p = CountOnePageUp(Buffer, BufLength, p);
+              i = strcspn(p, "\n");
+            }
+
+            KdbNumberOfRowsPrinted = 0;
+            KdbNumberOfColsPrinted = 0;
+        }
+
+        /* Insert a NUL after the line and print only the current line. */
+        if (p[i] == '\n' && p[i + 1] != '\0')
+        {
+            c = p[i + 1];
+            p[i + 1] = '\0';
+        }
+        else
+        {
+            c = '\0';
+        }
+
+        /* Remove escape sequences from the line if there's no terminal connected */
+        if (!TerminalConnected)
+        {
+            while ((p2 = strrchr(p, '\x1b'))) /* Look for escape character */
+            {
+                if (p2[1] == '[')
+                {
+                    j = 2;
+                    while (!isalpha(p2[j++]));
+                    strcpy(p2, p2 + j);
+                }
+                else
+                {
+                    strcpy(p2, p2 + 1);
+                }
+            }
+        }
+
+        // The main printing of the current line:
+        DbgPrint(p);
+
+        // restore not null char with saved:
+        if (c != '\0')
+            p[i + 1] = c;
+
+        /* Set p to the start of the next line and
+         * remember the number of rows/cols printed
+         */
+        p += i;
+        if (p[0] == '\n')
+        {
+            p++;
+            KdbNumberOfColsPrinted = 0;
+        }
+        else
+        {
+            ASSERT(p[0] == '\0');
+            KdbNumberOfColsPrinted += i;
+        }
+
+        KdbNumberOfRowsPrinted += RowsPrintedByTerminal;
+    }
+}
+
 /*!\brief Appends a command to the command history
  *
  * \param Command  Pointer to the command to append to the history.
@@ -2417,7 +3108,7 @@ KdbpReadCommand(
     PCHAR Orig = Buffer;
     ULONG ScanCode = 0;
     BOOLEAN EchoOn;
-    static CHAR LastCommand[1024] = "";
+    static CHAR LastCommand[1024];
     static CHAR NextKey = '\0';
     INT CmdHistIndex = -1;
     INT i;
@@ -2489,17 +3180,16 @@ KdbpReadCommand(
              * Repeat the last command if the user presses enter. Reduces the
              * risk of RSI when single-stepping.
              */
-            if (Buffer == Orig)
+            if (Buffer != Orig)
             {
-                strncpy(Buffer, LastCommand, Size);
-                Buffer[Size - 1] = '\0';
+                KdbRepeatLastCommand = TRUE;
+                *Buffer = '\0';
+                RtlStringCbCopyA(LastCommand, sizeof(LastCommand), Orig);
             }
+            else if (KdbRepeatLastCommand)
+                RtlStringCbCopyA(Buffer, Size, LastCommand);
             else
-            {
                 *Buffer = '\0';
-                strncpy(LastCommand, Orig, sizeof (LastCommand));
-                LastCommand[sizeof (LastCommand) - 1] = '\0';
-            }
 
             return;
         }
@@ -2598,6 +3288,82 @@ KdbpReadCommand(
     }
 }
 
+
+BOOLEAN
+NTAPI
+KdbRegisterCliCallback(
+    PVOID Callback,
+    BOOLEAN Deregister)
+{
+    ULONG i;
+
+    /* Loop all entries */
+    for (i = 0; i < _countof(KdbCliCallbacks); i++)
+    {
+        /* Check if deregistering was requested */
+        if (Deregister)
+        {
+            /* Check if this entry is the one that was registered */
+            if (KdbCliCallbacks[i] == Callback)
+            {
+                /* Delete it and report success */
+                KdbCliCallbacks[i] = NULL;
+                return TRUE;
+            }
+        }
+        else
+        {
+            /* Check if this entry is free */
+            if (KdbCliCallbacks[i] == NULL)
+            {
+                /* Set it and and report success */
+                KdbCliCallbacks[i] = Callback;
+                return TRUE;
+            }
+        }
+    }
+
+    /* Unsuccessful */
+    return FALSE;
+}
+
+/*! \brief Invokes registered CLI callbacks until one of them handled the
+ *         Command.
+ *
+ * \param Command - Command line to parse and execute if possible.
+ * \param Argc - Number of arguments in Argv
+ * \param Argv - Array of strings, each of them containing one argument.
+ *
+ * \return TRUE, if the command was handled, FALSE if it was not handled.
+ */
+static
+BOOLEAN
+KdbpInvokeCliCallbacks(
+    IN PCHAR Command,
+    IN ULONG Argc,
+    IN PCH Argv[])
+{
+    ULONG i;
+
+    /* Loop all entries */
+    for (i = 0; i < _countof(KdbCliCallbacks); i++)
+    {
+        /* Check if this entry is registered */
+        if (KdbCliCallbacks[i])
+        {
+            /* Invoke the callback and check if it handled the command */
+            if (KdbCliCallbacks[i](Command, Argc, Argv))
+            {
+                return TRUE;
+            }
+        }
+    }
+
+    /* None of the callbacks handled the command */
+    return FALSE;
+}
+
+
 /*!\brief Parses command line and executes command if found
  *
  * \param Command    Command line to parse and execute if possible.
@@ -2612,11 +3378,11 @@ KdbpDoCommand(
     ULONG i;
     PCHAR p;
     ULONG Argc;
+    // FIXME: for what do we need a 1024 characters command line and 256 tokens?
     static PCH Argv[256];
     static CHAR OrigCommand[1024];
 
-    strncpy(OrigCommand, Command, sizeof(OrigCommand) - 1);
-    OrigCommand[sizeof(OrigCommand) - 1] = '\0';
+    RtlStringCbCopyA(OrigCommand, sizeof(OrigCommand), Command);
 
     Argc = 0;
     p = Command;
@@ -2653,6 +3419,12 @@ KdbpDoCommand(
         }
     }
 
+    /* Now invoke the registered callbacks */
+    if (KdbpInvokeCliCallbacks(Command, Argc, Argv))
+    {
+        return TRUE;
+    }
+
     KdbpPrint("Command '%s' is unknown.\n", OrigCommand);
     return TRUE;
 }
@@ -2670,7 +3442,7 @@ KdbpCliMainLoop(
 
     if (EnteredOnSingleStep)
     {
-        if (!KdbSymPrintAddress((PVOID)KdbCurrentTrapFrame->Tf.Eip))
+        if (!KdbSymPrintAddress((PVOID)KdbCurrentTrapFrame->Tf.Eip, &KdbCurrentTrapFrame->Tf))
         {
             KdbpPrint("<%x>", KdbCurrentTrapFrame->Tf.Eip);
         }
@@ -2697,6 +3469,9 @@ KdbpCliMainLoop(
     /* Main loop */
     do
     {
+        /* Reset the number of rows/cols printed */
+        KdbNumberOfRowsPrinted = KdbNumberOfColsPrinted = 0;
+
         /* Print the prompt */
         KdbpPrint("kdb:> ");
 
@@ -2710,6 +3485,7 @@ KdbpCliMainLoop(
 
         /* Call the command */
         Continue = KdbpDoCommand(Command);
+        KdbOutputAborted = FALSE;
     }
     while (Continue);
 }
@@ -2802,7 +3578,8 @@ KdbpCliInit()
     InitializeObjectAttributes(&ObjectAttributes, &FileName, 0, NULL, NULL);
 
     /* Open the file */
-    Status = ZwOpenFile(&hFile, FILE_READ_DATA, &ObjectAttributes, &Iosb, 0,
+    Status = ZwOpenFile(&hFile, FILE_READ_DATA | SYNCHRONIZE,
+                        &ObjectAttributes, &Iosb, 0,
                         FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT |
                         FILE_NO_INTERMEDIATE_BUFFERING);
     if (!NT_SUCCESS(Status))
@@ -2918,6 +3695,9 @@ KdpPrompt(IN LPSTR InString,
                         *(KdpPromptString.Buffer + i));
     }
 
+    if (!(KdbDebugState & KD_DEBUG_KDSERIAL))
+        KbdDisableMouse();
+
     /* Loop the whole string */
     for (i = 0; i < OutStringLength; i++)
     {
@@ -2960,7 +3740,7 @@ KdpPrompt(IN LPSTR InString,
                 KdbpTryGetCharKeyboard(&DummyScanCode, 5);
             }
 
-            /* 
+            /*
              * Null terminate the output string -- documentation states that
              * DbgPrompt does not null terminate, but it does
              */
@@ -2968,7 +3748,7 @@ KdpPrompt(IN LPSTR InString,
 
             /* Print a new line */
             KdPortPutByteEx(&SerialPortInfo, '\r');
-            KdPortPutByteEx(&SerialPortInfo, '\n');         
+            KdPortPutByteEx(&SerialPortInfo, '\n');
 
             /* Release spinlock */
             KiReleaseSpinLock(&KdpSerialSpinLock);
@@ -2985,6 +3765,9 @@ KdpPrompt(IN LPSTR InString,
         KdPortPutByteEx(&SerialPortInfo, Response);
     }
 
+    if (!(KdbDebugState & KD_DEBUG_KDSERIAL))
+        KbdEnableMouse();
+
     /* Print a new line */
     KdPortPutByteEx(&SerialPortInfo, '\r');
     KdPortPutByteEx(&SerialPortInfo, '\n');