Step 37 · First contribution · C8
A boot failure, diagnosed
Apply diagnostics and log reading to a worked boot failure. Follow the evidence from the command line to a testable hypothesis, then use the same process in the debugging labs and your first change.
Before this page: Debugging & diagnostics · Reading a run · ELF to first guest instruction. View the learning route.
The scenario is real, the names are not. A title we'll call Neptune boots, stays on a black screen for a few seconds, and then the process dies. Everything below is what a diagnosis of that actually looks like.
Work through the supplied excerpts without needing a game. Record what each excerpt establishes and what still needs a reproduction or test. If you have a suitable title and build, the live exercise below is an additional application of the method.
The setup — turn the taps on
You never diagnose blind. Start with the main log, and add command/shader dumps for a bounded reproduction:
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\Neptune" ` --printf-direction File --printf-output-file _kyty.txt ` --command-buffer-dump true --shader-log-direction File
Three artifacts land on disk: _kyty.txt (every log line), the command-buffer dumps, and _Shaders/ (the shader compile trail). The --printf-direction and --shader-log-direction switches are the ones from Debugging & diagnostics; the playground tools are designed to read exactly these files. Record the visible behavior alongside the logs; neither alone establishes the cause.
Step one — read the log in both directions
Open _kyty.txt. Top-down you see the boot story: modules loading, mounts happening, relocations resolving. Then, near the bottom, the unresolved imports — the loader logs every NID it could not bind, with the owning module:
[1][00:00:01.241] Loader::Programs:: ... : unresolved symbol rPo6tV8D9bM#B#A, lib = "Neptune_Game", mod = "Neptune_Game" [1][00:00:01.244] Loader::Programs:: ... : unresolved symbol pT2fEBBpEJk#A#A, lib = "libScePad", mod = "libScePad" [1][00:00:01.310] Loader::Programs:: ... : unresolved symbol 0GnN4QCgIfs#D#A, lib = "libSceVideoOut", mod = "libSceVideoOut"
Three habits, all learned earlier:
- Bottom-up is for the crash; top-down is for the story. Unresolved imports are not errors yet — they become errors only if the game calls them. Each line is a candidate for Your first change.
- The
#B#Atail is the id encoding from the id encoder tool — module and library ids, not a readable name. - The whole log is droppable into the log triage tool, which groups these lines into "unresolved", "unimplemented", and "errors" buckets. That is the to-do list.
In Neptune's case the log ends not with unresolved imports but with the --- Guest fault context --- block. The log alone does not establish whether the preceding black screen came from a wait, missing work, or incorrect rendering.
Step two — the crash block, through the reader
Copy the crash block from _kyty.txt — from --- Guest fault context --- down through the Unhandled host exception line — into the crash-log reader. What comes back:
--- Guest fault context --- thread: GameMainThread rax=0000000000000000 rbx=000000090009e000 rcx=0000000000000050 rdx=00000009000a3b40 rsi=0000000900abcd00 rdi=0000000000000000 rbp=0000000900c80000 rsp=0000000900c7f800 ... code (pc-48 .. pc+48, fault at byte 48): ... 48 89 07 48 83 c4 50 c3 ... byte 48: mov [rdi], rax — a store through rdi stack: 000000090009e000 0000000900c01234 0000000900c7f830 0000000000000050 candidate address; map ownership must be checked ... Unhandled host exception: type=1 code=3221225477 pc=0x00000009000a3b4c access=2 address=0x0000000000000000
The discipline from topic C3, applied:
- The symptom is the write to address 0.
type=1is an access violation,access=2is a write,address=0x0is a null. The native code3221225477is0xC0000005, STATUS_ACCESS_VIOLATION — expected. - Stack words suggest leads. A value resembling a code address is not a validated return address. Resolve the fault PC and each candidate against actual loaded executable segments, then inspect the caller and call instruction. This fixture does not supply enough mapping or call-history evidence to blame the pad library.
- Cross-check with the register table.
rdi = 0with a store throughrdiin the code bytes is the giveaway: the destination pointer is a null. A register dump is only useful when you ask "which register was supposed to hold an address?"
Step three — confirm what the loader sees
Resolve the faulting PC using the load log and inspect the relevant module. The module inspector can show imports, exports and Sony dynamic tags, but an import declaration does not prove where its relocation points at runtime. Inspect the actual slot: is it host HLE, a guest export, or a generated unresolved-import thunk? Follow that destination before choosing a file to edit.
Step four — the instruction itself
From the crash block's code window — 96 bytes, the faulting instruction starting at byte 48 — you can decode the exact instruction that died. The RDNA2 decoder is for shaders, but the same idea applies here: feed the bytes from offset 48 onward to any x86 disassembler (or read them with Zydis, which is in the tree — topic C6) and confirm the operation is a store through a null base. That turns "access violation" into "this exact store wrote to a null pointer" — a more precise symptom; the origin of the null pointer still needs tracing.
Step five — the fix loop
Trace the bad value backward to its producer. If it crosses an HLE boundary, establish that API’s return code, output-buffer, timing and failure contracts. If a relocation or patch supplied the wrong destination, the fix belongs there instead. Do not insert a plausible success value just to move the crash.
- Reduce the failure to a specific input and expected result.
- Add a focused test that fails for the observed contract violation, including relevant error and boundary cases.
- Make the smallest correction, build the affected target and run those tests.
- Repeat the same game route if runtime validation is in scope. Record correctness and visible progression separately from “no crash.”
When it is not a crash
A persistent black screen with no crash block is a different animal, and it is worth listing its checklist because it comes up more often:
| Symptom | Suspect | Evidence |
|---|---|---|
| Black screen, no crash, CPU idle | a blocking HLE call never returned | debugger shows a blocked thread; inspect its wait condition and the producer expected to signal it |
| Black screen, CPU busy | a draw is submitted but nothing renders | command-buffer dump: feed the PM4 decoder; look for a draw with no render-target state (the Lab's pm4build shows the failure shape) |
| Black screen, first frame slow then nothing | a shader fell back or failed to compile | _Shaders/ + the shader reader: check the last compiler phase and actual emitted module; dispatcher fallback is a supported path, not itself a failure |
| Flicker then crash on frame N | a resource lifetime bug | the crash trace names a texture/buffer address; the timeline story (topic C4) is the suspect |
Record the symptom, locate the executing code, trace the value or dependency that failed, then test the proposed cause. A module name, absent crash or dispatcher-fallback line is a lead, not a verdict.
- GuideDebugging & diagnosticsThe preceding diagnostics lesson lists the flags and artifacts this case study uses.
- GuideReading a crash logTopic C3's annotated walk through every block of the dump, referenced above.
- ArticleDebugging for beginnersMicrosoft’s introduction to reproducing a problem, inspecting state and testing a debugging hypothesis.
Which switch produces which artifact — the first step of the case study, and the same map the crash reader and shader reader tools were built on.
Annotate the supplied log excerpts first. You can apply the same sequence to your own title when a game and an emulator build are available.
Use the supplied excerpts or a bounded run of your own title. Feed the relevant evidence to the playground tools in order: log triage (find the buckets), crash reader (if it crashed), module inspector (what the loader saw). For an ELF inspection you need an actual module; mark that step pending if you have none. Write down a candidate cause, the observations supporting it and the test that could disprove it. A log line alone does not establish causation.
Use the worked example or your own reproduction. Label supplied evidence, source inspection and observed runtime results separately.
Checkpoint