From 3c468bb2d1418915136f9e36f044ac6672914836 Mon Sep 17 00:00:00 2001 From: Nicolas Hillegeer Date: Mon, 9 Dec 2024 08:57:01 -0800 Subject: [PATCH] internal/gocore: find new names of runtime._type and runtime.itab This change makes (e.g.) `viewcore histogram` run without crashing cores produced by current toolchains again. The output is still partially wrong, in the sense that it outputs a mixture of named and unnamed types: runtime.g size=448 []*runtime.moduledata size=24 [1+1?]*runtime.moduledata size=16 [10+6?]uint8 size=16 [1+3?]uint32 size=16 Still it's an improvement over the status quo. In Go 1.21, runtime._type was converted from its own type into an alias of internal/abi.Type. In the process, its fields became public too. For an overview of the history: - https://go.dev/cl/462995 (internal/abi: refactor (basic) type struct into one definition) - https://go.dev/cl/484856 (internal/reflectlite, runtime: move more constants and types into internal/abi) - https://go.dev/cl/488435 (runtime: redefine _type to abi.Type; add rtype for methods) In https://go.dev/cl/608475 (internal/gocore: support Go 1.22 allocation headers), this was partially reflected in viewcore, but only just enough to get basic tests for the current toolchain working. There were still references to the old `runtime._type` and `runtime.itab` (renamed `abi.ITab`), causing crashes for certain operations, like `viewcore histogram` as well as the nascent dominator tree code: panic: can't find type runtime._type [recovered] panic: can't find type runtime._type golang.org/x/debug/internal/gocore.(*Process).findType(...) /home/aktau/go/debug/internal/gocore/process.go:112 golang.org/x/debug/internal/gocore.(*Process).runtimeType2Type(0xc00026a000, 0x46d8a0, 0x4ee3a8) /home/aktau/go/debug/internal/gocore/type.go:153 +0x14d9 golang.org/x/debug/internal/gocore.(*Process).typeObject(0xc00026a000, 0x4ee3a0, 0xc000392460, {0x7b9e40, 0xc0001f4000}, 0xc00052d960) /home/aktau/go/debug/internal/gocore/type.go:636 +0xd45 golang.org/x/debug/internal/gocore.(*Process).typeHeap.func1.2(0xc000582a08?) /home/aktau/go/debug/internal/gocore/type.go:514 +0x79 golang.org/x/debug/internal/gocore.(*Process).ForEachRoot(0xc00026a000, 0xc00052db20) /home/aktau/go/debug/internal/gocore/object.go:220 +0x6a golang.org/x/debug/internal/gocore.(*Process).typeHeap.func1() /home/aktau/go/debug/internal/gocore/type.go:509 +0x179 sync.(*Once).doSlow(0x6fe760?, 0xc00026a000?) /usr/lib/google-golang/src/sync/once.go:76 +0xb4 sync.(*Once).Do(...) /usr/lib/google-golang/src/sync/once.go:67 golang.org/x/debug/internal/gocore.(*Process).typeHeap(0x3?) /home/aktau/go/debug/internal/gocore/type.go:383 +0x3b golang.org/x/debug/internal/gocore.runLT(0xc00026a000) /home/aktau/go/debug/internal/gocore/dominator.go:89 +0x58 golang.org/x/debug/internal/gocore.TestVersions.func2.1(0xc00012c680) /home/aktau/go/debug/internal/gocore/gocore_test.go:513 +0x5e Change-Id: I8559c86f03d52b114f92756caf4487df75237983 Reviewed-on: https://go-review.googlesource.com/c/debug/+/634755 Auto-Submit: Nicolas Hillegeer LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Knyszek Commit-Queue: Nicolas Hillegeer --- internal/gocore/gocore_test.go | 18 ++--- internal/gocore/type.go | 119 +++++++++++++++++++++++++++++---- 2 files changed, 111 insertions(+), 26 deletions(-) diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go index c716e8e..ec60ad9 100644 --- a/internal/gocore/gocore_test.go +++ b/internal/gocore/gocore_test.go @@ -471,6 +471,11 @@ func checkProcess(t *testing.T, p *Process) { if heapStat == nil || heapStat.Size == 0 { t.Errorf("stat[%q].Size == 0, want >0", heapName) } + + lt := runLT(p) + if !checkDominator(t, lt) { + t.Errorf("sanityCheckDominator(...) = false, want true") + } } func TestVersions(t *testing.T) { @@ -490,11 +495,6 @@ func TestVersions(t *testing.T) { t.Run(ver, func(t *testing.T) { p := loadExampleVersion(t, ver) checkProcess(t, p) - - lt := runLT(p) - if !checkDominator(t, lt) { - t.Errorf("sanityCheckDominator(...) = false, want true") - } }) } @@ -506,14 +506,6 @@ func TestVersions(t *testing.T) { t.Run(strings.Join(buildFlags, ","), func(t *testing.T) { p := loadExampleGenerated(t, buildFlags...) checkProcess(t, p) - - // TODO(aktau): Move checkDominator into checkProcess once this passes - // for loadExampleGenerated. - t.Skip(`skipping dominator check due to "panic: can't find type runtime.itab"`) - lt := runLT(p) - if !checkDominator(t, lt) { - t.Errorf("checkDominator(...) = false, want true") - } }) } }) diff --git a/internal/gocore/type.go b/internal/gocore/type.go index 9f0b0ea..b3a4b27 100644 --- a/internal/gocore/type.go +++ b/internal/gocore/type.go @@ -140,6 +140,102 @@ func readNameLen(p *Process, a core.Address) (int64, int64) { } } +// runtimeType is a thin wrapper around a runtime._type (AKA abi.Type) region +// that abstracts over the name changes seen in Go 1.21. +type runtimeType struct { + reg region + hasAbiType bool // True if Go 1.21+ +} + +// findRuntimeType finds either abi.Type (Go 1.21+) or runtime._type. +func (p *Process) findRuntimeType(a core.Address) runtimeType { + typ := runtimeType{ + reg: region{p: p, a: a, typ: p.tryFindType("abi.Type")}, + hasAbiType: true, + } + if typ.reg.typ == nil { + typ.reg.typ = p.findType("runtime._type") + typ.hasAbiType = false + } + return typ +} + +// Size_ is either abi.Type.Size_ or runtime._type.Size_. +func (r runtimeType) Size_() int64 { + if !r.hasAbiType { + return int64(r.reg.Field("size").Uintptr()) + } + return int64(r.reg.Field("Size_").Uintptr()) +} + +// TFlag is either abi.Type.TFlag or runtime._type.TFlag. +func (r runtimeType) TFlag() uint8 { + if !r.hasAbiType { + return r.reg.Field("tflag").Uint8() + } + return r.reg.Field("TFlag").Uint8() +} + +// Str is either abi.Type.Str or runtime._type.Str. +func (r runtimeType) Str() int64 { + if !r.hasAbiType { + return int64(r.reg.Field("str").Int32()) + } + return int64(r.reg.Field("Str").Int32()) +} + +// PtrBytes is either abi.Type.PtrBytes or runtime._type.PtrBytes. +func (r runtimeType) PtrBytes() int64 { + if !r.hasAbiType { + return int64(r.reg.Field("ptrdata").Uintptr()) + } + return int64(r.reg.Field("PtrBytes").Uintptr()) +} + +// Kind_ is either abi.Type.Kind_ or runtime._type.Kind_. +func (r runtimeType) Kind_() uint8 { + if !r.hasAbiType { + return r.reg.Field("kind").Uint8() + } + return r.reg.Field("Kind_").Uint8() +} + +// GCData is either abi.Type.GCData or runtime._type.GCData. +func (r runtimeType) GCData() core.Address { + if !r.hasAbiType { + return r.reg.Field("gcdata").Address() + } + return r.reg.Field("GCData").Address() +} + +// runtimeItab is a thin wrapper around a abi.ITab (used to be runtime.itab). It +// abstracts over name/package changes in Go 1.21. +type runtimeItab struct { + typ *Type + hasAbiITab bool // True if Go 1.21+ +} + +// findItab finds either abi.ITab (Go 1.21+) or runtime.itab. +func (p *Process) findItab() runtimeItab { + typ := runtimeItab{ + typ: p.tryFindType("abi.ITab"), + hasAbiITab: true, + } + if typ.typ == nil { + typ.typ = p.findType("runtime.itab") + typ.hasAbiITab = false + } + return typ +} + +// Type is the field representing either abi.ITab.Type or runtime.itab._type. +func (r runtimeItab) Type() *Field { + if !r.hasAbiITab { + return r.typ.field("_type") + } + return r.typ.field("Type") +} + // Convert the address of a runtime._type to a *Type. // The "d" is the address of the second field of an interface, used to help disambiguate types. // If "d" is 0, just return *Type and not to do the interface disambiguation. @@ -150,8 +246,8 @@ func (p *Process) runtimeType2Type(a core.Address, d core.Address) *Type { } // Read runtime._type.size - r := region{p: p, a: a, typ: p.findType("runtime._type")} - size := int64(r.Field("size").Uintptr()) + r := p.findRuntimeType(a) + size := r.Size_() // Find module this type is in. var m *module @@ -165,12 +261,12 @@ func (p *Process) runtimeType2Type(a core.Address, d core.Address) *Type { // Read information out of the runtime._type. var name string if m != nil { - x := m.types.Add(int64(r.Field("str").Int32())) + x := m.types.Add(r.Str()) i, n := readNameLen(p, x) b := make([]byte, n) p.proc.ReadAt(b, x.Add(i+1)) name = string(b) - if r.Field("tflag").Uint8()&uint8(p.rtConstants["tflagExtraStar"]) != 0 { + if r.TFlag()&uint8(p.rtConstants["tflagExtraStar"]) != 0 { name = name[1:] } } else { @@ -182,10 +278,10 @@ func (p *Process) runtimeType2Type(a core.Address, d core.Address) *Type { // Read ptr/nonptr bits ptrSize := p.proc.PtrSize() - nptrs := int64(r.Field("ptrdata").Uintptr()) / ptrSize + nptrs := int64(r.PtrBytes()) / ptrSize var ptrs []int64 - if r.Field("kind").Uint8()&uint8(p.rtConstants["kindGCProg"]) == 0 { - gcdata := r.Field("gcdata").Address() + if r.Kind_()&uint8(p.rtConstants["kindGCProg"]) == 0 { + gcdata := r.GCData() for i := int64(0); i < nptrs; i++ { if p.proc.ReadUint8(gcdata.Add(i/8))>>uint(i%8)&1 != 0 { ptrs = append(ptrs, i*ptrSize) @@ -604,11 +700,8 @@ func extractTypeFromFunctionName(method string, p *Process) *Type { // ifaceIndir reports whether t is stored indirectly in an interface value. func ifaceIndir(t core.Address, p *Process) bool { - typr := region{p: p, a: t, typ: p.findType("runtime._type")} - if typr.Field("kind").Uint8()&uint8(p.rtConstants["kindDirectIface"]) == 0 { - return true - } - return false + typr := p.findRuntimeType(t) + return typr.Kind_()&uint8(p.rtConstants["kindDirectIface"]) == 0 } // typeObject takes an address and a type for the data at that address. @@ -629,7 +722,7 @@ func (p *Process) typeObject(a core.Address, t *Type, r reader, add func(core.Ad } data := a.Add(ptrSize) if t.Kind == KindIface { - typPtr = p.proc.ReadPtr(typPtr.Add(p.findType("runtime.itab").field("_type").Off)) + typPtr = p.proc.ReadPtr(typPtr.Add(p.findItab().Type().Off)) } // TODO: for KindEface, type typPtr. It might point to the heap // if the type was allocated with reflect.