Repository navigation
pclntab: recognize .lrodata_gopclntab and __lrodata_gopcln sections - #1267
Merged
Merged
Conversation
zaeku
force-pushed
the
fix/gopclntab-lrodata
branch
from
October 26, 2026 01:06
51b21a1 to
42206ef
Compare
…reg, default on with the golang spec) (Noelo-Lab#1276) At spec build, when the golang cspec is active, the g register (amd64 R14, arm64 x28, per the vendored register.info; x86-32 is TLS and gets none) joins the global scope the way a cspec <global><register/> entry would, with a type+name-locked g symbol typed runtime.g* - vendored specs stay pristine. runtime.g/gobuf shells carry only source-verified fields (release-stable liblink prefix + go1.22-pinned tail); past-shell accesses render as anchored offsets. option goreg on|off default on, inert without goabi by construction. Stripped go1.22 witness: phantom r14 locals 267 -> 23, named g-> readers 7 -> 297, wall +1.1%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…tstr Go arm) (Noelo-Lab#1277) The string-record pass gains a Go registration: on real go1.22 binaries (non-PIE, zero relocs) 480/545 of the witness (ptr,len) headers sit in .data/.noptrdata, so the Go arm widens only the SLOT regions; the target test is unchanged (text in genuinely read-only data) and the rustc arm is byte-identical. Longest-first same-start dedup + the occupied-address guard handle Go substring sharing; registered before gopclntab (appended-last contract); same fatstr gate, no new option. 12-binary corpus: fns with a string literal 164 -> 7,046; wall +1.2%. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…r entry points (Noelo-Lab#1278) CI's runner Go is newer still than go1.25: exitsyscall no longer carries the stackguard0 store there either. Which single function touches the field shifts release to release, but the preemption protocol guarantees it is written somewhere among the scheduler entry points, so the e2e now scans six small unambiguous candidates (exitsyscall, reentersyscall, gopark, execute, park_m, acquireSudog - all rendered it on go1.22.10 and go1.25.3) and asserts at least one renders g->stackguard0, with every transcript in the failure message. unlock2 keeps the g-> and off-arm asserts. Test-only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
… runtime.m shell (Noelo-Lab#1282) Shells are versioned off the image build-info version (GoLayout tables per release: go1.23 inserted g.syscallbp, go1.25 dropped gobuf.ret), fixing the g shell silently mis-naming fields on newer Go; runtime.m + gsignalStack shells join the builder with source-verified fields, g.m typed runtime.m*. Rides goreg, no new option. go1.22 hello: raw g->m[idx] fns 166 -> 42, ~77 fns render named curg/locks; go1.25: 53 curg readers at shifted offsets (was 10). Self-selecting m-field witness green under go1.22.10 + go1.25.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…obool, default auto) (Noelo-Lab#1283) Go returns a bool as bare SETcc with unspecified upper bytes; upstream rightly refuses to narrow without zero-evidence (rustc zeroes first), so RETURN(PIECE(stale7,bool1)) rendered CONCAT71 artifacts and 8-byte signatures. RuleGoBoolReturn rewrites the RETURN to ZEXT(bool) when the low byte is a genuine boolean definition; the dead concatenation falls to DCE, the PIECE survives other readers - no deleted stores by construction. gobool off|auto|always, default auto (Go images). Stripped hello: 57 fns change, CONCAT71 fns 64 -> 34 (residue = mid-function stores, separate judgment), type:.eq.* helpers recover bool signatures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…ata (option gotypes, default on) (Noelo-Lab#1284) Moduledata located on stripped images by scanning data sections for a word pointing at the pclntab header (validated by the funcnametab slice beside it); types/etypes + typelinks found by cross-validation (sampled entries must decode) rather than per-release offsets, absorbing go1.18-1.25 drift; typelink pointer forms resolve to their named pointee; _type/name decoders cover both layouts. Phase 1 is a provider: hello 356 typelinks -> 143 named shells, gofmt 731 -> 322, vet 1626 -> 638; output byte-identical on/off across all witness functions by proof; cost below the measurement floor. option gotypes on|off default on, Go images only. E2e pins main.Point end-to-end under go1.22.10 and go1.25.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…e typelinks slice is gone (Noelo-Lab#1285) go1.26 emits firstmoduledata into a new .go.module section (the anchor scan never looked there) and removed the typelinks slice (runtime builds the list by walking [types+PtrSize, types+typedesclen) with per-kind descriptor strides). Fix stays shape-detected: anchor scan gains the .go.module spellings; meta locator tries the slice form, else the types/typedesclen/etypes triple walked with strides faithful to internal/abi.Type::DescriptorSize, accepted only when >=8 records decode back-to-back. Full Go e2e family green under go1.22.10/1.25.3/1.27.2; suite ran green with 1.27.2 on PATH; proven on CI toolchain via full-ci. Strict fix, no counts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
…nly subset) (Noelo-Lab#1286) A ported name-only Swift demangler subset (parse-or-passthrough: context chains, functions with argument labels, accessors, init/deinit, six global markers; punycode/witnesses/thunks/specializations pass through byte-identical; the one ambiguous production refused). Wired beside the Rust/Itanium/MSVC demanglers behind the Swift name-shape test; swiftdemangle on|off default on, loadtime env bridge. Crate alternatives rejected on evidence (cmake C++ build + rustc floor; unvetted 0.x). Corpus hand-derived from the Swift ABI mangling spec with per-case citations. E2e synthesizes an ELF with genuine mangled names (no Swift toolchain exists here or on CI - the vendored-fixture follow-up is named in the spec prose). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoTv7T2Ep5hmbZcyYSMk1g
… export (Noelo-Lab#1290) * Add --stream-layout address: streamed progress over a reproducible export `decompile-project --stream --stream-layout address` runs the non-stream export unchanged (same serial or pooled decompile, plan, structure naming, merge and artifacts) and reports on it while it runs: `.streaming` on its clock, a running README, and `index.jsonl` as a per-function feed in completion order with null `.c` spans. At the end the four artifacts are the non-stream export's byte for byte, `index.jsonl` is replaced by the address-ordered index of that `.c`, and `.streaming` is removed, so the folder does not depend on --jobs N wherever the non-stream export does not. The default layout (`decompile`) is unchanged. Hooks: jobs::PoolWatch on the pool's planned first pass, a `landed` callback on the serial and callee-first loops, and decompile_project::ExportWatch around the export. Measured on /usr/bin/ssh (1,090 functions): address-layout runs at --jobs 2 and 4 and a non-stream --jobs 4 run leave identical folders apart from index.jsonl; CPU time of the two --jobs 4 arms is within noise. [AUTOMATED] Authored by an agent. * Rename --stream-layout address to --progress Progress reporting and incremental artifacts are two separate things; `--stream` bundles both, and only the incremental `.c` costs reproducibility. `--progress` is now its own flag: the status file and the index.jsonl feed over an ordinary export written at the end. With both flags the export is the streamed one. `--progress` accepts `--assert` under the non-stream export's rules. `--stream-layout` is removed. [AUTOMATED] Authored by an agent.
…ions The pclntab bullet in docs/spec/01-program-prep.md and locate_header's header listed only .gopclntab/__gopclntab; add the large read-only data split's spellings this PR teaches the locator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
Contributor
|
[AUTOMATED] Pushed b4d4ef1 (docs only, no code change): the Go pclntab bullet in |
…Lab#1259) On every PowerPC language, a `lwarx`/`stwcx.` retry loop decompiles as a loop instead of as one atomic update. In the output below, `v1` is never assigned, so the store and the loop exit both depend on an uninitialized value. ```sh printf '\tlwarx 9,0,3\n\taddi 9,9,1\n\tstwcx. 9,0,3\n\tbne- .-12\n\tblr\n' > atomic.s powerpc-linux-gnu-as atomic.s -o atomic.o powerpc-linux-gnu-ld -Ttext=0x10000 -e 0x10000 atomic.o -o atomic.elf kuna decompile atomic.elf 0x10000 --addr ``` ```c void sub_10000(int *a0) { char v1; // reserve unsigned char v2; // cr0 do { if (v1) { *a0 = storeWordConditionalIndexed(*a0 + 1,0,a0); v2 = 2; } } while (!(bool)(v2 >> 1 & 1)); } ``` With the fix this is `*a0 = storeWordConditionalIndexed(*a0 + 1,0,a0);`. Compiled `__atomic_fetch_add`, compare-exchange, exchange and spinlock code shows the same pattern. ## Fix - `stwcx.` and `stdcx.` skip their store while `RESERVE` is 0, but `lwarx` in `ppc_instructions.sinc` had its reservation setup commented out, with a misspelled register name. It now sets `RESERVE`, `RESERVE_LENGTH` and `RESERVE_ADDRESS`, as the ISA `lwarx` does. - That constructor fixes the hint bit at 0, so for ordinary encodings it is chosen over the ISA one in the ISA languages too. All 23 PowerPC languages were affected, and all now produce the single conditional store. ## Tests - `tests/stages/kuna-ppc-lwarx-reservation.xml`: the loop above on `PowerPC:BE:32:default`. It passes 0/2 without the fix and 2/2 with it. - `kuna-sleigh` `ppc_lwarx_reservation`: `lwarx` writes all three reservation registers on generic 32- and 64-bit, e500 and ISA languages. It fails on the unpatched specs. - The datatest corpus is unchanged (675/675). No other stage expectation moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
…oelo-Lab#1291) ## Problem A PPC32 call to `__stack_chk_fail` can lose its name and no-return contract, making decompilation walk past the end of the function. With Clang and GNU PowerPC binutils: ```sh mkdir -p tmp/ppc32-plt printf '%s\n' 'extern void __stack_chk_fail(void) __attribute__((noreturn));' \ 'int check(int x) { if (x) __stack_chk_fail(); return 7; }' | clang --target=powerpc-linux-gnu -O1 -fPIC -fno-unwind-tables \ -fno-asynchronous-unwind-tables -x c -c - -o tmp/ppc32-plt/check.o powerpc-linux-gnu-ld -shared --bss-plt tmp/ppc32-plt/check.o -o tmp/ppc32-plt/check.so kuna decompile tmp/ppc32-plt/check.so check --mode fast ``` ```text error: Unable to load 512 bytes at r0x00000184 ``` ## Fix - Resolve BSS-PLT imports from jump-slot relocations and absolute secure-PLT stubs from validated slot addresses. - Use the imported name for calls, known contracts and xref queries; retain local stub labels as aliases. - Decline incomplete stubs, conflicting metadata and all unproven `r30`-relative stubs. An absent `.got2` does not prove the caller uses the GOT base. ## Tests Synthetic ELF32 tests cover both byte orders, stripped and symbolized stubs, returning imports and absent fall-through bytes; the terminal-call test fails before the fix. Collision tests and a GNU-linked fixture with discarded `.got2` require `return 7` to survive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
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.
Follow-up to #1237.
In large Go binaries built with split/large read-only data linker rules (such as Bazel/Blaze Go toolchains), the
pclntabtable is emitted in its own.lrodata_gopclntabsection rather than.gopclntabor.rodata:.lrodata_gopclntab__lrodata_gopcln(truncated to 16 bytes by Mach-O'ssectname[16]limit)Because
pclntabsits in that dedicated section instead of__gopclntabor__rodata, both step (1) and the step (3) fallback scan inlocate_headermiss it.The fix
".lrodata_gopclntab"and"__lrodata_gopcln"to the step (1) named-section lookup inlocate_header.Tests
pclntab/tests.rs:macho_lrodata_gopcln_section_is_found_and_decodedexercisesrecover_go_functionson a hand-built arm64 Mach-O carrying__TEXT,__lrodata_gopclnwithtextStart = 0.cargo test -p kuna-analysis pclntab(18/18) andmake check-specpass.