A naive, easy-to-follow sample to check whether your analysis tool (decoders, lifters, emulators) models undefined effects correctly across these opaque instructions.
Two codegen paths, same runtime behavior:
- Static (default), stubs from
ub_stubs.gen.asm, maze fromchain.gen.cpp(both generated and committed). - JIT (
-DUB86_USE_ASMJIT=ON), asmjit emits stubs + maze into aPAGE_EXECUTE_READregion at startup with per-process random tail-jmps (push/ret, lea+jmp, decoy bytes after jmp); chain runs on a freshstd::thread.
Design notes in devdocs/plans.md; changelog in devdocs/progress.md. (thanks @mrexodia for this tip!)
Windows + MSVC x64, Ninja optional.
build.bat # static path -> build/toy.exe
build-jit.bat # JIT path -> build-jit/toy.exe (UB86_USE_ASMJIT=ON)
Both scripts auto-detect MSVC via vswhere.exe (ships with every VS 2017+ install). If it's not on a default path, set VCVARS to your vcvars64.bat to override, or run from an x64 Native Tools Command Prompt:
set "VCVARS=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
build.bat
toy.exe # all probes + branchers (default)
toy.exe cpuid # CPU vendor / brand / family-model-stepping
toy.exe ctt-chain # prompt for key, walk the maze, print success|fail
toy.exe probe-self [PATH] # observe truths, write back to stubs.gen.json
toy.exe use-affinity # split probes/branchers across E vs P cores
ctt-chain is the lifting/emulation toy. It reads 8 ASCII chars and checks a password. The code itself isn't interesting; the point is whether your tool can either (1) recover a path to the success condition, or (2) lift + optimize the program down to a form where the opaque CPU state is obviously irrelevant.
tools/gen_stubs.py is the source of truth: it emits the manifest (tools/stubs.gen.json), the asm stubs, the shared header, and a placeholder chain.gen.cpp so the first build links. tools/gen_chain.py later overwrites chain.gen.cpp with the real maze from the recorded truths. Truths come from toy.exe probe-self (works in both modes) and are folded back via gen_stubs.py --record-truth.
Static path:
python tools/gen_stubs.py --seed 12345 # stubs + placeholder chain
build.bat # bootstrap (truths still null)
build/toy.exe probe-self # fill truths in stubs.gen.json
python tools/gen_stubs.py --record-truth # re-emit header with truths
python tools/gen_chain.py # emit the real chain.gen.cpp
build.bat # final build
printf 'ub86pred\n' | build/toy.exe ctt-chain # -> success
JIT path (no gen_chain.py, chain_jit.cpp emits the maze at runtime from the same manifest):
python tools/gen_stubs.py --seed 12345
build-jit.bat
build-jit/toy.exe probe-self
python tools/gen_stubs.py --record-truth
build-jit.bat
printf 'ub86pred\n' | build-jit/toy.exe ctt-chain # -> success
Run on a different uarch and probe-self records that host's truths; both codegen paths consume them. Stubs that fault (aad_imm_*) stay in the corpus but are excluded from the chain.
tools/gen_stubs.py: --seed N (default 0xC0FFEE), --instances-per-primitive N (default 2, pool ≈ 14 × N), --record-truth (re-emit header from manifest), --no-placeholder-chain, --out-asm/--out-hpp/--out-chain/--manifest PATH.
tools/gen_chain.py: --depth N (default 1024, ≥ 2), --manifest/--out PATH.
| # | Primitive | UB axis |
|---|---|---|
| 1 | imul_zf |
imul r32,r32: SF/ZF/AF/PF undefined |
| 2 | mul_zf |
mul r32: SF/ZF/AF/PF undefined |
| 3 | idiv_flags |
idiv r32: CF/OF/SF/ZF/AF/PF undefined |
| 4 | bsf_dest_zero |
bsf r32,0: destination undefined |
| 5 | bsr_dest_zero |
bsr r32,0: destination undefined |
| 6 | bswap16_high |
66 bswap r16: result undefined |
| 7 | shld_overshoot |
shld r,r,cl>31: result + flags undefined |
| 8 | shrd_overshoot |
shrd r,r,cl>31: result + flags undefined |
| 9 | lzcnt_zero |
lzcnt: SF/PF/AF/OF undefined |
| 10 | tzcnt_zero |
tzcnt: SF/PF/AF/OF undefined |
| 11 | bextr_pf |
bextr: PF/AF/SF undefined |
| 12 | rcpss_lowbit |
rcpss xmm: low mantissa bit varies (AMD vs Intel) |
| 13 | rsqrtss_lowbit |
rsqrtss xmm: low mantissa bit varies |
| 14 | aad_imm |
D5 imm8: #UD in long mode (faults) |
Per-instruction Intel SDM JSON in scripts/data/intel/ (committed). Regenerate or browse via scripts/scrape_intel_pdf_entries.py + scripts/serve_intel_browser.py, see SCRAPER.md.
- Scraper relies on https://revers.engineering/x86/, happy to remove if not OK.
- liblisa, public dataset on undefined flags/conditional behavior (https://explore.liblisa.nl/instruction/).
- sandpile, same idea different angle (https://www.sandpile.org/x86/flags.htm).
- asmjit, fetched on demand for the JIT path (https://github.com/asmjit/asmjit).