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