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.

Read

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:

the first run
.\_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:

from _kyty.txt — the work queue
[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#A tail 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:

the crash, annotated
--- 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=1 is an access violation, access=2 is a write, address=0x0 is a null. The native code 3221225477 is 0xC0000005, 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 = 0 with a store through rdi in 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.

  1. Reduce the failure to a specific input and expected result.
  2. Add a focused test that fails for the observed contract violation, including relevant error and boundary cases.
  3. Make the smallest correction, build the affected target and run those tests.
  4. 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:

SymptomSuspectEvidence
Black screen, no crash, CPU idlea blocking HLE call never returneddebugger shows a blocked thread; inspect its wait condition and the producer expected to signal it
Black screen, CPU busya draw is submitted but nothing renderscommand-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 nothinga 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 Na resource lifetime bugthe crash trace names a texture/buffer address; the timeline story (topic C4) is the suspect
The whole method in one line

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.

Learn more · the diagnosis craft
  • Guide
    Debugging & diagnosticsThe preceding diagnostics lesson lists the flags and artifacts this case study uses.
  • Guide
    Reading a crash logTopic C3's annotated walk through every block of the dump, referenced above.
  • Article
    Debugging for beginnersMicrosoft’s introduction to reproducing a problem, inspecting state and testing a debugging hypothesis.
Watch

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.

Try

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.

Apply the diagnosis method

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.

Do

Use the worked example or your own reproduction. Label supplied evidence, source inspection and observed runtime results separately.

Checkpoint