Index: ntoskrnl/kd/kdio.c =================================================================== --- ntoskrnl/kd/kdio.c (revision 51206) +++ ntoskrnl/kd/kdio.c (working copy) @@ -29,8 +29,30 @@ /* Current Port in use. FIXME: Do we support more then one? */ ULONG KdpPort; -/* DEBUG LOG FUNCTIONS *******************************************************/ +//Dmesg +const ULONG KdpDmesgBufferSize = 128*1024;; //512*1024; //5*1024*1024; +PCHAR KdpDmesgBuffer = NULL; //KdpDmesgBuffer is used as circular buffer +volatile ULONG KdpDmesgCurrentPosition = 0; +volatile ULONG KdpDmesgFreeBytes = 0; +volatile ULONG KdbDmesgTotalWritten = 0; +KSPIN_LOCK KdpDmesgLogSpinLock; +extern volatile BOOLEAN KdbpIsInDmesgMode; +/* FILE DEBUG LOG FUNCTIONS **************************************************/ + +/* + File debug logger uses Producer-consumer model of parallel computing: + Producer part, KdpPrintToLogFile(), writes text messages into KdpDebugBuffer, + using it as circular buffer, + and signals the KdpLoggerThreadEvent, notifying so the consumer. + Consumer part, KdpLoggerThread(), reads text from KdpDebugBuffer and + writes the buffer segment into file with KdpLogFileHandle handle. + While not writing it sleeps, waiting for signal on KdpLoggerThreadEvent. + + KdpPrintToLogFile() protects KdpDebugBuffer from simultaneous writes by use of + KdpDebugLogSpinLock. + */ + VOID NTAPI KdpLoggerThread(PVOID Context) @@ -45,8 +67,11 @@ KeWaitForSingleObject(&KdpLoggerThreadEvent, 0, KernelMode, FALSE, NULL); /* Bug */ + // Keep KdpCurrentPosition and KdpFreeBytes values in local variables + // to avoid their possible change from Producer part, KdpPrintToLogFile function end = KdpCurrentPosition; num = KdpFreeBytes; + // now securely calculate values, based on local variables beg = (end + num) % KdpBufferSize; num = KdpBufferSize - num; @@ -144,6 +169,7 @@ IO_STATUS_BLOCK Iosb; HANDLE ThreadHandle; KPRIORITY Priority; + SIZE_T MemSizeMBs; if (!KdpDebugMode.File) return; @@ -171,6 +197,8 @@ /* Display separator + ReactOS version at start of the debug log */ DPRINT1("---------------------------------------------------------------\n"); DPRINT1("ReactOS "KERNEL_VERSION_STR" (Build "KERNEL_VERSION_BUILD_STR")\n"); + MemSizeMBs = MmNumberOfPhysicalPages * PAGE_SIZE / 1024 / 1024; + DPRINT1("%u System Processor [%u MB Memory]\n", KeNumberProcessors, MemSizeMBs); } else if (BootPhase == 2) { @@ -272,6 +300,7 @@ KdpSerialInit(PKD_DISPATCH_TABLE DispatchTable, ULONG BootPhase) { + SIZE_T MemSizeMBs; if (!KdpDebugMode.Serial) return; if (BootPhase == 0) @@ -297,6 +326,8 @@ /* Display separator + ReactOS version at start of the debug log */ DPRINT1("-----------------------------------------------------\n"); DPRINT1("ReactOS "KERNEL_VERSION_STR" (Build "KERNEL_VERSION_BUILD_STR")\n"); + MemSizeMBs = MmNumberOfPhysicalPages * PAGE_SIZE / 1024 / 1024; + DPRINT1("%u System Processor [%u MB Memory]\n", KeNumberProcessors, MemSizeMBs); DPRINT1("Command Line: %s\n", KeLoaderBlock->LoadOptions); DPRINT1("ARC Paths: %s %s %s %s\n", KeLoaderBlock->ArcBootDeviceName, KeLoaderBlock->NtHalPathName, @@ -311,20 +342,90 @@ /* SCREEN FUNCTIONS **********************************************************/ +/* + Screen debug logger function KdpScreenPrint() writes text messages into + KdpDmesgBuffer, using it as circular buffer. + KdpDmesgBuffer contents could be later (re)viewed using dmesg command of kdbg. + KdpScreenPrint() protects KdpDmesgBuffer from simultaneous writes by use of + KdpDmesgLogSpinLock. + */ + VOID NTAPI KdpScreenPrint(LPSTR Message, - ULONG Length) + ULONG MsgLength) { + ULONG beg, end, num; + KIRQL OldIrql; + /* Call HAL */ HalDisplayString(Message); + + //Dmesg : store Message in buffer, to show later + if( KdbpIsInDmesgMode ) + return; // skip logging the same text again + + if (KdpDmesgBuffer == NULL) // buffer was not allocated + return; // nothing to do + + /* Acquire the printing spinlock without waiting at raised IRQL */ + while (TRUE) + { + /* Wait when the spinlock becomes available */ + while (!KeTestSpinLock(&KdpDmesgLogSpinLock)); + + /* Spinlock was free, raise IRQL */ + KeRaiseIrql(HIGH_LEVEL, &OldIrql); + + /* Try to get the spinlock */ + if (KeTryToAcquireSpinLockAtDpcLevel(&KdpDmesgLogSpinLock)) + break; + + /* Someone else got the spinlock, lower IRQL back */ + KeLowerIrql(OldIrql); + } + + // Invariant: always_true(KdpDmesgFreeBytes == KdpDmesgBufferSize); + // set num to min(KdpDmesgFreeBytes,MsgLength) + num = (MsgLength < KdpDmesgFreeBytes) ? MsgLength : KdpDmesgFreeBytes; + beg = KdpDmesgCurrentPosition; + if (num != 0) + { + end = (beg + num) % KdpDmesgBufferSize; + if (end > beg) + { + RtlCopyMemory(KdpDmesgBuffer + beg, Message, MsgLength); + } + else + { + RtlCopyMemory(KdpDmesgBuffer + beg, Message, KdpDmesgBufferSize - beg); + RtlCopyMemory(KdpDmesgBuffer, Message + (KdpDmesgBufferSize - beg), end); + } + KdpDmesgCurrentPosition = end; + + // Counting the total bytes written + KdbDmesgTotalWritten += num; + } + + /* Release spinlock */ + KiReleaseSpinLock(&KdpDmesgLogSpinLock); + + /* Lower IRQL */ + KeLowerIrql(OldIrql); + + //Optional step(?): find out a way to notify about buffer exhaustion, + // and possibly fall into kbd to use dmesg command: user will read debug messages + // before they will be wiped over by next writes. + } + VOID NTAPI KdpScreenInit(PKD_DISPATCH_TABLE DispatchTable, ULONG BootPhase) { + SIZE_T MemSizeMBs; if (!KdpDebugMode.Screen) return; if (BootPhase == 0) @@ -336,6 +437,31 @@ /* Register as a Provider */ InsertTailList(&KdProviders, &DispatchTable->KdProvidersList); } + else if (BootPhase == 1) + { + //Dmesg Start + /* Allocate a buffer for dmesg log buffer */ + KdpDmesgBuffer = ExAllocatePool(NonPagedPool, KdpDmesgBufferSize+1); //+1 for + // terminating null, see kdbp_cli.c:KdbpCmdDmesg()/2 + RtlZeroMemory(KdpDmesgBuffer, KdpDmesgBufferSize+1); + KdpDmesgFreeBytes = KdpDmesgBufferSize; + KdbDmesgTotalWritten = 0; + + /* Initialize spinlock */ + KeInitializeSpinLock(&KdpDmesgLogSpinLock); + + /* Display separator + ReactOS version at start of the debug log */ + DPRINT1("-----------------------------------------------------\n"); + DPRINT1("ReactOS "KERNEL_VERSION_STR" (Build "KERNEL_VERSION_BUILD_STR")\n"); + MemSizeMBs = MmNumberOfPhysicalPages * PAGE_SIZE / 1024 / 1024; + DPRINT1("%u System Processor [%u MB Memory]\n", KeNumberProcessors, MemSizeMBs); + DPRINT1("Command Line: %s\n", KeLoaderBlock->LoadOptions); + DPRINT1("ARC Paths: %s %s %s %s\n", KeLoaderBlock->ArcBootDeviceName, + KeLoaderBlock->NtHalPathName, + KeLoaderBlock->ArcHalDeviceName, + KeLoaderBlock->NtBootPathName); + //End of Dmesg + } else if (BootPhase == 2) { HalDisplayString("\n Screen debugging enabled\n\n"); Index: ntoskrnl/kdbg/kdb_cli.c =================================================================== --- ntoskrnl/kdbg/kdb_cli.c (revision 51206) +++ ntoskrnl/kdbg/kdb_cli.c (working copy) @@ -29,6 +29,7 @@ /* INCLUDES ******************************************************************/ #include +#include //ceilf in KdbpPager()/CountOnePageUp() #define NDEBUG #include @@ -42,6 +43,14 @@ #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" : \ @@ -56,6 +65,7 @@ ((state) == NPX_STATE_LOADED ? "Loaded" : \ ((state) == NPX_STATE_NOT_LOADED ? "Not loaded" : "Unknown")) + /* PROTOTYPES ****************************************************************/ static BOOLEAN KdbpCmdEvalExpression(ULONG Argc, PCHAR Argv[]); @@ -81,6 +91,7 @@ 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[]); /* GLOBALS *******************************************************************/ @@ -101,6 +112,18 @@ PCHAR KdbInitFileBuffer = NULL; /* Buffer where KDBinit file is loaded into during initialization */ BOOLEAN KdbpBugCheckRequested = FALSE; +//Vars for Dmesg +// defined here, used in ../kd/kdio.c: +volatile BOOLEAN KdbpIsInDmesgMode = FALSE; + +// defined in ../kd/kdio.c, declare here: +extern const ULONG KdpDmesgBufferSize; +extern PCHAR KdpDmesgBuffer; +extern volatile ULONG KdpDmesgCurrentPosition; +extern volatile ULONG KdpDmesgFreeBytes; +extern volatile ULONG KdbDmesgTotalWritten; + + static const struct { PCHAR Name; @@ -150,6 +173,8 @@ { "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 }, + { "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 } }; @@ -1887,6 +1912,55 @@ 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=%d, BufferSize=%d, CurrentPosition=%d\n", + KdbDmesgTotalWritten, KdpDmesgBufferSize, KdpDmesgCurrentPosition); + + // Pass data to the pager: + end = KdpDmesgCurrentPosition; + beg = (end + KdpDmesgFreeBytes) % KdpDmesgBufferSize; + + if (KdbDmesgTotalWritten <= KdpDmesgBufferSize ) //no roll-overs, and overwritten=lost bytes + { + //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 @@ -2102,6 +2176,7 @@ * * \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( @@ -2207,12 +2282,12 @@ 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 } } @@ -2248,6 +2323,7 @@ DbgPrint("\n"); DbgPrint("--- Press q to abort, any other key to continue ---"); + RowsPrintedByTerminal++; //added by Mna. if (KdbDebugState & KD_DEBUG_KDSERIAL) c = KdbpGetCharSerial(); @@ -2329,6 +2405,345 @@ } } +/** memchr(), 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; //result + // 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--; ) { + p1 = memrchr(p0, '\n', p-p0); + prev_p = p; + p = p1; + if( NULL==p ) { + p = prev_p; + if( NULL==p ) + p = p0; + break; + } + int linesCnt = (int) ceilf( (float)(prev_p-p-1) / 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) + { + 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.