// Kernel debugging and bugcheck support. This file owns panic reporting, process fault reporting, and diagnostic queries. #include #include #include #include #include #include #include #include #include #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; static const HAL_API* gDebugHal; #define KE_DEBUG_LOG_BUFFER_SIZE (50u * 1024u) #define KE_DEBUG_LOG_LINE_SIZE 1024u #define KE_FATAL_OUTPUT_LINE_SIZE 2048u #define KE_STACK_DUMP_SLOT_COUNT 12u #define KE_RBP_CALL_STACK_DEPTH 16u static SPINLOCK gDebugPrintLock = SPINLOCK_INIT; static char* gDebugLogBuffer; static size_t gDebugLogSize; static size_t gDebugLogWrite; static size_t gDebugLogUsed; static volatile int32_t gDebugLogFlushing; static bool gDebugAtLineStart = true; static bool gDebugClockReady; static uint64_t gDebugClockOrigin; static uint64_t gDebugLastTick; // Appends bytes to the in-memory debug log while its lock is held. 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; } // Writes one record through the HAL output router. static void KiOutputWrite(uint32_t stream, uint32_t flags, uint32_t foreground, uint32_t background, const char* text, size_t length) { if (!gDebugHal || !gDebugHal->Output.Write || !text || length == 0) return; HAL_OUTPUT_RECORD record; record.Stream = stream; record.Flags = flags; record.Foreground = foreground; record.Background = background; record.Buffer = text; record.Length = length; gDebugHal->Output.Write(&record); } // Formats one bounded output record and routes it through the HAL. static void KiOutputVPrintf(uint32_t stream, uint32_t flags, uint32_t foreground, uint32_t background, const char* format, va_list args) { if (!format) return; char line[KE_FATAL_OUTPUT_LINE_SIZE]; int count = vsnprintf_(line, sizeof(line), format, args); size_t length = 0; if (count > 0) { length = (size_t)count; if (length >= sizeof(line)) length = sizeof(line) - 1u; } KiOutputWrite(stream, flags, foreground, background, line, length); } static volatile int32_t gPanicInProgress; static volatile uintptr_t gPanicOwnerToken; static volatile int32_t gPanicStoppedCpuCount; // Permanently stops a secondary processor that entered an active panic. static _Noreturn void KiPanicSecondaryHalt(void) { NtlAtomicIncrement32(&gPanicStoppedCpuCount); KeStopProcessor(); } // Returns a stable token for the current execution context. 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; } // Returns whether a fatal report is active. static bool KiPanicIsActive(void) { return NtlAtomicLoad32(&gPanicInProgress) != 0; } // Returns whether the current processor owns the active fatal report. static bool KiPanicCurrentCpuOwnsReport(void) { uintptr_t token = KiPanicCurrentToken(); return token != 0 && gPanicOwnerToken == token; } // Claims fatal-report ownership and masks interrupts on this processor. static void KiBeginFatalReport(void) { (void)KeDisableInterrupts(); uintptr_t token = KiPanicCurrentToken(); if (NtlAtomicCompareExchange32(&gPanicInProgress, 1, 0) == 0) { gPanicOwnerToken = token; return; } if (gPanicOwnerToken != token) KiPanicSecondaryHalt(); } // Handles NMI-based panic stops and TLB shootdowns. __declspec(dllexport) bool KeHandleNmiIpi(void) { if (KiPanicIsActive()) KiPanicSecondaryHalt(); if (MmPteHandleShootdownNmi()) return true; return false; } // Initializes debug logging and initial output subscriptions. void KeInitDebug(const HAL_API* hal, uint32_t flags) { gDebugHal = hal; gDebugFlags = flags; gDebugEnabled = (flags & (KE_DEBUG_CONSOLE | KE_DEBUG_FILE_LOG)) != 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 (hal && hal->Output.SetEndpointStreams && hal->Output.BootFramebufferEndpointId != HAL_OUTPUT_ENDPOINT_INVALID) { uint32_t streams = HAL_OUTPUT_STREAM_CONSOLE | HAL_OUTPUT_STREAM_CRASH | HAL_OUTPUT_STREAM_PANIC; if (flags & KE_DEBUG_CONSOLE) streams |= HAL_OUTPUT_STREAM_DEBUG; (void)hal->Output.SetEndpointStreams(hal->Output.BootFramebufferEndpointId, streams); } } // Returns the active debug flags. uint32_t KeGetDebugFlags(void) { return gDebugFlags; } // Prefixes each logical debug line with elapsed monotonic seconds when available. static size_t KiDebugTimestamp(char* buffer, size_t capacity) { if (!gDebugHal || !gDebugHal->Timer.QueryTicks) return 0; uint64_t tick = gDebugHal->Timer.QueryTicks(); if (tick == 0) return 0; if (!gDebugClockReady) { gDebugClockOrigin = tick; gDebugLastTick = tick; gDebugClockReady = true; } if (tick < gDebugLastTick) tick = gDebugLastTick; gDebugLastTick = tick; uint64_t elapsed = tick - gDebugClockOrigin; int count = snprintf_(buffer, capacity, "[%6llu.%06llu] ", (unsigned long long)(elapsed / 1000000ull), (unsigned long long)(elapsed % 1000000ull)); return count > 0 && (size_t)count < capacity ? (size_t)count : 0; } // Writes one formatted kernel diagnostic. __declspec(dllexport) void DbgPrint(const char* fmt, ...) { if (!gDebugEnabled || !fmt) return; char line[KE_DEBUG_LOG_LINE_SIZE]; va_list args; va_start(args, fmt); int count = vsnprintf_(line, sizeof(line), fmt, args); va_end(args); size_t length = 0; if (count > 0) { length = (size_t)count; if (length >= sizeof(line)) length = sizeof(line) - 1u; } if (length == 0) return; bool panic = KiPanicIsActive(); if (panic && !KiPanicCurrentCpuOwnsReport()) KiPanicSecondaryHalt(); uint32_t stream = panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_DEBUG; uint32_t flags = panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0; uint32_t foreground = panic ? 0x00FF2222u : 0x00CCCCCCu; uint32_t background = panic ? 0x00000000u : 0x00012456u; uintptr_t lockFlags = 0; if (!panic) lockFlags = KeAcquireSpinlock(&gDebugPrintLock); size_t offset = 0; while (offset < length) { size_t end = offset; while (end < length && line[end] != '\n') end++; if (end < length) end++; if (panic || gDebugAtLineStart) { char prefix[48]; size_t prefixLength = KiDebugTimestamp(prefix, sizeof(prefix)); if (prefixLength) { if (!panic && !NtlAtomicLoad32(&gDebugLogFlushing)) KiDebugLogAppendLocked(prefix, prefixLength); KiOutputWrite(stream, flags, foreground, background, prefix, prefixLength); } } if (!panic && !NtlAtomicLoad32(&gDebugLogFlushing)) KiDebugLogAppendLocked(line + offset, end - offset); KiOutputWrite(stream, flags, foreground, background, line + offset, end - offset); if (!panic) gDebugAtLineStart = line[end - 1u] == '\n'; offset = end; } if (!panic) KeReleaseSpinlock(&gDebugPrintLock, lockFlags); } // Writes one debug-log segment and rejects successful zero-progress writes. static KSTATUS KiDebugLogWriteAll(FILE_OBJECT* file, const char* data, size_t length, size_t* totalWritten) { if (!file || (!data && length) || !totalWritten) return NTLSTATUS_INVALID_PARAMETER; while (length != 0) { size_t written = 0; KSTATUS st = IoWriteFile(file, data, length, &written); if (!KSUCCESS(st)) return st; if (written == 0) return NTLSTATUS_DEVICE_ERROR; data += written; length -= written; *totalWritten += written; } return NTLSTATUS_SUCCESS; } // Persists the in-memory debug ring and flushes it through the storage stack. KSTATUS KeFlushDebugLogToSystemRoot(const char* relativePath) { if (!gDebugLogBuffer || gDebugLogSize == 0 || !relativePath || !relativePath[0]) return NTLSTATUS_INVALID_PARAMETER; if (!KeCanWait()) return NTLSTATUS_INVALID_IRQL; if (NtlAtomicCompareExchange32(&gDebugLogFlushing, 1, 0) != 0) return NTLSTATUS_PENDING; size_t used; size_t start; uintptr_t flags = KeAcquireSpinlock(&gDebugPrintLock); used = gDebugLogUsed; start = (gDebugLogUsed == gDebugLogSize) ? gDebugLogWrite : 0; KeReleaseSpinlock(&gDebugPrintLock, flags); (void)KeDeleteSystemRootFile(relativePath); FILE_OBJECT* file = (FILE_OBJECT*)0; KSTATUS st = KeCreateSystemRootFile(relativePath, &file); if (!KSUCCESS(st)) { NtlAtomicExchange32(&gDebugLogFlushing, 0); return st; } // Shutdown logging must not depend on deferred file-cache writeback. FAT // still updates the directory entry and cluster chain through its normal // write path, while IoFlushFile pushes metadata and the device cache. file->Flags |= IO_FILE_FLAG_NO_CACHE | IO_FILE_FLAG_SKIP_ZERO_EXTEND; size_t totalWritten = 0; if (used != 0) { size_t first = gDebugLogSize - start; if (first > used) first = used; st = KiDebugLogWriteAll(file, gDebugLogBuffer + start, first, &totalWritten); if (KSUCCESS(st) && used > first) { st = KiDebugLogWriteAll(file, gDebugLogBuffer, used - first, &totalWritten); } } if (KSUCCESS(st) && totalWritten != used) st = NTLSTATUS_DEVICE_ERROR; if (KSUCCESS(st)) st = IoFlushFile(file); IoCloseFile(file); NtlAtomicExchange32(&gDebugLogFlushing, 0); return 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; KSTATUS st = KeFlushDebugLogToSystemRoot("kernel-panic.log"); if (!KSUCCESS(st)) DbgPrint("debuglog: panic flush failed st=0x%x\n", st); } // Writes one formatted panic record through panic-safe endpoints. static void KiPanicPrintf(const char* format, ...) { va_list args; va_start(args, format); KiOutputVPrintf(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, 0x00FF2222u, 0x00000000u, format, args); va_end(args); } // Stops the system after reporting a fatal kernel error. __declspec(dllexport) _Noreturn void KePanic(int32_t code, const char* fmt, ...) { KiBeginFatalReport(); // Stop every other CPU before writing the final report. Panic-safe endpoints // do not depend on scheduler locks held by processors stopped by this IPI. KeBroadcastProcessorNmi(); static const char banner[] = "\n*** KERNEL PANIC ***\n"; KiOutputWrite(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, 0x00FF2222u, 0x00000000u, banner, sizeof(banner) - 1u); KiPanicPrintf("Panic code: %d\n", code); KCPU* cpu = KeGetCurrentCpu(); THREAD* thread = KeGetCurrentThread(); if (cpu) { KiPanicPrintf("Panic CPU: %u hardware=%u", (unsigned)cpu->CpuIndex, (unsigned)cpu->HardwareId); if (thread) { KiPanicPrintf(" thread=0x%016llX tid=%llu", thread, (unsigned long long)thread->ThreadId); } KiPanicPrintf("\n"); } va_list args; va_start(args, fmt); KiOutputVPrintf(HAL_OUTPUT_STREAM_PANIC, HAL_OUTPUT_WRITE_EMERGENCY, 0x00FF2222u, 0x00000000u, fmt, args); va_end(args); KiTryFlushPanicDebugLog(); KeStopProcessor(); } // Writes user-visible console text through subscribed output endpoints. __declspec(dllexport) void KePrintConsole(const char* buf, size_t len) { KiOutputWrite(HAL_OUTPUT_STREAM_CONSOLE, 0, 0x00CCCCCCu, 0x00012456u, buf, len); } // Writes one formatted process-fault or kernel-fault diagnostic. static void KiFaultPrintf(const char* format, ...) { bool panic = KiPanicIsActive(); va_list args; va_start(args, format); KiOutputVPrintf(panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_CRASH, panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0, panic ? 0x00FF2222u : 0x00FFAA00u, 0x00000000u, format, args); va_end(args); } // Resolves the main user image or a registered kernel image that owns an address. // User-mode DLL bookkeeping belongs to the user-mode loader and is intentionally // not duplicated in kernel diagnostics. 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; } uintptr_t kernelBase = 0; size_t kernelSize = 0; const char* kernelModule = LdrFindKernelModuleByAddress(addr, &kernelBase, &kernelSize); if (kernelBase) { if (baseOut) *baseOut = kernelBase; if (sizeOut) *sizeOut = kernelSize; return kernelModule; } 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) { KiFaultPrintf("%s%016llx <%s+0x%016llX>\n", label, (unsigned long long)addr, mod, (unsigned long long)(addr - base)); } else { KiFaultPrintf("%s%016llx \n", label, (unsigned long long)addr); } } // Prints a resolved address without ending the current line. static void KiPrintAddressValue(PROCESS* proc, uintptr_t addr) { uintptr_t base = 0; size_t size = 0; const char* mod = KiResolveModuleEx(proc, addr, &base, &size); if (base) { KiFaultPrintf("%016llx <%s+0x%016llX>", (unsigned long long)addr, mod, (unsigned long long)(addr - base)); } else { KiFaultPrintf("%016llx", (unsigned long long)addr); } } // Checks whether a kernel virtual range is mapped before diagnostic reads. static bool KiKernelRangeReadable(uintptr_t addr, size_t size) { if (!addr || size == 0) return false; if (size - 1u > (size_t)(UINTPTR_MAX - addr)) return false; uintptr_t start = addr & PAGE_MASK; uintptr_t last = (addr + size - 1u) & PAGE_MASK; for (uintptr_t page = start;; page += PAGE_SIZE) { PHYSICAL_ADDRESS phys = 0; uint64_t flags = 0; if (!KSUCCESS(MmPteQueryPageFor(MmPteQueryDebug, page, &phys, &flags))) return false; if ((flags & PAGE_PRESENT) == 0) return false; if (page == last) break; if (page > UINTPTR_MAX - PAGE_SIZE) return false; } return true; } // Reads diagnostic memory from either user space or a mapped kernel range. static bool KiReadDiagnosticMemory(uintptr_t addr, void* out, size_t size) { if (!out || !addr || size == 0) return false; if (addr >= KERNEL_BASE) { if (!KiKernelRangeReadable(addr, size)) return false; memcpy(out, (const void*)addr, size); return true; } if (!KSUCCESS(MmProbeUserRead((const void*)addr, size))) return false; memcpy(out, (const void*)addr, size); return true; } // Reads one diagnostic uint64_t. static bool KiUserReadU64(uintptr_t addr, uint64_t* out) { return KiReadDiagnosticMemory(addr, out, sizeof(*out)); } // Reads one diagnostic byte. static bool KiUserReadU8(uintptr_t addr, uint8_t* out) { return KiReadDiagnosticMemory(addr, out, sizeof(*out)); } // Dumps bytes. static void KiDumpBytes(PROCESS* proc, const char* label, uintptr_t center) { if (!center) { KiFaultPrintf("%s: \n", label); return; } KiFaultPrintf("%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; KiFaultPrintf(" %016llx:", (unsigned long long)addr); for (int i = 0; i < 16; i++) { uint8_t b = 0; if (KiUserReadU8(addr + (uintptr_t)i, &b)) KiFaultPrintf(" %02x", b); else KiFaultPrintf(" ??"); } KiFaultPrintf("\n"); } } // Reads one diagnostic int32_t. static bool KiUserReadI32(uintptr_t addr, int32_t* out) { return KiReadDiagnosticMemory(addr, out, sizeof(*out)); } // 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 { KiFaultPrintf("No RIP-relative indirect call pattern found before return address.\n"); return; } uintptr_t slot = ret + (intptr_t)disp; uint64_t target = 0; KiFaultPrintf("Decoded null indirect call:\n"); KiPrintAddress(proc, " insn=", insn); KiPrintAddress(proc, " slot=", slot); if (KiUserReadU64(slot, &target)) { KiPrintAddress(proc, " *slot=", (uintptr_t)target); } else { KiFaultPrintf(" *slot=\n"); } KiDumpBytes(proc, "Bytes near indirect slot", slot); } // Dumps modules. static void KiDumpModules(PROCESS* proc) { if (!proc) return; KiFaultPrintf("Images:\n"); if (proc->ImageBase && proc->ImageSize) { KiFaultPrintf(" %-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); } } #if 0 // Returns whether a range is inside the current kernel stack. static bool KiKernelStackContains(const THREAD* thread, uintptr_t addr, size_t size) { if (!thread || !thread->KernelStackBase || !thread->KernelStackTop || !addr || size == 0) return false; if (size - 1u > (size_t)(UINTPTR_MAX - addr)) return false; uintptr_t base = (uintptr_t)thread->KernelStackBase; uintptr_t top = thread->KernelStackTop; uintptr_t end = addr + size; return addr >= base && end <= top; } // Returns whether the fault frame is using the current kernel stack. static bool KiFaultFrameOnKernelStack(const THREAD* thread, const KFAULT_FRAME* f) { if (!f || !thread) return false; return KiKernelStackContains(thread, (uintptr_t)f->Rsp, sizeof(uint64_t)); } #endif // Dumps stack words near RSP. static void KiDumpStack(PROCESS* proc, uintptr_t rsp) { KiFaultPrintf("Stack @ %016llx:\n", (unsigned long long)rsp); if (!rsp) { KiFaultPrintf(" \n"); return; } for (uint32_t i = 0; i < KE_STACK_DUMP_SLOT_COUNT; i++) { uintptr_t slot = rsp + (uintptr_t)i * sizeof(uint64_t); uint64_t value = 0; KiFaultPrintf(" [%02u] %016llx : ", (unsigned)i, (unsigned long long)slot); if (KiUserReadU64(slot, &value)) { KiPrintAddressValue(proc, (uintptr_t)value); KiFaultPrintf("\n"); } else { KiFaultPrintf("\n"); } } } #if 0 // Dumps a best-effort frame-pointer call stack. static void KiDumpRbpCallStack(PROCESS* proc, THREAD* thread, const KFAULT_FRAME* f) { uintptr_t rbp = f ? (uintptr_t)f->Rbp : 0; KiFaultPrintf("RBP call stack @ %016llx:\n", (unsigned long long)rbp); if (!rbp) { KiFaultPrintf(" \n"); return; } int kernelStack = KiFaultFrameOnKernelStack(thread, f); for (uint32_t depth = 0; depth < KE_RBP_CALL_STACK_DEPTH; depth++) { if (rbp & (sizeof(uint64_t) - 1u)) { KiFaultPrintf(" stop: unaligned RBP %016llx\n", (unsigned long long)rbp); return; } if (kernelStack && !KiKernelStackContains(thread, rbp, sizeof(uint64_t) * 2u)) { KiFaultPrintf(" stop: RBP outside kernel stack %016llx\n", (unsigned long long)rbp); return; } uint64_t next = 0; uint64_t ret = 0; if (!KiUserReadU64(rbp, &next) || !KiUserReadU64(rbp + sizeof(uint64_t), &ret)) { KiFaultPrintf(" stop: frame unreadable at %016llx\n", (unsigned long long)rbp); return; } KiFaultPrintf(" #%02u rbp=%016llx ret=", (unsigned)depth, (unsigned long long)rbp); KiPrintAddressValue(proc, (uintptr_t)ret); KiFaultPrintf(" next=%016llx\n", (unsigned long long)next); if (!next) return; if ((uintptr_t)next <= rbp) { KiFaultPrintf(" stop: non-forward RBP chain %016llx -> %016llx\n", (unsigned long long)rbp, (unsigned long long)next); return; } rbp = (uintptr_t)next; } } #endif // Dumps a fault frame. static void KiDumpFaultFrame(PROCESS* proc, THREAD* thread, const KFAULT_FRAME* f) { if (!f) return; KiFaultPrintf("Regs:\n"); KiFaultPrintf(" 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); KiFaultPrintf(" 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); KiFaultPrintf(" 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); KiFaultPrintf(" 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); KiFaultPrintf(" 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)) { KiFaultPrintf("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 { KiFaultPrintf("Likely null indirect call, but [RSP] is unreadable.\n"); } } else { KiDumpBytes(proc, "Bytes near RIP", (uintptr_t)f->Rip); } KiDumpStack(proc, (uintptr_t)f->Rsp); #if 0 KiDumpRbpCallStack(proc, thread, f); #endif } static void KiPrintCrashHeader(PROCESS* proc, int vector, uintptr_t rip, uintptr_t errCode, uintptr_t cr2) { const char* faultName = KeExceptionVectorName((uint32_t)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"; KiOutputWrite(HAL_OUTPUT_STREAM_CRASH, 0, 0x00FFAA00u, 0x00000000u, banner, sizeof(banner) - 1u); if (vector == 14) KiFaultPrintf("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 KiFaultPrintf("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) { 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) { KeEnableInterrupts(); KeYield(); KeWaitForInterrupt(); } } _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, KeExceptionProcessExitStatus((uint32_t)vector)); } // Panics after printing a kernel-mode CPU exception report. static _Noreturn void KiPanicKernelFault(PROCESS* proc, THREAD* thread, const KFAULT_FRAME* frame) { KiBeginFatalReport(); int vector = (int)frame->Vector; const char* name = KeExceptionVectorName((uint32_t)vector); DbgPrint("FAULT: kernel exception vector=%d name=%s rip=%016llx rsp=%016llx err=%04llx cr2=%016llx thread=0x%016llX 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, thread, frame); if (vector == 14) { MmDebugDescribeAddress((uintptr_t)frame->Cr2); if (frame->Rip) MmDebugDescribeAddress((uintptr_t)frame->Rip); 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 (KeExceptionVectorHasErrorCode((uint32_t)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]); } } // Terminates the current process after printing an unhandled user exception report. static _Noreturn void KiTerminateUserFault(PROCESS* proc, THREAD* thread, const KFAULT_FRAME* frame) { KiPrintCrashHeader(proc, (int)frame->Vector, (uintptr_t)frame->Rip, (uintptr_t)frame->ErrorCode, (uintptr_t)frame->Cr2); KiDumpFaultFrame(proc, thread, frame); KiDumpModules(proc); KiFinishFaultedProcess(proc, thread, KeExceptionProcessExitStatus((uint32_t)frame->Vector)); } // Dispatches a CPU fault frame and returns only when a page fault was repaired. __declspec(dllexport) KSTATUS KeFaultDispatchFrame(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 (!KeFaultFrameIsValid(frame)) { KePanic(KE_PANIC_CODE_FAULT, "PANIC: bad KFAULT_FRAME %016llx\n", (unsigned long long)(uintptr_t)frame); } KSTATUS repaired = KeExceptionResolveRecoverableFault(frame); if (KSUCCESS(repaired)) return NTLSTATUS_SUCCESS; if (!KeFaultFrameFromUser(frame)) KiPanicKernelFault(proc, thread, frame); DbgPrint("USERFAULT: pid=%u tid=%u vector=%llu rip=0x%016llX rsp=0x%016llX cr2=0x%016llX error=0x%016llX rcx=0x%016llX rbx=0x%016llX rax=0x%016llX\n", proc ? proc->ProcessId : 0, thread ? thread->ThreadId : 0, frame->Vector, (uint64_t)frame->Rip, (uint64_t)frame->Rsp, (uint64_t)frame->Cr2, (uint64_t)frame->ErrorCode, (uint64_t)frame->Rcx, (uint64_t)frame->Rbx, (uint64_t)frame->Rax); KSTATUS delivered = KeDeliverUserException(frame); if (KSUCCESS(delivered)) return NTLSTATUS_SUCCESS; if (delivered != NTLSTATUS_NOT_FOUND) DbgPrint("USER/EXC: delivery failed vector=%llu rip=%016llx rsp=%016llx status=%u\n", frame->Vector, (unsigned long long)frame->Rip, (unsigned long long)frame->Rsp, delivered); KiTerminateUserFault(proc, thread, frame); } // KeFaultDispatch - single C entry point for all three IDT fault stubs. // The asm stubs collect the CPU exception frame values and call here. // KeFaultDispatchFrame owns the mode split and default unhandled policy. __declspec(dllexport) _Noreturn void KeFaultDispatch(uint32_t vector, uintptr_t rip, uintptr_t errCode, uintptr_t cr2, uintptr_t cs) { KFAULT_FRAME frame; memset(&frame, 0, sizeof(frame)); frame.Magic = KFAULT_FRAME_MAGIC_VALUE; frame.Vector = (uint64_t)vector; frame.ErrorCode = errCode; frame.Cr2 = cr2; frame.Rip = rip; frame.Cs = cs; (void)KeFaultDispatchFrame(&frame); KeStopProcessor(); }