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.

Read

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 forIt meansThen
unresolved symbolAn import the linker could not bind. The NID and owning module are on the same line.Your first change
EXIT_NOT_IMPLEMENTEDA 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 endRecompiler 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:

src/graphics/shader/recompiler/ShaderRecompiler.cpp — LogDispatcherFallback()
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:

  1. thread: — guest pthread name, (host thread), or (unnamed guest thread). Identifies the thread; use the faulting PC to determine which code it was executing.
  2. Sixteen registers, %016llx. rsp is the stack you are about to dump; rdi/rsi/… are the System V arguments of the faulting call if you are in a prologue.
  3. 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.
  4. stack: — 32 qwords from rsp. 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 unpatched fs:[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.

Watch

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.

Try

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.

Do

Checkpoint