Step 36 · First contribution
Reading a run
The rest of the course explains the machine. This page is the field guide: the exact log lines a build at d2413fc2 writes, what each field means, and which grep to run first.
Before this page: Debugging & diagnostics. View the learning route.
A useful run is one you can search. Start with --printf-direction File --printf-output-file _kyty.txt. Add dumps only when the first file has told you which subsystem is stuck.
The order the log arrives in
The first few dozen lines are the boot you traced in ELF to first guest instruction: host virtual memory, parsed flags, lifecycle inits, HLE registration, SELF/ELF load, TLS patches, relocations, then guest entry. After that the log is reactive — a line appears because guest code called something, faulted, or submitted GPU work.
| You grep for | It means | Then |
|---|---|---|
unresolved symbol | An import the linker could not bind. The NID and owning module are on the same line. | Your first change |
EXIT_NOT_IMPLEMENTED | A case the author marked and left. The condition and file are in the message. | That file, that condition |
Patch tls at addr: | A nine-byte fs:[0] read was rewritten to Jit::Call9. | Patching guest instructions |
CFG dispatcher fallback: | This shader will emit a program-counter dispatcher. Not an error. | Section below |
phase begin / phase end | Recompiler timings, with stage name and shader hash. | Section below |
--- Guest fault context --- | Unhandled host exception. The process is about to EXIT. | Crash reader |
Shader phase lines
TranslateProgram() and CompileProgram() log every expensive phase with the same shape. The label identifies the shader stage (for example ShaderRecompiler VS, PS, CS or MS). A cold vertex shader looks like this:
ShaderRecompiler VS phase begin: stage=VS hash=0x… code_words=N decode ShaderRecompiler VS phase end: stage=VS hash=0x… decode instructions=N elapsed_ms=… ShaderRecompiler VS phase begin: stage=VS hash=0x… CFG BuildGraph ShaderRecompiler VS phase end: stage=VS hash=0x… CFG BuildGraph blocks=N loops=N back_edges=N elapsed_ms=… ShaderRecompiler VS phase begin: stage=VS hash=0x… CFG Structurize ShaderRecompiler VS phase end: stage=VS hash=0x… CFG Structurize blocks=N loops=N elapsed_ms=… ShaderRecompiler VS phase begin: stage=VS hash=0x… IR TranslateProgram ShaderRecompiler VS phase end: stage=VS hash=0x… IR TranslateProgram blocks=N elapsed_ms=…
The frontend elapsed_ms values are cumulative from the start of TranslateProgram(); subtract adjacent phase-end values to estimate the time spent in an individual phase. SPIR-V compilation has its own timer. These lines prove compiler work occurred, but a repeated source hash can have different stage, input or specialization state. Compare the complete cache identity and pipeline creation evidence before diagnosing repeated misses; the absence of phase lines alone does not prove a cache hit.
The dispatcher-fallback line
When the CFG is irreducible, or Structurize() fails, the recompiler restores the unstructured graph and logs one line — then continues. The SPIR-V backend emits a loop around a switch on a program counter. The line is:
LOGF("%s CFG dispatcher fallback: stage=%s hash=0x%016" PRIx64
" phase=%s failure=%s block=%" PRIu32 " pc=0x%08" PRIx32 "..0x%08" PRIx32
" preds=%" PRIu64 " succs=%" PRIu64 " blocks=%" PRIu64 " loops=%" PRIu64
" back_edges=%" PRIu64 " reason=%s\n", …);
phase is build (irreducible graph) or structurize (structure could not be recovered). failure is a CFG::FailureKind name. reason is the human sentence — an indirect S_SETPC_B64 jump table is a common one. Grep CFG dispatcher fallback, not a longer phrase: that is the exact text.
Reading Guest fault context
The dump is four blocks, then an EXIT. Read them in this order:
thread:— guest pthread name,(host thread), or(unnamed guest thread). Identifies the thread; use the faulting PC to determine which code it was executing.- Sixteen registers,
%016llx.rspis the stack you are about to dump;rdi/rsi/… are the System V arguments of the faulting call if you are in a prologue. code (pc-48 .. pc+48, fault at byte 48)— 96 bytes, 6 lines of 16. Byte 48 is the first byte of the instruction that faulted. Feed from there into a disassembler.stack:— 32 qwords fromrsp. Values inside loaded executable segments are candidate return addresses; arbitrary stack words are not a validated backtrace.
Then:
EXIT("Unhandled host exception: type=%u code=%u pc=0x… access=%u address=0x…")
type 1 is access violation, 2 is illegal instruction. access 1/2/3 is read/write/execute. address is what was touched:
0x28— inspect the instruction: an unpatchedfs:[0x28]access and a null pointer plus an offset are different possible causes.- A small address — inspect the base register and call history; a null pointer is one possibility.
- A PC in a mapped guest segment — a fault while executing guest code; inspect the module and state before attributing its cause.
Each range is probed with IsReadableRange before it is printed to avoid dereferencing an already unreadable range. The probe is a diagnostic safeguard, not a guarantee against concurrent mapping changes. Drop the block into the crash-log reader.
A grep kit
rg "unresolved symbol" _kyty.txt rg "EXIT_NOT_IMPLEMENTED" _kyty.txt rg "CFG dispatcher fallback" _kyty.txt rg "phase end:.*elapsed_ms=" _kyty.txt rg "Guest fault context" _kyty.txt rg "mesh shader exceeds host limits" _kyty.txt
The last one is the pipeline cache refusing a mesh shader whose thread, vertex, primitive or LDS limits do not fit the device. If the extension is missing entirely, that path is an EXIT_NOT_IMPLEMENTED instead.
The coherency lesson's fault-path animation is the same three branches the dump above comes from. The crash reader on the Playground annotates a real block.
Paste a Guest fault context block into the crash reader, then paste the same log into log triage and confirm the unresolved-import bucket matches what you grepped.
Checkpoint