Automatically find one missing compiler adjustment

August 31, 2026 ยท View on GitHub

This small project starts one byte away from its reference executable. The C++ is already correct; Microsoft Visual C++ 4.2 simply chooses a different register encoding when it compiles the function.

The grind command runs a small automatic search over four declaration-only compiler states. Exactly one of them reproduces the reference function. Accepting that result should save two small reviewed records:

  • a cheap declaration-shape donor; and
  • an equal-body function intervention that uses only that freshly compiled donor.

The pair costs 26 relative points. The project already contains one independent timestamp-normalization rule so repeated linker runs are deterministic.

Run it

From the ReproBit repository root, prepare the compiler once and run the example:

cd examples/grind
rbit setup .
python prepare_reference.py
rbit discover grind .
rbit discover grind . --accept-exact
rbit verify .

rbit setup remembers the authenticated compiler location, so the other commands need no machine-specific paths. If MSVC 4.2 is already set up on this machine, skip that first line.

The first grind is a read-only, project-wide preview. It looks at committed compiler steps and the available objects under reference/. This sample has one of each, so no mapping or plan editing is needed. In a larger project, name a reference object after its source filename without the extension, use an exact translation-unit ID, or pair it explicitly with --reference-object TU=PATH.

Review the report, then run the approval command to repeat the proof and save only matching donor and function records. The search is deliberately bounded to low-cost candidates; it aims for useful progress, not an exhaustive automatic solution. The following verify is a separate build from scratch: it must not reuse a grind artifact or the developer cache as certification evidence. Open .reprobit-state/reports/grind/project/report.html for the campaign summary and links to each per-function decision and winning build-from-scratch verification report. The bounded plan behind every decision is kept beside the report, and the page shows the exact copyable command for approval or final verification.

For one deliberately selected function, the expert flow remains available through rbit discover init followed by rbit discover grind . --expert-plan reprobit/discovery.json.

To repeat the example from its original one-byte mismatch, reset only the two files that approval changes, then remove the generated reports:

git restore -- reprobit/interventions/tu.transform.json reprobit/proofs/tu.transform.proof.json
rbit clean . --reports

This keeps the prepared compiler, reusable build cache, and generated reference binary, so the next grind can start quickly.

Advanced: pinned binary provenance

What is deliberately unsolved

Before the grind, the clean _transform body is 137 bytes with SHA-256 059b98332d6e22d42878a7921fdc7f294f0388d571fdde80a731be79b05f832b. The reference body is also 137 bytes, but byte 65 differs and its SHA-256 is 0592ba1107856e319c261ed45129ab9b518486acbde960ada58b2ace9435ccfb. After timestamp normalization, that becomes one differing byte in the PE file at offset 577. This equal-size, one-byte case intentionally exercises the smallest built-in donor path rather than resize, mosaic, binary surgery, or reference-byte copying.

Reference provenance

prepare_reference.py compiles this checked-in source with the campaign's classes=1, functions=10 declaration state, using the authenticated MSVC 4.2 toolchain pinned by reprobit/toolchain.lock.json. It links a minimal PE with no libraries and normalizes only candidate-owned timestamp fields to zero. It then checks the known reference function and final image digests before publishing:

  • reference/reference.obj, used only as sealed discovery input; and
  • reference/grind.exe, used only by the final literal verifier.

Those generated binaries are intentionally not committed or redistributed. The normalized reference PE is 1,536 bytes with SHA-256 9c78bd9cfe3c8ded8a9a587165237d2a394719b48be34021a3cb09aff8220aab. The compiler comes from archaic-msvc/msvc420 with the two runtime DLLs pinned from archaic-msvc/msvc500, as recorded in the lock file.