mirror of
https://github.com/golang/go
synced 2024-11-11 23:40:22 -07:00
debug/dwarf: add Reader.SeekPC and Data.Ranges
These new methods help find the compilation unit to pass to the LineReader method in order to find the line information for a PC. The Ranges method also helps identify the specific function for a PC, needed to determine the function name. This uses the .debug.ranges section if necessary, and changes the object file format packages to pass in the section contents if available. Change-Id: I5ebc3d27faaf1a126ffb17a1e6027efdf64af836 Reviewed-on: https://go-review.googlesource.com/20769 Reviewed-by: Austin Clements <austin@google.com> Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
77f4b773e7
commit
d1b8871f13
@ -636,3 +636,123 @@ func (r *Reader) clone() typeReader {
|
||||
func (r *Reader) offset() Offset {
|
||||
return r.b.off
|
||||
}
|
||||
|
||||
// SeekPC returns the Entry for the compilation unit that includes pc,
|
||||
// and positions the reader to read the children of that unit. If pc
|
||||
// is not covered by any unit, SeekPC returns ErrUnknownPC and the
|
||||
// position of the reader is undefined.
|
||||
//
|
||||
// Because compilation units can describe multiple regions of the
|
||||
// executable, in the worst case SeekPC must search through all the
|
||||
// ranges in all the compilation units. Each call to SeekPC starts the
|
||||
// search at the compilation unit of the last call, so in general
|
||||
// looking up a series of PCs will be faster if they are sorted. If
|
||||
// the caller wishes to do repeated fast PC lookups, it should build
|
||||
// an appropriate index using the Ranges method.
|
||||
func (r *Reader) SeekPC(pc uint64) (*Entry, error) {
|
||||
unit := r.unit
|
||||
for i := 0; i < len(r.d.unit); i++ {
|
||||
if unit >= len(r.d.unit) {
|
||||
unit = 0
|
||||
}
|
||||
r.err = nil
|
||||
r.lastChildren = false
|
||||
r.unit = unit
|
||||
u := &r.d.unit[unit]
|
||||
r.b = makeBuf(r.d, u, "info", u.off, u.data)
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ranges, err := r.d.Ranges(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, pcs := range ranges {
|
||||
if pcs[0] <= pc && pc < pcs[1] {
|
||||
return e, nil
|
||||
}
|
||||
}
|
||||
unit++
|
||||
}
|
||||
return nil, ErrUnknownPC
|
||||
}
|
||||
|
||||
// Ranges returns the PC ranges covered by e, a slice of [low,high) pairs.
|
||||
// Only some entry types, such as TagCompileUnit or TagSubprogram, have PC
|
||||
// ranges; for others, this will return nil with no error.
|
||||
func (d *Data) Ranges(e *Entry) ([][2]uint64, error) {
|
||||
var ret [][2]uint64
|
||||
|
||||
low, lowOK := e.Val(AttrLowpc).(uint64)
|
||||
|
||||
var high uint64
|
||||
var highOK bool
|
||||
highField := e.AttrField(AttrHighpc)
|
||||
if highField != nil {
|
||||
switch highField.Class {
|
||||
case ClassAddress:
|
||||
high, highOK = highField.Val.(uint64)
|
||||
case ClassConstant:
|
||||
off, ok := highField.Val.(int64)
|
||||
if ok {
|
||||
high = low + uint64(off)
|
||||
highOK = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lowOK && highOK {
|
||||
ret = append(ret, [2]uint64{low, high})
|
||||
}
|
||||
|
||||
ranges, rangesOK := e.Val(AttrRanges).(int64)
|
||||
if rangesOK && d.ranges != nil {
|
||||
// The initial base address is the lowpc attribute
|
||||
// of the enclosing compilation unit.
|
||||
// Although DWARF specifies the lowpc attribute,
|
||||
// comments in gdb/dwarf2read.c say that some versions
|
||||
// of GCC use the entrypc attribute, so we check that too.
|
||||
var cu *Entry
|
||||
if e.Tag == TagCompileUnit {
|
||||
cu = e
|
||||
} else {
|
||||
i := d.offsetToUnit(e.Offset)
|
||||
if i == -1 {
|
||||
return nil, errors.New("no unit for entry")
|
||||
}
|
||||
u := &d.unit[i]
|
||||
b := makeBuf(d, u, "info", u.off, u.data)
|
||||
cu = b.entry(u.atable, u.base)
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
}
|
||||
|
||||
var base uint64
|
||||
if cuEntry, cuEntryOK := cu.Val(AttrEntrypc).(uint64); cuEntryOK {
|
||||
base = cuEntry
|
||||
} else if cuLow, cuLowOK := cu.Val(AttrLowpc).(uint64); cuLowOK {
|
||||
base = cuLow
|
||||
}
|
||||
|
||||
u := &d.unit[d.offsetToUnit(e.Offset)]
|
||||
buf := makeBuf(d, u, "ranges", Offset(ranges), d.ranges[ranges:])
|
||||
for len(buf.data) > 0 {
|
||||
low = buf.addr()
|
||||
high = buf.addr()
|
||||
|
||||
if low == 0 && high == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if low == ^uint64(0)>>uint((8-u.addrsize())*8) {
|
||||
base = high
|
||||
} else {
|
||||
ret = append(ret, [2]uint64{base + low, base + high})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package dwarf_test
|
||||
|
||||
import (
|
||||
. "debug/dwarf"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -34,3 +35,103 @@ func TestSplit(t *testing.T) {
|
||||
t.Fatalf("bad class: have %s, want %s", f.Class, ClassUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
// wantRange maps from a PC to the ranges of the compilation unit
|
||||
// containing that PC.
|
||||
type wantRange struct {
|
||||
pc uint64
|
||||
ranges [][2]uint64
|
||||
}
|
||||
|
||||
func TestReaderSeek(t *testing.T) {
|
||||
want := []wantRange{
|
||||
{0x40059d, [][2]uint64{{0x40059d, 0x400601}}},
|
||||
{0x400600, [][2]uint64{{0x40059d, 0x400601}}},
|
||||
{0x400601, [][2]uint64{{0x400601, 0x400611}}},
|
||||
{0x4005f0, [][2]uint64{{0x40059d, 0x400601}}}, // loop test
|
||||
{0x10, nil},
|
||||
{0x400611, nil},
|
||||
}
|
||||
testRanges(t, "testdata/line-gcc.elf", want)
|
||||
}
|
||||
|
||||
func TestRangesSection(t *testing.T) {
|
||||
want := []wantRange{
|
||||
{0x400500, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}},
|
||||
{0x400400, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}},
|
||||
{0x400548, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}},
|
||||
{0x400407, [][2]uint64{{0x400500, 0x400549}, {0x400400, 0x400408}}},
|
||||
{0x400408, nil},
|
||||
{0x400449, nil},
|
||||
{0x4003ff, nil},
|
||||
}
|
||||
testRanges(t, "testdata/ranges.elf", want)
|
||||
}
|
||||
|
||||
func testRanges(t *testing.T, name string, want []wantRange) {
|
||||
d := elfData(t, name)
|
||||
r := d.Reader()
|
||||
for _, w := range want {
|
||||
entry, err := r.SeekPC(w.pc)
|
||||
if err != nil {
|
||||
if w.ranges != nil {
|
||||
t.Errorf("%s: missing Entry for %#x", name, w.pc)
|
||||
}
|
||||
if err != ErrUnknownPC {
|
||||
t.Errorf("%s: expected ErrUnknownPC for %#x, got %v", name, w.pc, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ranges, err := d.Ranges(entry)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(ranges, w.ranges) {
|
||||
t.Errorf("%s: for %#x got %x, expected %x", name, w.pc, ranges, w.ranges)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderRanges(t *testing.T) {
|
||||
d := elfData(t, "testdata/line-gcc.elf")
|
||||
|
||||
subprograms := []struct {
|
||||
name string
|
||||
ranges [][2]uint64
|
||||
}{
|
||||
{"f1", [][2]uint64{{0x40059d, 0x4005e7}}},
|
||||
{"main", [][2]uint64{{0x4005e7, 0x400601}}},
|
||||
{"f2", [][2]uint64{{0x400601, 0x400611}}},
|
||||
}
|
||||
|
||||
r := d.Reader()
|
||||
i := 0
|
||||
for entry, err := r.Next(); entry != nil && err == nil; entry, err = r.Next() {
|
||||
if entry.Tag != TagSubprogram {
|
||||
continue
|
||||
}
|
||||
|
||||
if i > len(subprograms) {
|
||||
t.Fatalf("too many subprograms (expected at most %d)", i)
|
||||
}
|
||||
|
||||
if got := entry.Val(AttrName).(string); got != subprograms[i].name {
|
||||
t.Errorf("subprogram %d name is %s, expected %s", i, got, subprograms[i].name)
|
||||
}
|
||||
ranges, err := d.Ranges(entry)
|
||||
if err != nil {
|
||||
t.Errorf("subprogram %d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(ranges, subprograms[i].ranges) {
|
||||
t.Errorf("subprogram %d ranges are %x, expected %x", i, ranges, subprograms[i].ranges)
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if i < len(subprograms) {
|
||||
t.Errorf("saw only %d subprograms, expected %d", i, len(subprograms))
|
||||
}
|
||||
}
|
||||
|
25
src/debug/dwarf/testdata/ranges.c
vendored
Normal file
25
src/debug/dwarf/testdata/ranges.c
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// gcc -g -O2 -freorder-blocks-and-partition
|
||||
|
||||
const char *arr[10000];
|
||||
const char *hot = "hot";
|
||||
const char *cold = "cold";
|
||||
|
||||
__attribute__((noinline))
|
||||
void fn(int path) {
|
||||
int i;
|
||||
|
||||
if (path) {
|
||||
for (i = 0; i < sizeof arr / sizeof arr[0]; i++) {
|
||||
arr[i] = hot;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < sizeof arr / sizeof arr[0]; i++) {
|
||||
arr[i] = cold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
fn(argc);
|
||||
return 0;
|
||||
}
|
BIN
src/debug/dwarf/testdata/ranges.elf
vendored
Executable file
BIN
src/debug/dwarf/testdata/ranges.elf
vendored
Executable file
Binary file not shown.
@ -958,7 +958,7 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
// There are many other DWARF sections, but these
|
||||
// are the ones the debug/dwarf package uses.
|
||||
// Don't bother loading others.
|
||||
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil}
|
||||
var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
|
||||
for i, s := range f.Sections {
|
||||
suffix := ""
|
||||
switch {
|
||||
@ -979,7 +979,7 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
dat[suffix] = b
|
||||
}
|
||||
|
||||
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, nil, dat["str"])
|
||||
d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -474,7 +474,7 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
// There are many other DWARF sections, but these
|
||||
// are the ones the debug/dwarf package uses.
|
||||
// Don't bother loading others.
|
||||
var names = [...]string{"abbrev", "info", "line", "str"}
|
||||
var names = [...]string{"abbrev", "info", "line", "ranges", "str"}
|
||||
var dat [len(names)][]byte
|
||||
for i, name := range names {
|
||||
name = "__debug_" + name
|
||||
@ -489,8 +489,8 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
dat[i] = b
|
||||
}
|
||||
|
||||
abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
|
||||
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
|
||||
abbrev, info, line, ranges, str := dat[0], dat[1], dat[2], dat[3], dat[4]
|
||||
return dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
|
||||
}
|
||||
|
||||
// ImportedSymbols returns the names of all symbols
|
||||
|
@ -298,7 +298,7 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
// There are many other DWARF sections, but these
|
||||
// are the ones the debug/dwarf package uses.
|
||||
// Don't bother loading others.
|
||||
var names = [...]string{"abbrev", "info", "line", "str"}
|
||||
var names = [...]string{"abbrev", "info", "line", "ranges", "str"}
|
||||
var dat [len(names)][]byte
|
||||
for i, name := range names {
|
||||
name = ".debug_" + name
|
||||
@ -316,8 +316,8 @@ func (f *File) DWARF() (*dwarf.Data, error) {
|
||||
dat[i] = b
|
||||
}
|
||||
|
||||
abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
|
||||
return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
|
||||
abbrev, info, line, ranges, str := dat[0], dat[1], dat[2], dat[3], dat[4]
|
||||
return dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
|
||||
}
|
||||
|
||||
// ImportedSymbols returns the names of all symbols
|
||||
|
Loading…
Reference in New Issue
Block a user