// 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 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 static SPINLOCK gDebugPrintLock = SPINLOCK_INIT; static char* gDebugLogBuffer; static size_t gDebugLogSize; static size_t gDebugLogWrite; static size_t gDebugLogUsed; static volatile long gDebugLogFlushing; // 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 long gPanicInProgress; static volatile uintptr_t gPanicOwnerToken; static volatile long gPanicStoppedCpuCount; // Permanently stops a secondary processor that entered an active panic. static _Noreturn void KiPanicSecondaryHalt(void) { _InterlockedIncrement(&gPanicStoppedCpuCount); HalStopProcessor(); } // 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 int KiPanicIsActive(void) { return _InterlockedCompareExchange((volatile long*)&gPanicInProgress, 0, 0) != 0; } // Returns whether the current processor owns the active fatal report. static int 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)HalDisableInterrupts(); uintptr_t token = KiPanicCurrentToken(); if (_InterlockedCompareExchange((volatile long*)&gPanicInProgress, 1, 0) == 0) { gPanicOwnerToken = token; return; } if (gPanicOwnerToken != token) KiPanicSecondaryHalt(); } // Handles NMI-based panic stops and TLB shootdowns. __declspec(dllexport) int KeHandleNmiIpi(void) { if (KiPanicIsActive()) KiPanicSecondaryHalt(); if (MmPteHandleShootdownNmi()) return 1; return 0; } // 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; } // 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; } bool panic = KiPanicIsActive(); if (panic && !KiPanicCurrentCpuOwnsReport()) KiPanicSecondaryHalt(); if (!panic) { uintptr_t lockFlags = KeAcquireSpinlock(&gDebugPrintLock); if (!_InterlockedCompareExchange((volatile long*)&gDebugLogFlushing, 0, 0)) KiDebugLogAppendLocked(line, length); KeReleaseSpinlock(&gDebugPrintLock, lockFlags); } KiOutputWrite(panic ? HAL_OUTPUT_STREAM_PANIC : HAL_OUTPUT_STREAM_DEBUG, panic ? HAL_OUTPUT_WRITE_EMERGENCY : 0, panic ? 0x00FF2222u : 0x00CCCCCCu, panic ? 0x00000000u : 0x00012456u, line, length); } // 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); } // 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(int 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. HalApicSendPanicIpi(); 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 LAPIC=%u", (unsigned)cpu->CpuIndex, (unsigned)cpu->LapicId); 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(); HalStopProcessor(); } // 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); } // 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; } } 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); } } // Checks whether a kernel virtual range is mapped before diagnostic reads. static int KiKernelRangeReadable(uintptr_t addr, size_t size) { if (!addr || size == 0) return 0; if (size - 1u > (size_t)(UINTPTR_MAX - addr)) return 0; 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 0; if ((flags & PAGE_PRESENT) == 0) return 0; if (page == last) break; if (page > UINTPTR_MAX - PAGE_SIZE) return 0; } return 1; } // Reads diagnostic memory from either user space or a mapped kernel range. static int KiReadDiagnosticMemory(uintptr_t addr, void* out, size_t size) { if (!out || !addr || size == 0) return 0; if (addr >= KERNEL_BASE) { if (!KiKernelRangeReadable(addr, size)) return 0; memcpy(out, (const void*)addr, size); return 1; } if (!KSUCCESS(MmProbeUserRead((const void*)addr, size))) return 0; memcpy(out, (const void*)addr, size); return 1; } // Reads one diagnostic uint64_t. static int KiUserReadU64(uintptr_t addr, uint64_t* out) { return KiReadDiagnosticMemory(addr, out, sizeof(*out)); } // Reads one diagnostic byte. static int 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 int 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); } for (uint32_t i = 0; i < proc->ModuleCount; i++) { uintptr_t base = proc->Modules[i].Base; size_t size = proc->Modules[i].Size; KiFaultPrintf(" %-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) { KiFaultPrintf("User stack @ %016llx:\n", (unsigned long long)rsp); if (!rsp) { KiFaultPrintf(" \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) { KiFaultPrintf(" [%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 { KiFaultPrintf(" [%02d] %016llx : %016llx\n", i, (unsigned long long)slot, (unsigned long long)value); } } else { KiFaultPrintf(" [%02d] %016llx : \n", i, (unsigned long long)slot); } } } // Dumps fault Frame. static void KiDumpFaultFrame(PROCESS* proc, 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); } KiDumpUserStack(proc, (uintptr_t)f->Rsp); } 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"; 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) { HalEnableInterrupts(); KeYield(); HalWaitForInterrupt(); } } _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->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 (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); HalStopProcessor(); }