test(#850): harden 9 differential oracles to read ELF .symtab, not disasm text#853
Merged
Conversation
…sasm text Nine sibling differential scripts located function offsets by regex-parsing `synth disasm` TEXT — host-dependent, the exact latent pattern that made the v0.50 rv32 memory.size/grow oracle fail 100% on the Linux CI runner (empty syms) while passing on macOS (#850). Convert each to read the ELF symbol table via pyelftools instead (host-independent), copying the rv32_mem_size_grow_242 reference fix. synth emits the symtab as an UNNAMED SHT_SYMTAB section (get_section_by_name (".symtab") returns None for the ARM builds), so all scripts iterate by sh_type == "SHT_SYMTAB" and filter st_shndx == text_idx. Address semantics locked per regime, verified before==after against the captured baselines (same match counts): - ARM (add_imm_large, bulk_memory_374, div_const, i64_large_offset_382, load_store_big_offset_382, uxth_fold): ARM Thumb function symbols carry the Thumb bit (bit 0), so mask `& ~1` at map construction — the masked value equals the clean disasm address exactly, making every downstream `(CODE + fa - base) | 1` formula byte-identical. In bulk_memory_374 the mask is load-bearing for trap_addr too (the hook compares the EVEN executing PC; an unmasked odd trap_addr would silently never fire). - div_const is positional (sort addrs, zip with PAIRS): the symtab carries two names per address (func_N + export alias), so dedupe with set() and filter STT_FUNC before the len==PAIRS assert; masked-sorted set equals the old sorted disasm addresses. - RV32 (control_step_riscv, controller_step_riscv, signed_div_const_riscv): --relocatable, st_value is the .text offset with no Thumb bit — use directly (CODE + fa), no mask, no base subtract. No disasm fallback added: every target build carries a symtab and none are the stripped self-contained images that motivated 677/679's fallback — a fallback would just re-import the host-dependent path #850 removes. bulk_mask_679 and bulk_local_clobber_677 already read the symtab by type and mask `& ~1` at use — verified their symtab branch fires (fixtures carry a SHT_SYMTAB), so they were left as-is (already hardened). Test-harness only — byte-invisible to codegen. All 9 re-run PASS with identical results to their pre-conversion baselines. Advances #850. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Nine sibling differential/oracle scripts under
scripts/repro/located function offsets by regex-parsingsynth disasmTEXT. That is host-dependent — the exact latent pattern that made the v0.50rv32 memory.size/groworacle fail 100% on the Linux CI runner (empty syms → "no symbol for export sz") while passing on macOS (#850). This converts each to read the ELF symbol table via pyelftools instead (host-independent), copying therv32_mem_size_grow_242_differential.pyreference fix.Scripts converted (9)
add_imm_large_differential.py& ~1(Thumb bit)bulk_memory_374_differential.py& ~1— also fixes trap_addrdiv_const_differential.pyset()dedupe + STT_FUNCi64_large_offset_382_differential.py& ~1load_store_big_offset_382_differential.py& ~1uxth_fold_differential.py& ~1control_step_riscv_differential.pyst_valuedirect (no mask)controller_step_riscv_differential.pyst_valuedirectsigned_div_const_riscv_differential.pyst_valuedirectKey findings / correctness
SHT_SYMTABsection for the ARM builds (get_section_by_name(".symtab")returnsNone), so every script iterates bysh_type == "SHT_SYMTAB"and filtersst_shndx == text_idx.st_value= clean disasm addr + 1. Masking& ~1at map construction makes the map byte-identical to the old disasm map, so every downstream(CODE + fa - base) | 1formula is unchanged. Inbulk_memory_374the mask is load-bearing fortrap_addrtoo (the hook compares the even executing PC — an unmasked odd trap_addr would silently never fire, a vacuous green).div_constis positional (sort addrs, zip withPAIRS): the symtab carries two names per address (func_N+ export alias), so dedupe withset()and filterSTT_FUNCbefore thelen == PAIRSassert..symtab,st_valueis the.textoffset with no Thumb bit → used directly.Verification
Captured each script's baseline output before editing, then re-ran after with a freshly-built binary — all 9 PASS with identical match counts (before == after):
control_step 5/5 · controller_step 5/5 · signed_div 5/5 · add_imm 8/8 · div_const 338/338 · uxth 7/7 · i64_lo 5/5 · ls382 5/5 · bulk_memory 16/16Cross-checked masked symtab addresses == disasm addresses across all ARM ELFs and RV32 (st_value=0, base=0) regimes before deleting the disasm path.
bulk_mask_679andbulk_local_clobber_677already read the symtab by type and mask& ~1at use — verified their symtab branch fires (fixtures carry aSHT_SYMTAB), so left as-is (already hardened).Test-harness only — byte-invisible to codegen.
Advances #850.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJK5LZZEkV5smCY1jKn18L