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).
This commit is contained in:
Alessandro Arzilli 2019-06-24 17:02:14 +02:00 committed by Derek Parker
parent 79ad269bbb
commit a7c2d837d5
4 changed files with 109 additions and 32 deletions

@ -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

@ -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.

@ -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")
}
}

@ -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