From 0461af8392a910c1a2acd238635c7263c6c5d13d Mon Sep 17 00:00:00 2001 From: aarzilli Date: Fri, 17 Aug 2018 08:17:22 +0200 Subject: [PATCH] proc: fix type of some struct global variables Normally variables that have a named struct as a type will get a typedef entry as their type, sometimes however the Go linker will decide to use the DW_TAG_structure_type entry instead. For consistency always wrap a struct type into a typedef when we are creating a new variables (see comment in newVariable for exceptions). This fixes a bug where it would be impossible to call methods on a global variable. --- _fixtures/fncall.go | 4 +++- pkg/proc/bininfo.go | 11 +++++++++++ pkg/proc/eval.go | 2 +- pkg/proc/types.go | 35 ++++++++++++---------------------- pkg/proc/variables.go | 25 ++++++++++++++++++++++++ service/test/variables_test.go | 2 ++ 6 files changed, 54 insertions(+), 25 deletions(-) diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go index d451ec15..1100b542 100644 --- a/_fixtures/fncall.go +++ b/_fixtures/fncall.go @@ -65,6 +65,8 @@ func makeclos(pa *astruct) func(int) string { } } +var ga astruct + func main() { one, two := 1, 2 intslice := []int{1, 2, 3} @@ -86,5 +88,5 @@ func main() { runtime.Breakpoint() call1(one, two) fn2clos(2) - fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil) + fmt.Println(one, two, zero, callpanic, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga) } diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 4c097196..91c8677a 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -88,6 +88,8 @@ type compileUnit struct { concreteInlinedFns []inlinedFn // list of concrete inlined functions within this compile unit optimized bool // this compile unit is optimized producer string // producer attribute + + startOffset, endOffset dwarf.Offset // interval of offsets contained in this compile unit } type partialUnitConstant struct { @@ -495,6 +497,15 @@ func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit { return nil } +func (bi *BinaryInfo) findCompileUnitForOffset(off dwarf.Offset) *compileUnit { + for _, cu := range bi.compileUnits { + if off >= cu.startOffset && off < cu.endOffset { + return cu + } + } + return nil +} + func (bi *BinaryInfo) Producer() string { for _, cu := range bi.compileUnits { if cu.isgo && cu.producer != "" { diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index be0216a6..f734f4ee 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -1266,7 +1266,7 @@ func (v *Variable) mapAccess(idx *Variable) (*Variable, error) { } if first { first = false - if err := idx.isType(key.DwarfType, key.Kind); err != nil { + if err := idx.isType(key.RealType, key.Kind); err != nil { return nil, err } } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index eb24d069..ba2b530b 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -197,19 +197,26 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou var pu *partialUnit = nil var partialUnits = make(map[dwarf.Offset]*partialUnit) abstractOriginNameTable := make(map[dwarf.Offset]string) + var lastOffset dwarf.Offset + outer: for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { break } + lastOffset = entry.Offset switch entry.Tag { case dwarf.TagCompileUnit: if pu != nil { partialUnits[pu.entry.Offset] = pu pu = nil } + if cu != nil { + cu.endOffset = entry.Offset + } cu = &compileUnit{} cu.entry = entry + cu.startOffset = entry.Offset if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage { cu.isgo = true } @@ -439,6 +446,11 @@ outer: } } } + + if cu != nil { + cu.endOffset = lastOffset + 1 + } + sort.Sort(compileUnitsByLowpc(bi.compileUnits)) sort.Sort(functionsDebugInfoByEntry(bi.Functions)) sort.Sort(packageVarsByAddr(bi.packageVars)) @@ -520,29 +532,6 @@ func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader. if _, ok := bi.runtimeTypeToDIE[off]; !ok { bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} } - return - } - - if entry.Tag != dwarf.TagTypedef { - return - } - - // For named structs the compiler will emit a TagStructType entry and a - // TagTypedef entry. The AttrGoRuntimeType is set on the TagStructType - // entry but we prefer to use the typedef instead, to make interface - // values consistent with other variables. - - rtypOff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset) - if !ok { - return - } - ardr.Seek(rtypOff) - rentry, _ := ardr.Next() - if rentry == nil { - return - } - if off, ok := rentry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { - bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} } } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index ec89a044..f69fed65 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -202,6 +202,31 @@ func (v *Variable) newVariable(name string, addr uintptr, dwarfType godwarf.Type } func newVariable(name string, addr uintptr, dwarfType godwarf.Type, bi *BinaryInfo, mem MemoryReadWriter) *Variable { + if styp, isstruct := dwarfType.(*godwarf.StructType); isstruct && !strings.Contains(styp.Name, "<") && !strings.Contains(styp.Name, "{") { + // For named structs the compiler will emit a DW_TAG_structure_type entry + // and a DW_TAG_typedef entry. + // + // Normally variables refer to the typedef entry but sometimes global + // variables will refer to the struct entry incorrectly. + // Also the runtime type offset resolution (runtimeTypeToDIE) will return + // the struct entry directly. + // + // In both cases we prefer to have a typedef type for consistency's sake. + // + // So we wrap all struct types into a fake typedef type except for: + // a. types not defined by go + // b. anonymous struct types (they contain the '{' character) + // c. Go internal struct types used to describe maps (they contain the '<' + // character). + cu := bi.findCompileUnitForOffset(dwarfType.Common().Offset) + if cu != nil && cu.isgo { + dwarfType = &godwarf.TypedefType{ + CommonType: *(dwarfType.Common()), + Type: dwarfType, + } + } + } + v := &Variable{ Name: name, Addr: addr, diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 411af69e..b0ad4a38 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -1112,6 +1112,8 @@ func TestCallFunction(t *testing.T) { {`fn2ptrmeth(14)`, []string{`:string:"14 - 6 = 8"`}, nil}, // indirect call of func value / set to pointer method {"fn2nil()", nil, errors.New("nil pointer dereference")}, + + {"ga.PRcvr(2)", []string{`:string:"2 - 0 = 2"`}, nil}, } withTestProcess("fncall", t, func(p proc.Process, fixture protest.Fixture) {