// Kernel debugging and bugcheck support. This file owns panic reporting, process fault reporting, and diagnostic queries. #include #include #include #include #include #include #include #pragma intrinsic(_disable) #pragma intrinsic(_enable) #include #include #include #include #include #include #include #include #include #include #include int _fltused; // MSVC floating-point helper anchor int gDebugEnabled = 0; static uint32_t gDebugFlags = 0; void (*PrintChar)(char, uint32_t, uint32_t); #define KE_DEBUG_LOG_BUFFER_SIZE (50u * 1024u) #define KE_DEBUG_SCREEN_BUFFER_SIZE (16u * 1024u) #define KE_DEBUG_LOG_LINE_SIZE 1024u #define KE_DEBUG_SCREEN_DRAIN_SIZE 512u static SPINLOCK gDebugPrintLock = SPINLOCK_INIT; static char* gDebugLogBuffer; static size_t gDebugLogSize; static size_t gDebugLogWrite; static size_t gDebugLogUsed; static char* gDebugScreenBuffer; static size_t gDebugScreenSize; static size_t gDebugScreenRead; static size_t gDebugScreenWrite; static size_t gDebugScreenUsed; static volatile long gDebugLogFlushing; static volatile long gDebugConsoleOwnsSerial; static volatile long gDebugConsoleWorkReady; static KWORK_ITEM gDebugConsoleWorkItem; // Implements the debug Log Append Locked operation. static void KiDebugLogAppendLocked(const char* text, size_t len) { if (!gDebugLogBuffer || gDebugLogSize == 0 || !text || len == 0) return; if (len >= gDebugLogSize) { text += len - (gDebugLogSize - 1u); len = gDebugLogSize - 1u; } size_t first = gDebugLogSize - gDebugLogWrite; if (first > len) first = len; memcpy(gDebugLogBuffer + gDebugLogWrite, text, first); if (len > first) memcpy(gDebugLogBuffer, text + first, len - first); gDebugLogWrite = (gDebugLogWrite + len) % gDebugLogSize; if (gDebugLogUsed + len >= gDebugLogSize) gDebugLogUsed = gDebugLogSize; else gDebugLogUsed += len; } // Implements the debug Screen Append Locked operation. static void KiDebugScreenAppendLocked(const char* text, size_t len) { if (!gDebugScreenBuffer || gDebugScreenSize == 0 || !text || len == 0) return; if (len >= gDebugScreenSize) { text += len - (gDebugScreenSize - 1u); len = gDebugScreenSize - 1u; } while (gDebugScreenUsed + len > gDebugScreenSize) { gDebugScreenRead = (gDebugScreenRead + 1u) % gDebugScreenSize; gDebugScreenUsed--; } size_t first = gDebugScreenSize - gDebugScreenWrite; if (first > len) first = len; memcpy(gDebugScreenBuffer + gDebugScreenWrite, text, first); if (len > first) memcpy(gDebugScreenBuffer, text + first, len - first); gDebugScreenWrite = (gDebugScreenWrite + len) % gDebugScreenSize; gDebugScreenUsed += len; } // Implements the debug Screen Drain Locked operation. static size_t KiDebugScreenDrainLocked(char* out, size_t max) { if (!out || max == 0 || !gDebugScreenBuffer || gDebugScreenSize == 0 || gDebugScreenUsed == 0) return 0; size_t len = gDebugScreenUsed; if (len > max) len = max; size_t first = gDebugScreenSize - gDebugScreenRead; if (first > len) first = len; memcpy(out, gDebugScreenBuffer + gDebugScreenRead, first); if (len > first) memcpy(out + first, gDebugScreenBuffer, len - first); gDebugScreenRead = (gDebugScreenRead + len) % gDebugScreenSize; gDebugScreenUsed -= len; return len; } // Ensures the asynchronous screen ring exists before console output starts using it. static bool KiDebugEnsureScreenBuffer(void) { if (gDebugScreenBuffer && gDebugScreenSize != 0) return true; char* buffer = (char*)MmAllocatePoolTag(KE_DEBUG_SCREEN_BUFFER_SIZE, MM_POOL_TAG('d', 's', 'c', 'r')); if (!buffer) return false; memset(buffer, 0, KE_DEBUG_SCREEN_BUFFER_SIZE); bool installed = false; uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); if (!gDebugScreenBuffer) { gDebugScreenBuffer = buffer; gDebugScreenSize = KE_DEBUG_SCREEN_BUFFER_SIZE; gDebugScreenRead = 0; gDebugScreenWrite = 0; gDebugScreenUsed = 0; installed = true; } KeReleaseSpinlock(&gDebugPrintLock, flags); if (!installed) MmFreePool(buffer); return true; } // Implements the debug Serial Write Char operation. static void KiDebugSerialWriteChar(char c) { while ((__inbyte(0x3FD) & 0x20) == 0) { } if (c == '\n') { __outbyte(0x3F8, '\r'); while ((__inbyte(0x3FD) & 0x20) == 0) { } } __outbyte(0x3F8, (uint8_t)c); } // Implements the debug Serial Write Locked operation. static void KiDebugSerialWriteLocked(const char* text, size_t len) { if (!text) return; for (size_t i = 0; i < len; i++) KiDebugSerialWriteChar(text[i]); } // Implements the debug Console Write operation. static void KiDebugConsoleWrite(const char* text, size_t len) { if (!PrintChar || !text) return; for (size_t i = 0; i < len; i++) PrintChar(text[i], 0xCCCCCC, 0x012456); } // Implements the debug Console Work operation. static void KiDebugConsoleWork(void* context) { (void)context; char chunk[KE_DEBUG_SCREEN_DRAIN_SIZE]; for (;;) { uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); size_t len = KiDebugScreenDrainLocked(chunk, sizeof(chunk)); KeReleaseSpinlock(&gDebugPrintLock, flags); if (len == 0) break; KiDebugConsoleWrite(chunk, len); } } // Queues the debug Console Work operation if the passive work queue is ready. static void KiDebugQueueConsoleWork(void) { if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleWorkReady, 1, 1) && KeIsWorkQueueStarted()) (void)KeQueueWorkItem(&gDebugConsoleWorkItem); } static volatile long gPanicInProgress = 0; static volatile uintptr_t gPanicOwnerToken = 0; static volatile long gPanicStoppedCpuCount = 0; // Implements the panic Secondary Halt operation. static _Noreturn void KiPanicSecondaryHalt(void) { _disable(); _InterlockedIncrement(&gPanicStoppedCpuCount); for (;;) __halt(); } // Implements the panic Current Token operation. static uintptr_t KiPanicCurrentToken(void) { THREAD* thread = KeGetCurrentThread(); if (thread) return (uintptr_t)thread; KCPU* cpu = KeGetCurrentCpu(); if (cpu) return (uintptr_t)cpu; return 1u; } // Implements the panic Is Active operation. static int KiPanicIsActive(void) { return _InterlockedCompareExchange((volatile long*)&gPanicInProgress, 0, 0) != 0; } // Implements the panic Current Cpu Owns Report operation. static int KiPanicCurrentCpuOwnsReport(void) { uintptr_t token = KiPanicCurrentToken(); return token != 0 && gPanicOwnerToken == token; } // Implements the begin Fatal Report operation. static void KiBeginFatalReport(void) { _disable(); uintptr_t token = KiPanicCurrentToken(); if (_InterlockedCompareExchange((volatile long*)&gPanicInProgress, 1, 0) == 0) { gPanicOwnerToken = token; return; } if (gPanicOwnerToken != token) KiPanicSecondaryHalt(); } // Returning NMI handler used for SMP control IPIs. Panic NMIs stop the // target CPU permanently; TLB shootdown NMIs flush and return. A genuinely // unexpected NMI is not handled here and will fall through to the normal fault // path in the assembly stub. __declspec(dllexport) int KeHandleNmiIpi(void) { if (KiPanicIsActive()) KiPanicSecondaryHalt(); if (MmPteHandleShootdownNmi()) return 1; return 0; } // Implements the kernel Put Char operation. void KiKernelPutChar(char c) { if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) KiDebugSerialWriteChar(c); if (PrintChar) PrintChar(c, 0xCCCCCC, 0x012456); } // Initializes debug. void KeInitDebug(void (*PrintPtr)(char, uint32_t, uint32_t), uint32_t flags) { gDebugFlags = flags; gDebugEnabled = (flags & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) != 0; PrintChar = PrintPtr; printf_set_putchar(KiKernelPutChar); if (!_InterlockedCompareExchange((volatile long*)&gDebugConsoleWorkReady, 1, 0)) KeInitializeWorkItem(&gDebugConsoleWorkItem, KiDebugConsoleWork, (void*)0); if ((flags & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) && !gDebugLogBuffer) { gDebugLogBuffer = (char*)MmAllocatePoolTag(KE_DEBUG_LOG_BUFFER_SIZE, MM_POOL_TAG('d', 'l', 'o', 'g')); if (gDebugLogBuffer) { gDebugLogSize = KE_DEBUG_LOG_BUFFER_SIZE; gDebugLogWrite = 0; gDebugLogUsed = 0; memset(gDebugLogBuffer, 0, gDebugLogSize); } } if (flags & KE_DEBUG_CONSOLE) (void)KiDebugEnsureScreenBuffer(); } // Returns debug Flags. uint32_t KeGetDebugFlags(void) { return gDebugFlags; } // Updates debug Console Writer. void KeSetDebugConsoleWriter(void (*PrintPtr)(char, uint32_t, uint32_t)) { if (!PrintPtr) return; PrintChar = PrintPtr; printf_set_putchar(KiKernelPutChar); // After the display manager owns debug drawing, DbgPrint and user console // writes mirror to COM1 themselves. The display writer is screen-only so // asynchronous screen flushing does not duplicate serial bytes. (void)KiDebugEnsureScreenBuffer(); _InterlockedExchange((volatile long*)&gDebugConsoleOwnsSerial, 1); KiDebugQueueConsoleWork(); } // Implements the dbg Print operation. __declspec(dllexport) void DbgPrint(const char* fmt, ...) { if (!gDebugEnabled || !fmt) return; char line[KE_DEBUG_LOG_LINE_SIZE]; va_list va; va_start(va, fmt); int n = vsnprintf_(line, sizeof(line), fmt, va); va_end(va); size_t len = 0; if (n > 0) { len = (size_t)n; if (len >= sizeof(line)) len = sizeof(line) - 1u; } if (KiPanicIsActive()) { if (!KiPanicCurrentCpuOwnsReport()) KiPanicSecondaryHalt(); if (gDebugFlags & KE_DEBUG_CONSOLE) { if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) KiDebugSerialWriteLocked(line, len); KiDebugConsoleWrite(line, len); } return; } bool queueConsole = false; bool writeConsoleSync = false; uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) KiDebugSerialWriteLocked(line, len); if (!_InterlockedCompareExchange((volatile long*)&gDebugLogFlushing, 0, 0)) KiDebugLogAppendLocked(line, len); if (gDebugFlags & KE_DEBUG_CONSOLE) { if (KeIsWorkQueueStarted() && gDebugScreenBuffer) { KiDebugScreenAppendLocked(line, len); queueConsole = true; } else { writeConsoleSync = true; } } KeReleaseSpinlock(&gDebugPrintLock, flags); if (queueConsole) KiDebugQueueConsoleWork(); else if (writeConsoleSync) KiDebugConsoleWrite(line, len); } // Flushes debug Log To File. __declspec(dllexport) KSTATUS KeFlushDebugLogToFile(SYS_STRING_VIEW path) { if (!gDebugLogBuffer || gDebugLogSize == 0 || !SysSvIsValid(path) || path.Length == 0) return STATUS_INVALID_PARAMETER; if (!KeCanWait()) return STATUS_INVALID_IRQL; if (_InterlockedCompareExchange((volatile long*)&gDebugLogFlushing, 1, 0) != 0) return STATUS_PENDING; size_t used; size_t start; uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); used = gDebugLogUsed; start = (gDebugLogUsed == gDebugLogSize) ? gDebugLogWrite : 0; KeReleaseSpinlock(&gDebugPrintLock, flags); if (used == 0) { _InterlockedExchange((volatile long*)&gDebugLogFlushing, 0); return STATUS_SUCCESS; } (void)IoDeleteFile(path); FILE_OBJECT* file = (FILE_OBJECT*)0; KSTATUS st = IoCreateFile(path, &file); if (!KSUCCESS(st)) { _InterlockedExchange((volatile long*)&gDebugLogFlushing, 0); return st; } size_t written = 0; size_t totalWritten = 0; size_t first = gDebugLogSize - start; if (first > used) first = used; st = IoWriteFile(file, gDebugLogBuffer + start, first, &written); totalWritten += written; if (KSUCCESS(st) && used > first) { written = 0; st = IoWriteFile(file, gDebugLogBuffer, used - first, &written); totalWritten += written; } IoCloseFile(file); _InterlockedExchange((volatile long*)&gDebugLogFlushing, 0); return (KSUCCESS(st) && totalWritten == used) ? STATUS_SUCCESS : st; } // Attempts to persist the debug log during a panic when the filesystem is usable. static void KiTryFlushPanicDebugLog(void) { if (!(gDebugFlags & KE_DEBUG_FILE_LOG)) return; if (!KeCanWait() || !KeSystemRootReady()) return; char path[1024]; if (!KeBuildSystemRootPath("kernel-panic.log", path, sizeof(path))) return; KSTATUS st = KeFlushDebugLogToFile(SysSvFromCstr(path)); if (!KSUCCESS(st)) DbgPrint("debuglog: panic flush to %s failed st=0x%x\n", path, st); } // KePanic. __declspec(dllexport) _Noreturn void KePanic(int code, const char* fmt, ...) { KiBeginFatalReport(); // Stop every other CPU for the final panic banner. Kernel fault dispatch // claims panic ownership before its detailed dump, so this does not steal // ownership from the CPU that found the fatal error. HalApicSendPanicIpi(); if (PrintChar) { // Print a bright-red banner so the framebuffer is unmistakable. static const char banner[] = "\n*** KERNEL PANIC ***\n"; const char* p; KCPU* cpu = KeGetCurrentCpu(); THREAD* thread = KeGetCurrentThread(); for (p = banner; *p; p++) PrintChar(*p, 0xFF2222, 0x000000); printf("Panic code: %d\n", code); if (cpu) { printf("Panic CPU: %u LAPIC=%u", (unsigned)cpu->CpuIndex, (unsigned)cpu->LapicId); if (thread) printf(" thread=0x%016llX tid=%llu", thread, (unsigned long long)thread->ThreadId); printf("\n"); } va_list va; va_start(va, fmt); vprintf(fmt, va); va_end(va); } KiTryFlushPanicDebugLog(); for (;;) __halt(); } // KePrintConsole - write user-visible console text using the default console palette. // User text mirrors to COM1 immediately, then queues expensive screen drawing // through the passive debug-console worker when that path is available. __declspec(dllexport) void KePrintConsole(const char* buf, size_t len) { if (!buf || len == 0) return; bool queueConsole = false; bool writeConsoleSync = false; bool canQueueScreen = PrintChar && KeIsWorkQueueStarted() && KiDebugEnsureScreenBuffer(); uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) KiDebugSerialWriteLocked(buf, len); if (PrintChar) { if (!KiPanicIsActive() && canQueueScreen) { KiDebugScreenAppendLocked(buf, len); queueConsole = true; } else { writeConsoleSync = true; } } KeReleaseSpinlock(&gDebugPrintLock, flags); if (queueConsole) KiDebugQueueConsoleWork(); else if (writeConsoleSync) KiDebugConsoleWrite(buf, len); } // KeBugCheckProcess - user-mode fault handler // Resolves the faulting RIP to a module name, prints a yellow crash banner, // kills the offending process, then exits the current thread so the scheduler // can continue running everything else. // Return the short exception name for a CPU exception vector. // Keep this table complete for vectors 0..31 so faults never collapse into // a vague "general protection" bucket just because the IDT entry existed // first or the crash printer lacked a name. static const char* KiFaultVectorName(int vector) { switch (vector) { case 0: return "#DE Divide Error"; case 1: return "#DB Debug"; case 2: return "NMI Interrupt"; case 3: return "#BP Breakpoint"; case 4: return "#OF Overflow"; case 5: return "#BR Bound Range Exceeded"; case 6: return "#UD Invalid Opcode"; case 7: return "#NM Device Not Available"; case 8: return "#DF Double Fault"; case 9: return "Coprocessor Segment Overrun"; case 10: return "#TS Invalid TSS"; case 11: return "#NP Segment Not Present"; case 12: return "#SS Stack-Segment Fault"; case 13: return "#GP General Protection"; case 14: return "#PF Page Fault"; case 15: return "Reserved Exception 15"; case 16: return "#MF x87 Floating-Point Exception"; case 17: return "#AC Alignment Check"; case 18: return "#MC Machine Check"; case 19: return "#XM SIMD Floating-Point Exception"; case 20: return "#VE Virtualization Exception"; case 21: return "#CP Control Protection Exception"; case 22: return "Reserved Exception 22"; case 23: return "Reserved Exception 23"; case 24: return "Reserved Exception 24"; case 25: return "Reserved Exception 25"; case 26: return "Reserved Exception 26"; case 27: return "Reserved Exception 27"; case 28: return "#HV Hypervisor Injection Exception"; case 29: return "#VC VMM Communication Exception"; case 30: return "#SX Security Exception"; case 31: return "Reserved Exception 31"; default: return "Unknown Exception"; } } // Implements the fault Has Error Code operation. static int KiFaultHasErrorCode(int vector) { switch (vector) { case 8: // #DF case 10: // #TS case 11: // #NP case 12: // #SS case 13: // #GP case 14: // #PF case 17: // #AC case 21: // #CP case 29: // #VC case 30: // #SX return 1; default: return 0; } } // Walk the process module list to find which image owns an address. // Returns the module name string, or "unknown" if the address is outside all // known image ranges. Base/size are optional and are useful for offset dumps. static const char* KiResolveModuleEx(PROCESS* proc, uintptr_t addr, uintptr_t* baseOut, size_t* sizeOut) { if (baseOut) *baseOut = 0; if (sizeOut) *sizeOut = 0; if (!proc) return "unknown"; if (proc->ImageBase && proc->ImageSize && addr >= proc->ImageBase && addr < proc->ImageBase + proc->ImageSize) { if (baseOut) *baseOut = proc->ImageBase; if (sizeOut) *sizeOut = proc->ImageSize; return proc->Name; } for (uint32_t i = 0; i < proc->ModuleCount; i++) { uintptr_t base = proc->Modules[i].Base; size_t size = proc->Modules[i].Size; if (base && size && addr >= base && addr < base + size) { if (baseOut) *baseOut = base; if (sizeOut) *sizeOut = size; return proc->Modules[i].Name; } } return "unknown"; } // Implements the resolve Module operation. static const char* KiResolveModule(PROCESS* proc, uintptr_t addr) { return KiResolveModuleEx(proc, addr, (uintptr_t*)0, (size_t*)0); } // Prints address. static void KiPrintAddress(PROCESS* proc, const char* label, uintptr_t addr) { uintptr_t base = 0; size_t size = 0; const char* mod = KiResolveModuleEx(proc, addr, &base, &size); if (base) { printf("%s%016llx <%s+0x%016llX>\n", label, (unsigned long long)addr, mod, (unsigned long long)(addr - base)); } else { printf("%s%016llx \n", label, (unsigned long long)addr); } } // Implements the user Read U64 operation. static int KiUserReadU64(uintptr_t addr, uint64_t* out) { if (!out) return 0; if (!addr) return 0; if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(uint64_t)))) return 0; *out = *(const uint64_t*)addr; return 1; } // Implements the user Read U8 operation. static int KiUserReadU8(uintptr_t addr, uint8_t* out) { if (!out) return 0; if (!addr) return 0; if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(uint8_t)))) return 0; *out = *(const uint8_t*)addr; return 1; } // Dumps bytes. static void KiDumpBytes(PROCESS* proc, const char* label, uintptr_t center) { if (!center) { printf("%s: \n", label); return; } printf("%s around ", label); KiPrintAddress(proc, "", center); uintptr_t start = center >= 16 ? center - 16 : 0; for (int row = 0; row < 2; row++) { uintptr_t addr = start + (uintptr_t)row * 16u; printf(" %016llx:", (unsigned long long)addr); for (int i = 0; i < 16; i++) { uint8_t b = 0; if (KiUserReadU8(addr + (uintptr_t)i, &b)) printf(" %02x", b); else printf(" ??"); } printf("\n"); } } // Implements the user Read I32 operation. static int KiUserReadI32(uintptr_t addr, int32_t* out) { if (!out) return 0; if (!addr) return 0; if (!KSUCCESS(MmProbeUserRead((const void*)addr, sizeof(int32_t)))) return 0; *out = *(const int32_t*)addr; return 1; } // Dumps indirect Call Slot. static void KiDumpIndirectCallSlot(PROCESS* proc, uintptr_t ret) { if (!ret) return; // Most x64 import/function-pointer calls are one of: // FF 15 disp32 call qword ptr [rip+disp32] // 48 FF 15 disp32 rex.w call qword ptr [rip+disp32] // The saved return address points immediately after the instruction. uintptr_t insn = 0; int32_t disp = 0; uint8_t b0 = 0, b1 = 0, b2 = 0; if (KiUserReadU8(ret - 6, &b0) && KiUserReadU8(ret - 5, &b1) && b0 == 0xff && b1 == 0x15 && // Implements the user Read I32 operation. KiUserReadI32(ret - 4, &disp)) { insn = ret - 6; } else if (KiUserReadU8(ret - 7, &b0) && KiUserReadU8(ret - 6, &b1) && KiUserReadU8(ret - 5, &b2) && b0 == 0x48 && b1 == 0xff && b2 == 0x15 && // Implements the user Read I32 operation. KiUserReadI32(ret - 4, &disp)) { insn = ret - 7; } else { printf("No RIP-relative indirect call pattern found before return address.\n"); return; } uintptr_t slot = ret + (intptr_t)disp; uint64_t target = 0; printf("Decoded null indirect call:\n"); KiPrintAddress(proc, " insn=", insn); KiPrintAddress(proc, " slot=", slot); if (KiUserReadU64(slot, &target)) { KiPrintAddress(proc, " *slot=", (uintptr_t)target); } else { printf(" *slot=\n"); } KiDumpBytes(proc, "Bytes near indirect slot", slot); } // Dumps modules. static void KiDumpModules(PROCESS* proc) { if (!proc) return; printf("Images:\n"); if (proc->ImageBase && proc->ImageSize) { printf(" %-31s %016llx-%016llx size=%llx\n", proc->Name, (unsigned long long)proc->ImageBase, (unsigned long long)(proc->ImageBase + proc->ImageSize), (unsigned long long)proc->ImageSize); } for (uint32_t i = 0; i < proc->ModuleCount; i++) { uintptr_t base = proc->Modules[i].Base; size_t size = proc->Modules[i].Size; printf(" %-31s %016llx-%016llx size=%llx\n", proc->Modules[i].Name, (unsigned long long)base, (unsigned long long)(base + size), (unsigned long long)size); } } // Dumps user Stack. static void KiDumpUserStack(PROCESS* proc, uintptr_t rsp) { printf("User stack @ %016llx:\n", (unsigned long long)rsp); if (!rsp) { printf(" \n"); return; } for (int i = 0; i < 12; i++) { uintptr_t slot = rsp + (uintptr_t)i * 8u; uint64_t value = 0; if (KiUserReadU64(slot, &value)) { uintptr_t base = 0; size_t size = 0; const char* mod = KiResolveModuleEx(proc, (uintptr_t)value, &base, &size); if (base) { printf(" [%02d] %016llx : %016llx <%s+0x%016llX>\n", i, (unsigned long long)slot, (unsigned long long)value, mod, (unsigned long long)((uintptr_t)value - base)); } else { printf(" [%02d] %016llx : %016llx\n", i, (unsigned long long)slot, (unsigned long long)value); } } else { printf(" [%02d] %016llx : \n", i, (unsigned long long)slot); } } } // Dumps fault Frame. static void KiDumpFaultFrame(PROCESS* proc, const KFAULT_FRAME* f) { if (!f) return; printf("Regs:\n"); printf(" RAX=%016llx RBX=%016llx RCX=%016llx RDX=%016llx\n", (unsigned long long)f->Rax, (unsigned long long)f->Rbx, (unsigned long long)f->Rcx, (unsigned long long)f->Rdx); printf(" RSI=%016llx RDI=%016llx RBP=%016llx RSP=%016llx\n", (unsigned long long)f->Rsi, (unsigned long long)f->Rdi, (unsigned long long)f->Rbp, (unsigned long long)f->Rsp); printf(" R8 =%016llx R9 =%016llx R10=%016llx R11=%016llx\n", (unsigned long long)f->R8, (unsigned long long)f->R9, (unsigned long long)f->R10, (unsigned long long)f->R11); printf(" R12=%016llx R13=%016llx R14=%016llx R15=%016llx\n", (unsigned long long)f->R12, (unsigned long long)f->R13, (unsigned long long)f->R14, (unsigned long long)f->R15); printf(" CS=%04llx SS=%04llx RFLAGS=%016llx\n", (unsigned long long)f->Cs, (unsigned long long)f->Ss, (unsigned long long)f->Rflags); if (f->Rip == 0) { uint64_t ret = 0; if (KiUserReadU64((uintptr_t)f->Rsp, &ret)) { printf("Likely null indirect call. Return address from [RSP]:\n"); KiPrintAddress(proc, " ret=", (uintptr_t)ret); KiDumpBytes(proc, "Bytes near return address", (uintptr_t)ret); KiDumpIndirectCallSlot(proc, (uintptr_t)ret); } else { printf("Likely null indirect call, but [RSP] is unreadable.\n"); } } else { KiDumpBytes(proc, "Bytes near RIP", (uintptr_t)f->Rip); } KiDumpUserStack(proc, (uintptr_t)f->Rsp); } // Prints crash Char. static void KiPrintCrashChar(char c) { if (_InterlockedCompareExchange((volatile long*)&gDebugConsoleOwnsSerial, 1, 1)) KiDebugSerialWriteChar(c); if (PrintChar) PrintChar(c, 0xFFAA00, 0x000000); } static KSTATUS KiFaultExitStatus(int vector) { switch (vector) { case 0: return STATUS_PROCESS_DIVIDE_BY_ZERO; case 3: return STATUS_PROCESS_BREAKPOINT; case 6: return STATUS_PROCESS_ILLEGAL_OPCODE; case 13: return STATUS_PROCESS_GENERAL_PROTECTION; case 14: return STATUS_PROCESS_PAGE_FAULT; case 17: return STATUS_PROCESS_ALIGNMENT_FAULT; default: return STATUS_PROCESS_UNKNOWN_FAULT; } } static void KiPrintCrashHeader(PROCESS* proc, int vector, uintptr_t rip, uintptr_t errCode, uintptr_t cr2) { const char* faultName = KiFaultVectorName(vector); const char* procName = proc ? proc->Name : "unknown"; uint32_t pid = proc ? proc->ProcessId : 0; const char* modName = proc ? KiResolveModule(proc, rip) : "unknown"; static const char banner[] = "\n*** APPLICATION CRASH ***\n"; for (const char* p = banner; *p; p++) KiPrintCrashChar(*p); printf_set_putchar(KiPrintCrashChar); if (vector == 14) printf("Process: %s (PID %u) | Fault: %s\n" "Module: %s | RIP: %016llx | Code: %04llx | CR2: %016llx\n", procName, pid, faultName, modName, (unsigned long long)rip, (unsigned long long)errCode, (unsigned long long)cr2); else printf("Process: %s (PID %u) | Fault: %s\n" "Module: %s | RIP: %016llx | Code: %04llx\n", procName, pid, faultName, modName, (unsigned long long)rip, (unsigned long long)errCode); } // Implements the finish Faulted Process operation. static _Noreturn void KiFinishFaultedProcess(PROCESS* proc, THREAD* thread, KSTATUS exitStatus) { printf_set_putchar(KiKernelPutChar); int exitCode = (int)exitStatus; if (proc) (void)KeTerminateProcess(proc, exitCode); if (thread) KeExitThread(thread, exitCode); // KeExitThread normally switches away and never resumes this context. If // a fault path re-enters here during scheduler bring-up or with no runnable // peer yet, do not fall back to a local CLI/HLT dead stop: keep offering the // dead thread to the scheduler with interrupts enabled so waiters such as // the foreground shell can observe the process exit event. while (true) { _enable(); KeYield(); __halt(); } } _Noreturn void KeBugCheckProcess(int vector, uintptr_t rip, uintptr_t errCode, uintptr_t cr2) { THREAD* thread = KeGetCurrentThread(); PROCESS* proc = thread ? thread->Owner : (PROCESS*)0; KiPrintCrashHeader(proc, vector, rip, errCode, cr2); KiDumpModules(proc); KiFinishFaultedProcess(proc, thread, KiFaultExitStatus(vector)); } // Dispatches a CPU fault frame and returns only when a user #PF was repaired. __declspec(dllexport) KSTATUS KeFaultDispatchFrame(const KFAULT_FRAME* frame) { // Panic NMIs are broadcast to stop all other CPUs. Do not try to decode // the interrupted context on a secondary CPU once another CPU owns panic. if (KiPanicIsActive()) KiPanicSecondaryHalt(); THREAD* thread = KeGetCurrentThread(); PROCESS* proc = thread ? thread->Owner : (PROCESS*)0; if (!frame || frame->Magic != 0x3146544C5541464BULL) { KePanic(KE_PANIC_CODE_FAULT, "PANIC: bad KFAULT_FRAME %016llx\n", (unsigned long long)(uintptr_t)frame); } if (frame->Vector == 14) { KSTATUS handled = MmVadResolvePageFaultByAddress((uintptr_t)frame->Cr2, frame->ErrorCode); if (KSUCCESS(handled)) return STATUS_SUCCESS; } if ((frame->Cs & 3) == 0) { KiBeginFatalReport(); int vector = (int)frame->Vector; const char* name = KiFaultVectorName(vector); DbgPrint("FAULT: kernel exception vector=%d name=%s rip=%016llx rsp=%016llx err=%04llx cr2=%016llx thread=%p pid=%u proc=%s\n", vector, name, (unsigned long long)frame->Rip, (unsigned long long)frame->Rsp, (unsigned long long)frame->ErrorCode, (unsigned long long)frame->Cr2, thread, proc ? proc->ProcessId : 0, proc ? proc->Name : ""); KiDumpFaultFrame(proc, frame); if (vector == 14) { MmDebugDescribeAddress((uintptr_t)frame->Cr2); if (frame->Rsp) MmDebugDescribeAddress((uintptr_t)frame->Rsp); MmVadDebugDumpProcess(proc); } if (vector == 14) { KePanic(KE_PANIC_CODE_FAULT, "PANIC: %s | RIP: %016llx | RSP: %016llx | Code: %04llx | CR2: %016llx\n", name, (unsigned long long)frame->Rip, (unsigned long long)frame->Rsp, (unsigned long long)frame->ErrorCode, (unsigned long long)frame->Cr2); } else if (KiFaultHasErrorCode(vector)) { const uint8_t* ip = (const uint8_t*)(uintptr_t)frame->Rip; KePanic(KE_PANIC_CODE_FAULT, "PANIC: %s | RIP: %016llx | Code: %04llx | CS=%04llx RFLAGS=%016llx RSP=%016llx | bytes=%02x %02x %02x %02x %02x %02x %02x %02x\n", name, (unsigned long long)frame->Rip, (unsigned long long)frame->ErrorCode, (unsigned long long)frame->Cs, (unsigned long long)frame->Rflags, (unsigned long long)frame->Rsp, ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); } else { const uint8_t* ip = (const uint8_t*)(uintptr_t)frame->Rip; KePanic(KE_PANIC_CODE_FAULT, "PANIC: %s | RIP: %016llx | CS=%04llx RFLAGS=%016llx RSP=%016llx | bytes=%02x %02x %02x %02x %02x %02x %02x %02x\n", name, (unsigned long long)frame->Rip, (unsigned long long)frame->Cs, (unsigned long long)frame->Rflags, (unsigned long long)frame->Rsp, ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7]); } } KiPrintCrashHeader(proc, (int)frame->Vector, (uintptr_t)frame->Rip, (uintptr_t)frame->ErrorCode, (uintptr_t)frame->Cr2); KiDumpFaultFrame(proc, frame); KiDumpModules(proc); KiFinishFaultedProcess(proc, thread, KiFaultExitStatus((int)frame->Vector)); } // KeFaultDispatch - single C entry point for all three IDT fault stubs. // The asm stubs (ke_syscall.asm) collect the CPU exception frame values and // call here. We check the CS RPL to decide: kernel fault → panic and halt, // user fault → KeBugCheckProcess (kill process, resume scheduler). __declspec(dllexport) _Noreturn void KeFaultDispatch(int vector, uintptr_t rip, uintptr_t errCode, uintptr_t cr2, uintptr_t cs) { KFAULT_FRAME frame; memset(&frame, 0, sizeof(frame)); frame.Magic = 0x3146544C5541464BULL; frame.Vector = (uint64_t)vector; frame.ErrorCode = errCode; frame.Cr2 = cr2; frame.Rip = rip; frame.Cs = cs; (void)KeFaultDispatchFrame(&frame); while (1) __halt(); }