internal/gocore: disambiguate type candidates by object size Match the object size when there are multiple candidates for the pointers to structs with the same struct name and same package name. Also, allow `-` in the package path. Change-Id: Ie3b75aa4ab26283bb94c4c0c40d5c196656caf5f GitHub-Last-Rev: 5b48ab1a6716dfc805f13306fa36f4fc1c018583 GitHub-Pull-Request: golang/debug#11 Reviewed-on: https://go-review.googlesource.com/c/debug/+/419176 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Keith Randall <khr@google.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/internal/gocore/dwarf.go b/internal/gocore/dwarf.go index 0fc1d44..463b096 100644 --- a/internal/gocore/dwarf.go +++ b/internal/gocore/dwarf.go
@@ -348,7 +348,7 @@ } } -var pathRegexp = regexp.MustCompile(`([\w.]+/)+\w+`) +var pathRegexp = regexp.MustCompile(`([\w.-]+/)+\w+`) func stripPackagePath(name string) string { // The runtime uses just package names. DWARF uses whole package paths.
diff --git a/internal/gocore/gocore_test.go b/internal/gocore/gocore_test.go index ece0b56..5f8bfd3 100644 --- a/internal/gocore/gocore_test.go +++ b/internal/gocore/gocore_test.go
@@ -277,3 +277,78 @@ loadExampleVersion(t, "1.16.zip") loadExampleVersion(t, "1.17.zip") } + +func loadZipCore(t *testing.T, name string) *Process { + t.Helper() + if runtime.GOOS == "android" { + t.Skip("skipping test on android") + } + // Make temporary directory. + dir, err := ioutil.TempDir("", name+"_") + if err != nil { + t.Fatalf("can't make temp directory: %s", err) + } + defer os.RemoveAll(dir) + + // Unpack bin file and core file into directory. + unzip(t, filepath.Join("testdata", name+".zip"), dir) + + exe := filepath.Join(dir, name) + file := filepath.Join(dir, "core") + c, err := core.Core(file, dir, exe) + if err != nil { + t.Fatalf("can't load test core file: %s", err) + } + p, err := Core(c) + if err != nil { + t.Fatalf("can't parse Go core: %s", err) + } + return p +} + +func TestRuntimeTypes(t *testing.T) { + p := loadZipCore(t, "runtimetype") + // Check the type of a few objects. + for _, s := range [...]struct { + addr core.Address + size int64 + kind Kind + name string + repeat int64 + }{ + {0xc00018e000, 16, KindStruct, "example.com/m/path-a/pkg.T1", 1}, + {0xc00018e010, 16, KindStruct, "example.com/m/path-a/pkg.T2", 1}, + {0xc000190000, 32, KindStruct, "example.com/m/path-b/pkg.T1", 1}, + {0xc000190020, 32, KindStruct, "example.com/m/path-b/pkg.T2", 1}, + } { + x, i := p.FindObject(s.addr) + if x == 0 { + t.Errorf("can't find object at %x", s.addr) + continue + } + if i != 0 { + t.Errorf("offset(%x)=%d, want 0", s.addr, i) + } + if p.Size(x) != s.size { + t.Errorf("size(%x)=%d, want %d", s.addr, p.Size(x), s.size) + } + typ, repeat := p.Type(x) + if typ.Kind != s.kind { + t.Errorf("kind(%x)=%s, want %s", s.addr, typ.Kind, s.kind) + } + if typ.Name != s.name { + t.Errorf("name(%x)=%s, want %s", s.addr, typ.Name, s.name) + } + if repeat != s.repeat { + t.Errorf("repeat(%x)=%d, want %d", s.addr, repeat, s.repeat) + } + + y, i := p.FindObject(s.addr + 1) + if y != x { + t.Errorf("can't find object at %x", s.addr+1) + } + if i != 1 { + t.Errorf("offset(%x)=%d, want i", s.addr, i) + } + } +}
diff --git a/internal/gocore/testdata/README b/internal/gocore/testdata/README index af06176..fe2de04 100644 --- a/internal/gocore/testdata/README +++ b/internal/gocore/testdata/README
@@ -30,3 +30,16 @@ Then move the 1.12.zip to this directory. Add a new test to TestVersions in ../gocore_test.go. + +## runtimetype + +This directory also contains the source code to generate the runtimetype core, +which is used to test getting runtime type for a interface. + +steps for generate runtimetype core: + +cd testdata/runtimetype +go build -o runtimetype . +ulimit -c unlimited +GOTRACEBACK=crash ./runtimetype +zip runtimetype.zip runtimetype core
diff --git a/internal/gocore/testdata/runtimetype.zip b/internal/gocore/testdata/runtimetype.zip new file mode 100644 index 0000000..f6a988a --- /dev/null +++ b/internal/gocore/testdata/runtimetype.zip Binary files differ
diff --git a/internal/gocore/testdata/runtimetype/go.mod b/internal/gocore/testdata/runtimetype/go.mod new file mode 100644 index 0000000..594b4df --- /dev/null +++ b/internal/gocore/testdata/runtimetype/go.mod
@@ -0,0 +1,3 @@ +module example.com/m + +go 1.17
diff --git a/internal/gocore/testdata/runtimetype/main.go b/internal/gocore/testdata/runtimetype/main.go new file mode 100644 index 0000000..df647f9 --- /dev/null +++ b/internal/gocore/testdata/runtimetype/main.go
@@ -0,0 +1,14 @@ +package main + +import ( + pkga "example.com/m/path-a/pkg" + pkgb "example.com/m/path-b/pkg" +) + +var global []interface{} + +func main() { + global = append(global, pkga.NewIfaceDirect(), pkga.NewIfaceInDirect(), pkgb.NewIfaceDirect(), pkgb.NewIfaceInDirect()) + + _ = *(*int)(nil) +}
diff --git a/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go b/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go new file mode 100644 index 0000000..e0dfa4f --- /dev/null +++ b/internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go
@@ -0,0 +1,32 @@ +package pkg + +type T1 struct { + f1 uint64 + f2 uint64 +} + +type T2 struct { + f1 uint64 +} + +type IfaceDirect interface { + M1() +} + +type IfaceInDirect interface { + M2() +} + +func (t *T1) M1() { +} + +func (t T2) M2() { +} + +func NewIfaceDirect() IfaceDirect { + return &T1{100, 10} +} + +func NewIfaceInDirect() IfaceInDirect { + return T2{200} +}
diff --git a/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go b/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go new file mode 100644 index 0000000..f31a5ff --- /dev/null +++ b/internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go
@@ -0,0 +1,36 @@ +package pkg + +type T1 struct { + f1 uint64 + f2 uint64 + f3 uint64 + f4 uint64 +} + +type T2 struct { + f1 uint64 + f2 uint64 + f3 uint64 +} + +type IfaceDirect interface { + M1() +} + +type IfaceInDirect interface { + M2() +} + +func (t *T1) M1() { +} + +func (t T2) M2() { +} + +func NewIfaceDirect() IfaceDirect { + return &T1{1000, 0, 0, 0} +} + +func NewIfaceInDirect() IfaceInDirect { + return T2{2000, 0, 0} +}
diff --git a/internal/gocore/type.go b/internal/gocore/type.go index 5a944c7..0397449 100644 --- a/internal/gocore/type.go +++ b/internal/gocore/type.go
@@ -99,7 +99,7 @@ if x == 0 { return nil } - return p.runtimeType2Type(x) + return p.runtimeType2Type(x, a.Add(p.proc.PtrSize())) case KindIface: x := p.proc.ReadPtr(a) if x == 0 { @@ -107,7 +107,7 @@ } // Read type out of itab. x = p.proc.ReadPtr(x.Add(p.proc.PtrSize())) - return p.runtimeType2Type(x) + return p.runtimeType2Type(x, a.Add(p.proc.PtrSize())) } } @@ -132,8 +132,9 @@ } // 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. // Guaranteed to return a non-nil *Type. -func (p *Process) runtimeType2Type(a core.Address) *Type { +func (p *Process) runtimeType2Type(a core.Address, d core.Address) *Type { if t := p.runtimeMap[a]; t != nil { return t } @@ -193,6 +194,28 @@ candidates = append(candidates, t) } } + // There may be multiple candidates, when they are the pointers to the same type name, + // in the same package name, but in the different package paths. eg. path-1/pkg.Foo and path-2/pkg.Foo. + // Match the object size may be a proper choice, just for try best, since we have no other choices. + // If the interface is of type T, for direct interfaces, that pointer points to a T.Elem. + if len(candidates) > 1 && !ifaceIndir(a, p) { + ptr := p.proc.ReadPtr(d) + obj, off := p.FindObject(ptr) + // only usefull while it point to the head of an object, + // otherwise, the GC object size should bigger than the size of the type. + if obj != 0 && off == 0 { + sz := p.Size(obj) + var tmp []*Type + for _, t := range candidates { + if t.Elem != nil && t.Elem.Size == sz { + tmp = append(tmp, t) + } + } + if len(tmp) > 0 { + candidates = tmp + } + } + } var t *Type if len(candidates) > 0 { // If a runtime type matches more than one DWARF type, @@ -569,6 +592,15 @@ return nil } +// 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 +} + // typeObject takes an address and a type for the data at that address. // For each pointer it finds in the memory at that address, it calls add with the pointer // and the type + repeat count of the thing that it points to. @@ -591,9 +623,8 @@ } // TODO: for KindEface, type typPtr. It might point to the heap // if the type was allocated with reflect. - typ := p.runtimeType2Type(typPtr) - typr := region{p: p, a: typPtr, typ: p.findType("runtime._type")} - if typr.Field("kind").Uint8()&uint8(p.rtConstants["kindDirectIface"]) == 0 { + typ := p.runtimeType2Type(typPtr, data) + if ifaceIndir(typPtr, p) { // Indirect interface: the interface introduced a new // level of indirection, not reflected in the type. // Read through it.