Skip to content
2 changes: 1 addition & 1 deletion internal/gocore/dwarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ func runtimeName(dt dwarf.Type) string {
}
}

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.
Expand Down
75 changes: 75 additions & 0 deletions internal/gocore/gocore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,78 @@ func TestVersions(t *testing.T) {
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)
}
}
}
13 changes: 13 additions & 0 deletions internal/gocore/testdata/README
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,16 @@ zip 1.12.zip /tmp/coretest/core /tmp/coretest/test // use your version number

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
Binary file added internal/gocore/testdata/runtimetype.zip
Binary file not shown.
3 changes: 3 additions & 0 deletions internal/gocore/testdata/runtimetype/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module example.com/m

go 1.17
14 changes: 14 additions & 0 deletions internal/gocore/testdata/runtimetype/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
32 changes: 32 additions & 0 deletions internal/gocore/testdata/runtimetype/path-a/pkg/pkg.go
Original file line number Diff line number Diff line change
@@ -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}
}
36 changes: 36 additions & 0 deletions internal/gocore/testdata/runtimetype/path-b/pkg/pkg.go
Original file line number Diff line number Diff line change
@@ -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}
}
43 changes: 37 additions & 6 deletions internal/gocore/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,15 @@ func (p *Process) DynamicType(t *Type, a core.Address) *Type {
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 {
return nil
}
// 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()))
}
}

Expand All @@ -132,8 +132,9 @@ func readNameLen(p *Process, a core.Address) (int64, int64) {
}

// 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
}
Expand Down Expand Up @@ -193,6 +194,28 @@ func (p *Process) runtimeType2Type(a core.Address) *Type {
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,
Expand Down Expand Up @@ -569,6 +592,15 @@ func extractTypeFromFunctionName(method string, p *Process) *Type {
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.
Expand All @@ -591,9 +623,8 @@ func (p *Process) typeObject(a core.Address, t *Type, r reader, add func(core.Ad
}
// 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.
Expand Down