internal/gocore: support for DWARF version 5 location lists
Rework the DWARF reading code to handle location lists in DWARF 5
format in addition to the existing DWARF 2/4 code.
Updates golang/go#26379.
Change-Id: I9d502f5bcecc752e4c1fe7cfaa9ff5871fe417de
Reviewed-on: https://go-review.googlesource.com/c/debug/+/654256
Reviewed-by: Junyang Shao <shaojunyang@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
diff --git a/internal/core/process.go b/internal/core/process.go
index d7c000c..ad19ad0 100644
--- a/internal/core/process.go
+++ b/internal/core/process.go
@@ -47,11 +47,13 @@
memory splicedMemory // virtual address mappings
pageTable pageTable4 // for fast address->mapping lookups
- syms map[string]Address // symbols (could be empty if executable is stripped)
- symErr error // an error encountered while reading symbols
- dwarf *dwarf.Data // debugging info (could be nil)
- dwarfErr error // an error encountered while reading DWARF
- dwarfLoc []byte // .debug_loc section
+ syms map[string]Address // symbols (could be empty if executable is stripped)
+ symErr error // an error encountered while reading symbols
+ dwarf *dwarf.Data // debugging info (could be nil)
+ dwarfErr error // an error encountered while reading DWARF
+ dwarfLoc []byte // .debug_loc section
+ dwarfLocLists []byte // .debug_loclists section
+ dwarfAddr []byte // .debug_addr section
warnings []string // warnings generated during loading
}
@@ -181,6 +183,14 @@
return p.dwarfLoc, p.dwarfErr
}
+func (p *Process) DWARFLocLists() ([]byte, error) {
+ return p.dwarfLocLists, p.dwarfErr
+}
+
+func (p *Process) DWARFAddr() ([]byte, error) {
+ return p.dwarfAddr, p.dwarfErr
+}
+
// Symbols returns a mapping from name to inferior address, along with
// any error encountered during reading the symbol information.
// (There may be both an error and some returned symbols.)
@@ -282,12 +292,27 @@
if dwarfErr != nil {
dwarfErr = fmt.Errorf("error reading DWARF info from %s: %v", exeFile.Name(), dwarfErr)
}
- var dwarfLoc []byte
- if locSection := exeElf.Section(".debug_loc"); locSection != nil {
- var err error
- dwarfLoc, err = locSection.Data()
- if err != nil && dwarfErr == nil {
- dwarfErr = fmt.Errorf("error reading DWARF location list section from %s: %v", exeFile.Name(), err)
+
+ // Note that we expect to find .debug_loc for DWARF V4 binaries
+ // and .debug_loclists + .debug_addr for DWARF V5. If C code is
+ // mixed in, we may see both sections, since you're allowed to mix
+ // different DWARF versions by compilation unit.
+ var dwarfLoc, dwarfLocLists, dwarfAddr []byte
+ toRead := []struct {
+ name string
+ payload *[]byte
+ }{
+ {name: ".debug_loc", payload: &dwarfLoc},
+ {name: ".debug_loclists", payload: &dwarfLocLists},
+ {name: ".debug_addr", payload: &dwarfAddr},
+ }
+ for _, secitem := range toRead {
+ if section := exeElf.Section(secitem.name); section != nil {
+ payload, err := section.Data()
+ if err != nil && dwarfErr == nil {
+ dwarfErr = fmt.Errorf("error reading DWARF %s section from %s: %v", secitem.name, exeFile.Name(), err)
+ }
+ *(secitem.payload) = payload
}
}
@@ -368,19 +393,21 @@
}
p := &Process{
- meta: meta,
- entryPoint: entryPoint,
- staticBase: staticBase,
- args: args,
- threads: threads,
- memory: mem,
- pageTable: pageTable,
- syms: syms,
- symErr: symErr,
- dwarf: dwarf,
- dwarfErr: dwarfErr,
- dwarfLoc: dwarfLoc,
- warnings: warnings,
+ meta: meta,
+ entryPoint: entryPoint,
+ staticBase: staticBase,
+ args: args,
+ threads: threads,
+ memory: mem,
+ pageTable: pageTable,
+ syms: syms,
+ symErr: symErr,
+ dwarf: dwarf,
+ dwarfErr: dwarfErr,
+ dwarfLoc: dwarfLoc,
+ dwarfLocLists: dwarfLocLists,
+ dwarfAddr: dwarfAddr,
+ warnings: warnings,
}
return p, nil
diff --git a/internal/gocore/dwarf.go b/internal/gocore/dwarf.go
index 214c1ed..e6ade63 100644
--- a/internal/gocore/dwarf.go
+++ b/internal/gocore/dwarf.go
@@ -14,6 +14,7 @@
"golang.org/x/debug/dwtest"
"golang.org/x/debug/internal/core"
+ "golang.org/x/debug/third_party/delve/dwarf/godwarf"
"golang.org/x/debug/third_party/delve/dwarf/loclist"
"golang.org/x/debug/third_party/delve/dwarf/op"
"golang.org/x/debug/third_party/delve/dwarf/regnum"
@@ -386,15 +387,40 @@
if err != nil {
return nil, fmt.Errorf("failed to read DWARF: %v", err)
}
+ dLocListsSec, derr := p.DWARFLocLists()
+ if derr != nil {
+ return nil, fmt.Errorf("failed to read DWARF: %v", derr)
+ }
+ dAddrSec, daerr := p.DWARFAddr()
+ if daerr != nil {
+ return nil, fmt.Errorf("failed to read DWARF: %v", daerr)
+ }
+ debugAddrSec := godwarf.ParseAddr(dAddrSec)
vars := make(map[*Func][]dwarfVar)
var curfn *Func
r := d.Reader()
+ addrBase := uint64(0)
+ unitVersion := 4
for e, err := r.Next(); e != nil && err == nil; e, err = r.Next() {
if isNonGoCU(e) {
r.SkipChildren()
continue
}
-
+ if e.Tag == dwarf.TagCompileUnit {
+ // Determine whether we're looking at DWARF version 5 or
+ // some version prior to 5. At the moment the DWARF
+ // reading machinery in debug/dwarf keeps track of DWARF
+ // version for each unit but doesn't expose this info to
+ // clients, so we need to do the detection indirectly,
+ // here by looking for an attribute that only gets generated
+ // if DWARF 5 is being used.
+ if f := e.AttrField(dwarf.AttrAddrBase); f != nil {
+ addrBase = uint64(f.Val.(int64))
+ unitVersion = 5
+ } else {
+ unitVersion = 4
+ }
+ }
if e.Tag == dwarf.TagSubprogram {
if e.AttrField(dwarf.AttrLowpc) == nil ||
e.AttrField(dwarf.AttrHighpc) == nil {
@@ -459,30 +485,74 @@
}
name := nf.Val.(string)
- // No .debug_loc section, can't make progress.
- if len(dLocSec) == 0 {
- continue
- }
+ // FIXME A note on the code above: we're screening out
+ // variables that don't have a specific name and type, which
+ // on the surface seems workable, however this also rejects
+ // vars corresponding to "concrete" instances of vars that
+ // have been inlined, which is almost certainly a mistake. For
+ // more on concrete/abstract variables see
+ // https://github.com/golang/proposal/blob/master/design/22080-dwarf-inlining.md#how-the-generated-dwarf-should-look,
+ // which has examples. Instead what the code above should be
+ // doing is caching any abstract variables it encounters in a
+ // map (indexed by DWARF offset), then when it encounters a
+ // concrete var, pick the name and type up from the abstract
+ // variable.
- // Read the location list.
- locListOff := aloc.Val.(int64)
- dr := loclist.NewDwarf2Reader(dLocSec, int(p.PtrSize()))
- dr.Seek(int(locListOff))
- var base uint64
- var e loclist.Entry
- for dr.Next(&e) {
- if e.BaseAddressSelection() {
- base = e.HighPC + p.StaticBase()
- continue
+ switch unitVersion {
+ case 5:
+ {
+ // No .debug_loclists section, can't make progress.
+ if len(dLocListsSec) == 0 {
+ continue
+ }
+
+ debugAddr := debugAddrSec.GetSubsection(addrBase)
+ // Read the location lists.
+ locListOff := aloc.Val.(int64)
+ dr := loclist.NewDwarf5Reader(dLocListsSec)
+ elist, err := dr.Enumerate(locListOff, p.StaticBase(), debugAddr)
+ if err != nil {
+ return nil, err
+ }
+ for _, e := range elist {
+ vars[curfn] = append(vars[curfn], dwarfVar{
+ lowPC: core.Address(e.LowPC),
+ highPC: core.Address(e.HighPC),
+ kind: kind,
+ instr: e.Instr,
+ name: name,
+ typ: dwarfTypeMap[dt],
+ })
+ }
}
- vars[curfn] = append(vars[curfn], dwarfVar{
- lowPC: core.Address(e.LowPC + base),
- highPC: core.Address(e.HighPC + base),
- kind: kind,
- instr: e.Instr,
- name: name,
- typ: dwarfTypeMap[dt],
- })
+ default:
+ {
+ // No .debug_loc section, can't make progress.
+ if len(dLocSec) == 0 {
+ continue
+ }
+
+ // Read the location list.
+ locListOff := aloc.Val.(int64)
+ dr := loclist.NewDwarf2Reader(dLocSec, int(p.PtrSize()))
+ dr.Seek(int(locListOff))
+ var base uint64
+ var e loclist.Entry
+ for dr.Next(&e) {
+ if e.BaseAddressSelection() {
+ base = e.HighPC + p.StaticBase()
+ continue
+ }
+ vars[curfn] = append(vars[curfn], dwarfVar{
+ lowPC: core.Address(e.LowPC + base),
+ highPC: core.Address(e.HighPC + base),
+ kind: kind,
+ instr: e.Instr,
+ name: name,
+ typ: dwarfTypeMap[dt],
+ })
+ }
+ }
}
}
return vars, nil