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