Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proc: fix runtime type handling for Go 1.21 #3370

Merged
merged 1 commit into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions _scripts/rtype-out.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type bmap struct {
}

type eface struct {
_type *_type
_type *_type|*internal/abi.Type
data unsafe.Pointer
}

Expand Down Expand Up @@ -50,7 +50,7 @@ type iface struct {
}

type itab struct {
_type *_type
_type *_type|*internal/abi.Type
}

type moduledata struct {
Expand Down
12 changes: 10 additions & 2 deletions pkg/proc/bininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,13 @@ func (bi *BinaryInfo) typeToImage(typ godwarf.Type) *Image {
return bi.Images[typ.Common().Index]
}

func (bi *BinaryInfo) runtimeTypeTypename() string {
if goversion.ProducerAfterOrEqual(bi.Producer(), 1, 21) {
return "internal/abi.Type"
}
return "runtime._type"
}

var errBinaryInfoClose = errors.New("multiple errors closing executable files")

// Close closes all internal readers.
Expand Down Expand Up @@ -2130,10 +2137,11 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB
if fn != nil && fn.cu.image == image {
tree, err := image.getDwarfTree(fn.offset)
if err == nil {
tree.Children, err = regabiMallocgcWorkaround(bi)
children, err := regabiMallocgcWorkaround(bi)
if err != nil {
bi.logger.Errorf("could not patch runtime.mallogc: %v", err)
bi.logger.Errorf("could not patch runtime.mallocgc: %v", err)
} else {
tree.Children = children
image.runtimeMallocgcTree = tree
}
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/proc/fncall.go
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,8 @@ func (e fakeEntry) AttrField(attr dwarf.Attr) *dwarf.Field {
}

func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
ptrToRuntimeType := "*" + bi.runtimeTypeTypename()

var err1 error

t := func(name string) godwarf.Type {
Expand Down Expand Up @@ -1298,15 +1300,15 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) {
case "amd64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.AMD64_Rax, false),
m("typ", t("*runtime._type"), regnum.AMD64_Rbx, false),
m("typ", t(ptrToRuntimeType), regnum.AMD64_Rbx, false),
m("needzero", t("bool"), regnum.AMD64_Rcx, false),
m("~r1", t("unsafe.Pointer"), regnum.AMD64_Rax, true),
}
return r, err1
case "arm64":
r := []*godwarf.Tree{
m("size", t("uintptr"), regnum.ARM64_X0, false),
m("typ", t("*runtime._type"), regnum.ARM64_X0+1, false),
m("typ", t(ptrToRuntimeType), regnum.ARM64_X0+1, false),
m("needzero", t("bool"), regnum.ARM64_X0+2, false),
m("~r1", t("unsafe.Pointer"), regnum.ARM64_X0, true),
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4647,7 +4647,10 @@ func TestCgoStacktrace2(t *testing.T) {
// If a panic happens during cgo execution the stacktrace should show the C
// function that caused the problem.
withTestProcess("cgosigsegvstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
grp.Continue()
err := grp.Continue()
if _, exited := err.(proc.ErrProcessExited); exited {
t.Fatal("process exited")
}
frames, err := proc.ThreadStacktrace(p.CurrentThread(), 100)
assertNoError(err, t, "Stacktrace()")
logStacktrace(t, p, frames)
Expand Down Expand Up @@ -5099,6 +5102,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) {
candg := []*proc.G{}
bestg := []*proc.G{}
for _, g := range gs {
t.Logf("stacktracing goroutine %d (%v)\n", g.ID, g.CurrentLoc)
frames, err := g.Stacktrace(20, 0)
assertNoError(err, t, "Stacktrace")
for _, frame := range frames {
Expand Down
11 changes: 8 additions & 3 deletions pkg/proc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind
if rtdie.kind == -1 {
if kindField := _type.loadFieldNamed("kind"); kindField != nil && kindField.Value != nil {
rtdie.kind, _ = constant.Int64Val(kindField.Value)
} else if kindField := _type.loadFieldNamed("Kind_"); kindField != nil && kindField.Value != nil {
rtdie.kind, _ = constant.Int64Val(kindField.Value)
}
}
return typ, rtdie.kind, nil
Expand All @@ -146,7 +148,7 @@ func resolveParametricType(bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type,
if err != nil {
return ptyp.TypedefType.Type, err
}
runtimeType, err := bi.findType("runtime._type")
runtimeType, err := bi.findType(bi.runtimeTypeTypename())
if err != nil {
return ptyp.TypedefType.Type, err
}
Expand Down Expand Up @@ -189,13 +191,16 @@ func dwarfToRuntimeType(bi *BinaryInfo, mem MemoryReadWriter, typ godwarf.Type)

typeAddr = uint64(md.types) + off

rtyp, err := bi.findType("runtime._type")
rtyp, err := bi.findType(bi.runtimeTypeTypename())
if err != nil {
return 0, 0, false, err
}
_type := newVariable("", typeAddr, rtyp, bi, mem)
kindv := _type.loadFieldNamed("kind")
if kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
kindv = _type.loadFieldNamed("Kind_")
}
if kindv == nil || kindv.Unreadable != nil || kindv.Kind != reflect.Uint {
return 0, 0, false, fmt.Errorf("unreadable interface type: %v", kindv.Unreadable)
}
typeKind, _ = constant.Uint64Val(kindv.Value)
Expand Down
48 changes: 28 additions & 20 deletions pkg/proc/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,7 @@ func newVariable(name string, addr uint64, dwarfType godwarf.Type, bi *BinaryInf
v.stride = 1
v.fieldType = &godwarf.UintType{BasicType: godwarf.BasicType{CommonType: godwarf.CommonType{ByteSize: 1, Name: "byte", ReflectKind: reflect.Uint8}, BitSize: 8, BitOffset: 0}}
if v.Addr != 0 {
v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.bi.Arch, v.Addr)
v.Base, v.Len, v.Unreadable = readStringInfo(v.mem, v.bi.Arch, v.Addr, t)
}
case *godwarf.SliceType:
v.Kind = reflect.Slice
Expand Down Expand Up @@ -1455,30 +1455,38 @@ func convertToEface(srcv, dstv *Variable) error {
return dstv.writeEmptyInterface(typeAddr, srcv)
}

func readStringInfo(mem MemoryReadWriter, arch *Arch, addr uint64) (uint64, int64, error) {
func readStringInfo(mem MemoryReadWriter, arch *Arch, addr uint64, typ *godwarf.StringType) (uint64, int64, error) {
// string data structure is always two ptrs in size. Addr, followed by len
// http://research.swtch.com/godata

mem = cacheMemory(mem, addr, arch.PtrSize()*2)

// read len
strlen, err := readIntRaw(mem, addr+uint64(arch.PtrSize()), int64(arch.PtrSize()))
if err != nil {
return 0, 0, fmt.Errorf("could not read string len %s", err)
}
if strlen < 0 {
return 0, 0, fmt.Errorf("invalid length: %d", strlen)
}
var strlen int64
var outaddr uint64
var err error

// read addr
addr, err = readUintRaw(mem, addr, int64(arch.PtrSize()))
if err != nil {
return 0, 0, fmt.Errorf("could not read string pointer %s", err)
}
if addr == 0 {
return 0, 0, nil
for _, field := range typ.StructType.Field {
switch field.Name {
case "len":
strlen, err = readIntRaw(mem, addr+uint64(field.ByteOffset), int64(arch.PtrSize()))
if err != nil {
return 0, 0, fmt.Errorf("could not read string len %s", err)
}
if strlen < 0 {
return 0, 0, fmt.Errorf("invalid length: %d", strlen)
}
case "str":
outaddr, err = readUintRaw(mem, addr+uint64(field.ByteOffset), int64(arch.PtrSize()))
if err != nil {
return 0, 0, fmt.Errorf("could not read string pointer %s", err)
}
if addr == 0 {
return 0, 0, nil
}
}
}
return addr, strlen, nil

return outaddr, strlen, nil
}

func readStringValue(mem MemoryReadWriter, addr uint64, strlen int64, cfg LoadConfig) (string, error) {
Expand Down Expand Up @@ -2248,7 +2256,7 @@ func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {

// +rtype -field iface.tab *itab
// +rtype -field iface.data unsafe.Pointer
// +rtype -field eface._type *_type
// +rtype -field eface._type *_type|*internal/abi.Type
// +rtype -field eface.data unsafe.Pointer

for _, f := range ityp.Field {
Expand All @@ -2259,7 +2267,7 @@ func (v *Variable) readInterface() (_type, data *Variable, isnil bool) {
isnil = tab.Addr == 0
if !isnil {
var err error
_type, err = tab.structMember("_type") // +rtype *_type
_type, err = tab.structMember("_type") // +rtype *_type|*internal/abi.Type
if err != nil {
v.Unreadable = fmt.Errorf("invalid interface type: %v", err)
return
Expand Down
3 changes: 2 additions & 1 deletion pkg/proc/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func TestSetVariable(t *testing.T) {
assertNoError(err, t, "EvalVariable()")
assertVariable(t, variable, varTest{tc.name, true, tc.startVal, "", tc.typ, nil})

assertNoError(setVariable(p, tc.name, tc.expr), t, "SetVariable()")
assertNoError(setVariable(p, tc.name, tc.expr), t, fmt.Sprintf("SetVariable(%q, %q)", tc.name, tc.expr))

variable, err = evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
assertNoError(err, t, "EvalVariable()")
Expand Down Expand Up @@ -856,6 +856,7 @@ func TestEvalExpression(t *testing.T) {
assertNoError(grp.Continue(), t, "Continue() returned an error")
for i, tc := range testcases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("%q", tc.name)
variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig)
if err != nil && err.Error() == "evaluating methods not supported on this version of Go" {
// this type of eval is unsupported with the current version of Go.
Expand Down