From a7c2d837d5fe920fb7a08812cebd6981d0a09035 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 24 Jun 2019 17:02:14 +0200 Subject: [PATCH] proc: add LocationCover method to BinaryInfo (#1573) Also fixes findCompileUnitForOffset which was broken in some edge cases (when looking up an offset inside the last child of the compilation unit) which don't happen in normal executables (we only look up types, and those are always direct childs of compile units). --- pkg/dwarf/dwarfbuilder/info.go | 9 +++++ pkg/proc/bininfo.go | 60 +++++++++++++++++++++++++++++----- pkg/proc/dwarf_expr_test.go | 44 +++++++++++++++++++++---- pkg/proc/types.go | 28 +++++++--------- 4 files changed, 109 insertions(+), 32 deletions(-) diff --git a/pkg/dwarf/dwarfbuilder/info.go b/pkg/dwarf/dwarfbuilder/info.go index 58cb4160..32880467 100644 --- a/pkg/dwarf/dwarfbuilder/info.go +++ b/pkg/dwarf/dwarfbuilder/info.go @@ -143,6 +143,9 @@ func (b *Builder) Attr(attr dwarf.Attr, val interface{}) { case uint16: tag.form = append(tag.form, DW_FORM_data2) binary.Write(&b.info, binary.LittleEndian, x) + case uint64: + tag.form = append(tag.form, DW_FORM_data8) + binary.Write(&b.info, binary.LittleEndian, x) case Address: tag.form = append(tag.form, DW_FORM_addr) binary.Write(&b.info, binary.LittleEndian, x) @@ -232,6 +235,12 @@ func (b *Builder) makeAbbrevTable() []byte { return abbrev.Bytes() } +func (b *Builder) AddCompileUnit(name string, lowPC uint64) dwarf.Offset { + r := b.TagOpen(dwarf.TagCompileUnit, name) + b.Attr(dwarf.AttrLowpc, lowPC) + return r +} + // AddSubprogram adds a subprogram declaration to debug_info, must call // TagClose after adding all local variables and parameters. // Will write an abbrev corresponding to a DW_TAG_subprogram, followed by a diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 16d058c1..9bc0752b 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -61,9 +61,11 @@ type BinaryInfo struct { packageMap map[string]string frameEntries frame.FrameDescriptionEntries - compileUnits []*compileUnit - types map[string]dwarfRef - packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address + + compileUnits []*compileUnit // compileUnits is sorted by increasing DWARF offset + + types map[string]dwarfRef + packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address gStructOffset uint64 @@ -112,7 +114,7 @@ type compileUnit struct { optimized bool // this compile unit is optimized producer string // producer attribute - startOffset, endOffset dwarf.Offset // interval of offsets contained in this compile unit + offset dwarf.Offset // offset of the entry describing the compile unit image *Image // parent image of this compilation unit. } @@ -608,6 +610,45 @@ func (bi *BinaryInfo) locationExpr(entry reader.Entry, attr dwarf.Attr, pc uint6 return instr, descr.String(), nil } +// LocationCovers returns the list of PC addresses that is covered by the +// location attribute 'attr' of entry 'entry'. +func (bi *BinaryInfo) LocationCovers(entry *dwarf.Entry, attr dwarf.Attr) ([][2]uint64, error) { + a := entry.Val(attr) + if a == nil { + return nil, fmt.Errorf("attribute %s not found", attr) + } + if _, isblock := a.([]byte); isblock { + return [][2]uint64{[2]uint64{0, ^uint64(0)}}, nil + } + + off, ok := a.(int64) + if !ok { + return nil, fmt.Errorf("attribute %s of unsupported type %T", attr, a) + } + cu := bi.findCompileUnitForOffset(entry.Offset) + if cu == nil { + return nil, errors.New("could not find compile unit") + } + + image := cu.image + base := cu.lowPC + if image == nil || image.loclist.data == nil { + return nil, errors.New("malformed executable") + } + + r := [][2]uint64{} + image.loclist.Seek(int(off)) + var e loclistEntry + for image.loclist.Next(&e) { + if e.BaseAddressSelection() { + base = e.highpc + continue + } + r = append(r, [2]uint64{e.lowpc + base, e.highpc + base}) + } + return r, nil +} + // Location returns the location described by attribute attr of entry. // This will either be an int64 address or a slice of Pieces for locations // that don't correspond to a single memory address (registers, composite @@ -662,12 +703,13 @@ func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit { } func (bi *BinaryInfo) findCompileUnitForOffset(off dwarf.Offset) *compileUnit { - for _, cu := range bi.compileUnits { - if off >= cu.startOffset && off < cu.endOffset { - return cu - } + i := sort.Search(len(bi.compileUnits), func(i int) bool { + return bi.compileUnits[i].offset >= off + }) + if i > 0 { + i-- } - return nil + return bi.compileUnits[i] } // Producer returns the value of DW_AT_producer. diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index abcf8de1..b2f11bed 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -21,7 +21,7 @@ import ( const defaultCFA = 0xc420051d00 -func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) *proc.BinaryInfo { +func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) (*proc.BinaryInfo, *dwarf.Data) { abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build() assertNoError(err, t, "dwarfbuilder.Build") dwdata, err := dwarf.New(abbrev, aranges, frame, info, line, pubnames, ranges, str) @@ -30,7 +30,7 @@ func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) *proc.BinaryInfo { bi := proc.NewBinaryInfo("linux", "amd64") bi.LoadImageFromData(dwdata, frame, line, loc) - return bi + return bi, dwdata } // fakeMemory implements proc.MemoryReadWriter by reading from a byte slice. @@ -114,7 +114,7 @@ func TestDwarfExprRegisters(t *testing.T) { dwb.AddVariable("c", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_regx, int(1))) dwb.TagClose() - bi := fakeBinaryInfo(t, dwb) + bi, _ := fakeBinaryInfo(t, dwb) mainfn := bi.LookupFunc["main.main"] @@ -166,7 +166,7 @@ func TestDwarfExprComposite(t *testing.T) { dwb.AddVariable("n", intoff, dwarfbuilder.LocationBlock(op.DW_OP_reg3)) dwb.TagClose() - bi := fakeBinaryInfo(t, dwb) + bi, _ := fakeBinaryInfo(t, dwb) mainfn := bi.LookupFunc["main.main"] @@ -206,7 +206,7 @@ func TestDwarfExprLoclist(t *testing.T) { }) dwb.TagClose() - bi := fakeBinaryInfo(t, dwb) + bi, _ := fakeBinaryInfo(t, dwb) mainfn := bi.LookupFunc["main.main"] @@ -241,7 +241,7 @@ func TestIssue1419(t *testing.T) { dwb.AddVariable("a", sliceoff, dwarfbuilder.LocationBlock(op.DW_OP_reg2, op.DW_OP_piece, uint(8), op.DW_OP_reg2, op.DW_OP_piece, uint(8), op.DW_OP_reg2, op.DW_OP_piece, uint(8))) dwb.TagClose() - bi := fakeBinaryInfo(t, dwb) + bi, _ := fakeBinaryInfo(t, dwb) mainfn := bi.LookupFunc["main.main"] @@ -260,3 +260,35 @@ func TestIssue1419(t *testing.T) { t.Fatalf("wrong unreadable reason for variable 'a': %v", va.Unreadable) } } + +func TestLocationCovers(t *testing.T) { + const before = 0x1234 + const after = 0x4321 + + dwb := dwarfbuilder.New() + + uint16off := dwb.AddBaseType("uint16", dwarfbuilder.DW_ATE_unsigned, 2) + + dwb.AddCompileUnit("main", 0x0) + dwb.AddSubprogram("main.main", 0x40100, 0x41000) + aOff := dwb.AddVariable("a", uint16off, []dwarfbuilder.LocEntry{ + {0x40100, 0x40700, dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa)}, + {0x40700, 0x41000, dwarfbuilder.LocationBlock(op.DW_OP_call_frame_cfa, op.DW_OP_consts, int(2), op.DW_OP_plus)}, + }) + dwb.TagClose() + dwb.TagClose() + + bi, dwdata := fakeBinaryInfo(t, dwb) + + dwrdr := dwdata.Reader() + dwrdr.Seek(aOff) + aEntry, err := dwrdr.Next() + assertNoError(err, t, "reading 'a' entry") + ranges, err := bi.LocationCovers(aEntry, dwarf.AttrLocation) + assertNoError(err, t, "LocationCovers") + t.Logf("%x", ranges) + if fmt.Sprintf("%x", ranges) != "[[40100 40700] [40700 41000]]" { + t.Error("wrong value returned by LocationCover") + } + +} diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 63b304fc..b560d29e 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -164,11 +164,11 @@ func (v functionsDebugInfoByEntry) Len() int { return len(v) } func (v functionsDebugInfoByEntry) Less(i, j int) bool { return v[i].Entry < v[j].Entry } func (v functionsDebugInfoByEntry) Swap(i, j int) { v[i], v[j] = v[j], v[i] } -type compileUnitsByLowpc []*compileUnit +type compileUnitsByOffset []*compileUnit -func (v compileUnitsByLowpc) Len() int { return len(v) } -func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].lowPC < v[j].lowPC } -func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } +func (v compileUnitsByOffset) Len() int { return len(v) } +func (v compileUnitsByOffset) Less(i int, j int) bool { return v[i].offset < v[j].offset } +func (v compileUnitsByOffset) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } type packageVarsByAddr []packageVar @@ -223,7 +223,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg cu := &compileUnit{} cu.image = image cu.entry = entry - cu.startOffset = entry.Offset + cu.offset = entry.Offset if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage { cu.isgo = true } @@ -264,7 +264,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg } bi.compileUnits = append(bi.compileUnits, cu) if entry.Children { - cu.endOffset = bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu) + bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu) } case dwarf.TagPartialUnit: @@ -276,7 +276,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg } } - sort.Sort(compileUnitsByLowpc(bi.compileUnits)) + sort.Sort(compileUnitsByOffset(bi.compileUnits)) sort.Sort(functionsDebugInfoByEntry(bi.Functions)) sort.Sort(packageVarsByAddr(bi.packageVars)) @@ -302,19 +302,15 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg } // loadDebugInfoMapsCompileUnit loads entry from a single compile unit. -func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) dwarf.Offset { - var lastOffset dwarf.Offset +func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { image.setLoadError("error reading debug_info: %v", err) - return lastOffset + 1 - } - if entry.Tag != 0 { - lastOffset = entry.Offset + return } switch entry.Tag { case 0: - return lastOffset + 1 + return case dwarf.TagImportedUnit: bi.loadDebugInfoMapsImportedUnit(entry, ctxt, image, cu) reader.SkipChildren() @@ -409,7 +405,7 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex entry, err = reader.Next() if err != nil { image.setLoadError("error reading debug_info: %v", err) - return 0 + return } if entry.Tag == 0 { break @@ -441,8 +437,6 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex } } } - - return lastOffset + 1 } // loadDebugInfoMapsImportedUnit loads entries into cu from the partial unit