]> Cypherpunks repositories - gostls13.git/commitdiff
debug/dwarf: add Reader.SeekPC and Data.Ranges
authorIan Lance Taylor <iant@golang.org>
Wed, 16 Mar 2016 21:15:54 +0000 (14:15 -0700)
committerIan Lance Taylor <iant@golang.org>
Tue, 22 Mar 2016 14:06:09 +0000 (14:06 +0000)
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>

src/debug/dwarf/entry.go
src/debug/dwarf/entry_test.go
src/debug/dwarf/testdata/ranges.c [new file with mode: 0644]
src/debug/dwarf/testdata/ranges.elf [new file with mode: 0755]
src/debug/elf/file.go
src/debug/macho/file.go
src/debug/pe/file.go

index 6f72005e7204cedf8846eb6836feaf7d5dea117f..80bf14cb22fd6ec6c23ce8192123dae64e40ed9e 100644 (file)
@@ -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
+}
index 8bd2d2a8ad48154cc08693fb45ce066b0a78b244..58a5d570be56da0a62b14ae5ef0e89ffcd7e59e3 100644 (file)
@@ -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))
+       }
+}
diff --git a/src/debug/dwarf/testdata/ranges.c b/src/debug/dwarf/testdata/ranges.c
new file mode 100644 (file)
index 0000000..2f208e5
--- /dev/null
@@ -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;
+}
diff --git a/src/debug/dwarf/testdata/ranges.elf b/src/debug/dwarf/testdata/ranges.elf
new file mode 100755 (executable)
index 0000000..7f54138
Binary files /dev/null and b/src/debug/dwarf/testdata/ranges.elf differ
index 17bf76b29e52a1df4aec15ce33d9b18a0bc313e9..72796d535f89cc756af6f4e87b9ba2b6672bcd11 100644 (file)
@@ -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
        }
index 063a6f5ff89afe0218603bcfcf32a6251535d0f5..223346f10d7cfa3f79f67160938055d7760e28ae 100644 (file)
@@ -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
index 6adb039fd9b0a6085febed72013c12710561b394..1acc368e1b740de40005873633ab9f52e7de3169 100644 (file)
@@ -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