D276 - The expensive half of a watchpoint, built because the cheap half ran out

decided · 2026-08-25 · the image+0xafc959 wall, after every call was eliminated

D223 chose a snapshot over a watchpoint and gave the reason: a watchpoint costs a debug register, a per-platform API and an exception per access, while a snapshot answers "did anything ever fill this slot in?" for one memcpy and no platform code. That was right for the question being asked then, and it is still the thing to reach for first.

The question changed. Twenty-three dye runs - every function PPSA02664 calls, implemented and unimplemented, return values and out-parameters - left the fault byte-identical at write to 0xfffe0 while executing at image+0xafc959. So the missing region base is not produced by any call, and the snapshot has already said which words nobody wrote. What is left to ask is who read the word nobody wrote, and a snapshot cannot answer that at all.

That is the case D223 named as the one where a watchpoint earns its keep: when and who matter. So it is built now, and the two are kept as separate diagnostics rather than one replacing the other, because they answer different questions and the cheap one is still the one to run first:

ORBISTOUN_WATCH ORBISTOUN_WATCHPOINT
mechanism copy the region, diff it after x86 debug registers, trap per access
answers which bytes ended up different which instruction touched it, and when
costs one memcpy a debug register, an exception per access
limit any size four, of 1/2/4/8 bytes, aligned

The two compose into a mechanical pipeline, which is the point of building it rather than a one-off script: the snapshot names the words nobody wrote, and up to four of those addresses become read-or-write watchpoints on the next run, each reporting the instruction that consumed the empty slot. Neither step needs a person to read the guest's code, which is what makes it a candidate for step 17 of the loop rather than a manual detour.