Skip to content

[AUTOMATED] Name static Go itabs as go:itab.<concrete>,<interface> data symbols - #1544

Merged
mahaloz merged 1 commit into
mainfrom
multilang-go-itabs
Nov 11, 2026
Merged

mahaloz merged 1 commit into
mainfrom
multilang-go-itabs

Conversation

@mahaloz

@mahaloz mahaloz commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

A stripped Go binary's interface dispatch referenced anonymous addresses — the itab (interface-table) records that bind a concrete type to an interface rendered as dat_<addr>:

go build -ldflags="-s -w" -o hello main.go
kuna decompile hello <fn-with-iface> --json | grep itab
#   ... == &dat_4a1c20          # before
#   ... == &go:itab.main.Point,fmt.Stringer    # after

The fix (rides gotypeglobals #1540 — no new option):

  • Each static itab becomes a go:itab.<concrete>,<interface> named data symbol. The two type names come from the two descriptor addresses the record points at (offsets 0 and 8), resolved through the already-decoded type blob — never from a spelled name (the [AUTOMATED] Per-call-site output type lock + Go allocation result typing (sitetypelock, gonewtype) #1542 lesson); Inter is validated KIND_INTERFACE and both descriptors must land in the type blob or the record is rejected.
  • Locates itabs via moduledata's itablinks slice (go1.18–1.25) or the contiguous [types+itaboffset, …) region go1.26+ walks record-by-record (the same restructuring as [AUTOMATED] Walk go1.26+'s descriptor region: moduledata moved and the typelinks slice is gone #1285's typelinks change), with the era-stable internal/abi.ITab layout.
  • Name-only symbols (how Go's own symtab names itabs), not typed facts: an early typed design demoted the polymorphic itab-address pointer and lost 42 names on unstripped gofmt — the requirement-7 sweep caught it; the fix plus an occupied-address filter (never override a name the image already has) proves LOST=0 on unstripped binaries.

Measured (stripped, additive): hello 0→57 itab refs, gofmt 0→852 (358 distinct); gotypeglobals off emits zero (byte-identical to pre-change); unstripped off==on, zero names lost.

Tests: verify_gotypes::stripped_go_itab_renders_named green under go1.22/1.25/1.27; 5 hermetic unit tests on synthetic itab blobs (both locate paths, the garbage-stop, the method-tail size). Datatests 675/675 and stages unchanged.

🤖 Generated with Claude Code

…ta symbols (rides gotypeglobals)

Repro:
  kuna decompile-all hello.stripped --option gotypeglobals off | grep -c 'go:itab\.'   # 0
  kuna decompile-all hello.stripped                            | grep -c 'go:itab\.'   # 57

A Go binary's interface-dispatch tables (itabs) are reachable through the
moduledata but lose their `go:itab.<concrete>,<interface>` linker names when
the image is stripped, so every interface conversion renders its itab operand
as a bare `dat_<addr>`. This teaches the gotypes harvest — the second consumer
after gotypeglobals' type descriptors — to reinstate those names.

Each static itab is located per toolchain era: the go1.18..1.25 `itablinks`
pointer slice in the moduledata, or the go1.26+ `[types + itaboffset, +
itabsize)` contiguous region walked by each record's `ITab.Size()`. The two
type names are built from the two descriptor ADDRESSES the record points at
(Inter at offset 0, Type at offset 8), resolved through the already-decoded
`[types, etypes)` blob — never from a spelled type name (the #1542 lesson:
bind by descriptor address).

The itab becomes a NAME-only data symbol, not a typed object: an itab address
is used polymorphically (passed to getitab, compared, stored into an interface
word), so locking its global to a struct type would demote those uses and hide
the very symbol a reference renders — exactly how Go's own symtab names an itab
untyped. An itab the image already names (an unstripped binary's symbol table)
is left to that name via an occupied-address filter, so the pass only ever ADDS
the names stripping removed; it never overrides or loses an existing one.

Rides the existing `gotypeglobals` option (no new option, no catalog count
change): the same gate commits both descriptor globals and itab names, so
`gotypeglobals off` is byte-identical to before and non-Go images never see it.

Measured (fresh build):
  stripped additive:  hello 0->57 itab refs (15 distinct), gofmt 0->852 (358)
  unstripped no-loss: gofmt 184 distinct names OFF==ON (LOST=0, ADDED=0),
                      hello 0->10 (additive, LOST=0)

Gates: make test 675/675, make test-stages 4047/4047, make rust-test exit 0
(under vendored go 1.22; the system go 1.18 fails unrelated Go-build tests),
make check-spec OK, kuna catalog --check OK. Three-toolchain e2e witness
(go1.22/1.25/1.27) plus hermetic unit tests on synthetic itab blobs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mahaloz
mahaloz merged commit 8dc86c6 into main Nov 11, 2026
9 checks passed
@mahaloz
mahaloz deleted the multilang-go-itabs branch November 11, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant