Skip to content

Commit

Permalink
internal/gocore: find new names of runtime._type and runtime.itab
Browse files Browse the repository at this point in the history
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 <aktau@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
Commit-Queue: Nicolas Hillegeer <aktau@google.com>
  • Loading branch information
aktau authored and gopherbot committed Dec 10, 2024
1 parent acddda4 commit 3c468bb
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 26 deletions.
18 changes: 5 additions & 13 deletions internal/gocore/gocore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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")
}
})
}

Expand All @@ -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")
}
})
}
})
Expand Down
119 changes: 106 additions & 13 deletions internal/gocore/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down

0 comments on commit 3c468bb

Please sign in to comment.