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: case uint16:
tag.form = append(tag.form, DW_FORM_data2) tag.form = append(tag.form, DW_FORM_data2)
binary.Write(&b.info, binary.LittleEndian, x) 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: case Address:
tag.form = append(tag.form, DW_FORM_addr) tag.form = append(tag.form, DW_FORM_addr)
binary.Write(&b.info, binary.LittleEndian, x) binary.Write(&b.info, binary.LittleEndian, x)
@ -232,6 +235,12 @@ func (b *Builder) makeAbbrevTable() []byte {
return abbrev.Bytes() 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 // AddSubprogram adds a subprogram declaration to debug_info, must call
// TagClose after adding all local variables and parameters. // TagClose after adding all local variables and parameters.
// Will write an abbrev corresponding to a DW_TAG_subprogram, followed by a // Will write an abbrev corresponding to a DW_TAG_subprogram, followed by a

@ -61,9 +61,11 @@ type BinaryInfo struct {
packageMap map[string]string packageMap map[string]string
frameEntries frame.FrameDescriptionEntries frameEntries frame.FrameDescriptionEntries
compileUnits []*compileUnit
types map[string]dwarfRef compileUnits []*compileUnit // compileUnits is sorted by increasing DWARF offset
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
types map[string]dwarfRef
packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address
gStructOffset uint64 gStructOffset uint64
@ -112,7 +114,7 @@ type compileUnit struct {
optimized bool // this compile unit is optimized optimized bool // this compile unit is optimized
producer string // producer attribute 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. 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 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. // Location returns the location described by attribute attr of entry.
// This will either be an int64 address or a slice of Pieces for locations // 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 // 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 { func (bi *BinaryInfo) findCompileUnitForOffset(off dwarf.Offset) *compileUnit {
for _, cu := range bi.compileUnits { i := sort.Search(len(bi.compileUnits), func(i int) bool {
if off >= cu.startOffset && off < cu.endOffset { return bi.compileUnits[i].offset >= off
return cu })
} if i > 0 {
i--
} }
return nil return bi.compileUnits[i]
} }
// Producer returns the value of DW_AT_producer. // Producer returns the value of DW_AT_producer.

@ -21,7 +21,7 @@ import (
const defaultCFA = 0xc420051d00 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() abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build()
assertNoError(err, t, "dwarfbuilder.Build") assertNoError(err, t, "dwarfbuilder.Build")
dwdata, err := dwarf.New(abbrev, aranges, frame, info, line, pubnames, ranges, str) 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 := proc.NewBinaryInfo("linux", "amd64")
bi.LoadImageFromData(dwdata, frame, line, loc) bi.LoadImageFromData(dwdata, frame, line, loc)
return bi return bi, dwdata
} }
// fakeMemory implements proc.MemoryReadWriter by reading from a byte slice. // 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.AddVariable("c", uint16off, dwarfbuilder.LocationBlock(op.DW_OP_regx, int(1)))
dwb.TagClose() dwb.TagClose()
bi := fakeBinaryInfo(t, dwb) bi, _ := fakeBinaryInfo(t, dwb)
mainfn := bi.LookupFunc["main.main"] 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.AddVariable("n", intoff, dwarfbuilder.LocationBlock(op.DW_OP_reg3))
dwb.TagClose() dwb.TagClose()
bi := fakeBinaryInfo(t, dwb) bi, _ := fakeBinaryInfo(t, dwb)
mainfn := bi.LookupFunc["main.main"] mainfn := bi.LookupFunc["main.main"]
@ -206,7 +206,7 @@ func TestDwarfExprLoclist(t *testing.T) {
}) })
dwb.TagClose() dwb.TagClose()
bi := fakeBinaryInfo(t, dwb) bi, _ := fakeBinaryInfo(t, dwb)
mainfn := bi.LookupFunc["main.main"] 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.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() dwb.TagClose()
bi := fakeBinaryInfo(t, dwb) bi, _ := fakeBinaryInfo(t, dwb)
mainfn := bi.LookupFunc["main.main"] 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) 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) 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] } 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 compileUnitsByOffset) Len() int { return len(v) }
func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].lowPC < v[j].lowPC } func (v compileUnitsByOffset) Less(i int, j int) bool { return v[i].offset < v[j].offset }
func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] } func (v compileUnitsByOffset) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
type packageVarsByAddr []packageVar type packageVarsByAddr []packageVar
@ -223,7 +223,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
cu := &compileUnit{} cu := &compileUnit{}
cu.image = image cu.image = image
cu.entry = entry cu.entry = entry
cu.startOffset = entry.Offset cu.offset = entry.Offset
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage { if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
cu.isgo = true cu.isgo = true
} }
@ -264,7 +264,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
} }
bi.compileUnits = append(bi.compileUnits, cu) bi.compileUnits = append(bi.compileUnits, cu)
if entry.Children { if entry.Children {
cu.endOffset = bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu) bi.loadDebugInfoMapsCompileUnit(ctxt, image, reader, cu)
} }
case dwarf.TagPartialUnit: 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(functionsDebugInfoByEntry(bi.Functions))
sort.Sort(packageVarsByAddr(bi.packageVars)) 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. // loadDebugInfoMapsCompileUnit loads entry from a single compile unit.
func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) dwarf.Offset { func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContext, image *Image, reader *reader.Reader, cu *compileUnit) {
var lastOffset dwarf.Offset
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil { if err != nil {
image.setLoadError("error reading debug_info: %v", err) image.setLoadError("error reading debug_info: %v", err)
return lastOffset + 1 return
}
if entry.Tag != 0 {
lastOffset = entry.Offset
} }
switch entry.Tag { switch entry.Tag {
case 0: case 0:
return lastOffset + 1 return
case dwarf.TagImportedUnit: case dwarf.TagImportedUnit:
bi.loadDebugInfoMapsImportedUnit(entry, ctxt, image, cu) bi.loadDebugInfoMapsImportedUnit(entry, ctxt, image, cu)
reader.SkipChildren() reader.SkipChildren()
@ -409,7 +405,7 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex
entry, err = reader.Next() entry, err = reader.Next()
if err != nil { if err != nil {
image.setLoadError("error reading debug_info: %v", err) image.setLoadError("error reading debug_info: %v", err)
return 0 return
} }
if entry.Tag == 0 { if entry.Tag == 0 {
break break
@ -441,8 +437,6 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex
} }
} }
} }
return lastOffset + 1
} }
// loadDebugInfoMapsImportedUnit loads entries into cu from the partial unit // loadDebugInfoMapsImportedUnit loads entries into cu from the partial unit