proc: replace all uses of gosymtab/gopclntab with uses of debug_line

gosymtab and gopclntab only contain informations about go code, linked
C code isn't there, we should use debug_line instead to also cover C.

Updates #935
This commit is contained in:
aarzilli 2017-09-01 15:30:45 +02:00 committed by Derek Parker
parent 913153e7ff
commit 6d40517944
13 changed files with 392 additions and 262 deletions

@ -3,6 +3,7 @@ package line
import (
"bytes"
"encoding/binary"
"path/filepath"
"github.com/derekparker/delve/pkg/dwarf/util"
)
@ -28,7 +29,7 @@ type DebugLineInfo struct {
}
type FileEntry struct {
Name string
Path string
DirIdx uint64
LastModTime uint64
Length uint64
@ -36,17 +37,8 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo
func (d *DebugLines) GetLineInfo(name string) *DebugLineInfo {
// Find in which table file exists and return it.
for _, l := range *d {
if _, ok := l.Lookup[name]; ok {
return l
}
}
return nil
}
func Parse(data []byte) DebugLines {
// ParseAll parses all debug_line segments found in data
func ParseAll(data []byte) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
@ -54,8 +46,20 @@ func Parse(data []byte) DebugLines {
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
lines = append(lines, Parse("", buf))
}
return lines
}
// Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer) *DebugLineInfo {
dbl := new(DebugLineInfo)
dbl.Lookup = make(map[string]*FileEntry)
if compdir != "" {
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
}
parseDebugLinePrologue(dbl, buf)
parseIncludeDirs(dbl, buf)
@ -67,10 +71,7 @@ func Parse(data []byte) DebugLines {
// - So you have UnitLength - PrologueLength - (version_length_bytes(2) + prologue_length_bytes(4)).
dbl.Instructions = buf.Next(int(dbl.Prologue.UnitLength - dbl.Prologue.Length - 6))
lines = append(lines, dbl)
}
return lines
return dbl
}
func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) {
@ -106,17 +107,21 @@ func parseFileEntries(info *DebugLineInfo, buf *bytes.Buffer) {
for {
entry := new(FileEntry)
name, _ := util.ParseString(buf)
if name == "" {
entry.Path, _ = util.ParseString(buf)
if entry.Path == "" {
break
}
entry.Name = name
entry.DirIdx, _ = util.DecodeULEB128(buf)
entry.LastModTime, _ = util.DecodeULEB128(buf)
entry.Length, _ = util.DecodeULEB128(buf)
if !filepath.IsAbs(entry.Path) {
if entry.DirIdx >= 0 && entry.DirIdx < uint64(len(info.IncludeDirs)) {
entry.Path = filepath.Join(info.IncludeDirs[entry.DirIdx], entry.Path)
}
}
info.FileNames = append(info.FileNames, entry)
info.Lookup[name] = entry
info.Lookup[entry.Path] = entry
}
}

@ -60,7 +60,7 @@ const (
func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t)
debugLines := Parse(data)
debugLines := ParseAll(data)
dbl := debugLines[0]
prologue := dbl.Prologue
@ -105,7 +105,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) {
ok := false
for _, n := range dbl.FileNames {
if strings.Contains(n.Name, "/delve/_fixtures/testnextprog.go") {
if strings.Contains(n.Path, "/delve/_fixtures/testnextprog.go") {
ok = true
break
}
@ -154,6 +154,6 @@ func BenchmarkLineParser(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Parse(data)
_ = ParseAll(data)
}
}

@ -47,6 +47,11 @@ const (
DW_LNS_set_basic_block = 7
DW_LNS_const_add_pc = 8
DW_LNS_fixed_advance_pc = 9
// DWARF v4
DW_LNS_set_prologue_end = 10
DW_LNS_set_epilouge_begin = 11
DW_LNS_set_isa = 12
)
// Extended opcodes
@ -66,6 +71,11 @@ var standardopcodes = map[byte]opcodefn{
DW_LNS_set_basic_block: setbasicblock,
DW_LNS_const_add_pc: constaddpc,
DW_LNS_fixed_advance_pc: fixedadvancepc,
// DWARF v4
DW_LNS_set_prologue_end: donothing0,
DW_LNS_set_epilouge_begin: donothing0,
DW_LNS_set_isa: donothing1,
}
var extendedopcodes = map[byte]opcodefn{
@ -75,16 +85,19 @@ var extendedopcodes = map[byte]opcodefn{
}
func newStateMachine(dbl *DebugLineInfo) *StateMachine {
return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Name, line: 1}
return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1}
}
// Returns all PCs for a given file/line. Useful for loops where the 'for' line
// could be split amongst 2 PCs.
func (dbl *DebugLines) AllPCsForFileLine(f string, l int) (pcs []uint64) {
func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) {
if lineInfo == nil {
return nil
}
var (
foundFile bool
lastAddr uint64
lineInfo = dbl.GetLineInfo(f)
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
@ -116,11 +129,11 @@ func (dbl *DebugLines) AllPCsForFileLine(f string, l int) (pcs []uint64) {
var NoSourceError = errors.New("no source available")
func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) ([]uint64, error) {
lineInfo := dbl.GetLineInfo(filename)
func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error) {
if lineInfo == nil {
return nil, NoSourceError
return nil, nil
}
var (
pcs []uint64
lastaddr uint64
@ -144,6 +157,63 @@ func (dbl *DebugLines) AllPCsBetween(begin, end uint64, filename string) ([]uint
return pcs, nil
}
// PCToLine returns the filename and line number associated with pc.
// If pc isn't found inside lineInfo's table it will return the filename and
// line number associated with the closest PC address preceding pc.
func (lineInfo *DebugLineInfo) PCToLine(pc uint64) (string, int) {
if lineInfo == nil {
return "", 0
}
var (
buf = bytes.NewBuffer(lineInfo.Instructions)
sm = newStateMachine(lineInfo)
lastFilename string
lastLineno int
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if !sm.valid {
continue
}
if sm.address > pc {
return lastFilename, lastLineno
}
if sm.address == pc {
return sm.file, sm.line
}
lastFilename, lastLineno = sm.file, sm.line
}
return "", 0
}
// LineToPC returns the first PC address associated with filename:lineno.
func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
if lineInfo == nil {
return 0
}
var (
foundFile bool
sm = newStateMachine(lineInfo)
buf = bytes.NewBuffer(lineInfo.Instructions)
)
for b, err := buf.ReadByte(); err == nil; b, err = buf.ReadByte() {
findAndExecOpcode(sm, buf, b)
if foundFile && sm.file != filename {
break
}
if sm.line == lineno && sm.file == filename {
foundFile = true
if sm.valid {
return sm.address
}
}
}
return 0
}
func findAndExecOpcode(sm *StateMachine, buf *bytes.Buffer, b byte) {
switch {
case b == 0:
@ -215,7 +285,7 @@ func advanceline(sm *StateMachine, buf *bytes.Buffer) {
func setfile(sm *StateMachine, buf *bytes.Buffer) {
i, _ := util.DecodeULEB128(buf)
sm.file = sm.dbl.FileNames[i-1].Name
sm.file = sm.dbl.FileNames[i-1].Path
}
func setcolumn(sm *StateMachine, buf *bytes.Buffer) {
@ -242,6 +312,15 @@ func fixedadvancepc(sm *StateMachine, buf *bytes.Buffer) {
sm.address += uint64(operand)
}
func donothing0(sm *StateMachine, buf *bytes.Buffer) {
// does nothing, no operands
}
func donothing1(sm *StateMachine, buf *bytes.Buffer) {
// does nothing, consumes one operand
util.DecodeSLEB128(buf)
}
func endsequence(sm *StateMachine, buf *bytes.Buffer) {
sm.endSeq = true
sm.valid = true

@ -3,13 +3,14 @@ package proc
import (
"debug/dwarf"
"debug/elf"
"debug/gosym"
"debug/macho"
"debug/pe"
"errors"
"fmt"
"io"
"os"
"sort"
"strings"
"sync"
"time"
@ -31,13 +32,18 @@ type BinaryInfo struct {
Arch Arch
dwarf *dwarf.Data
frameEntries frame.FrameDescriptionEntries
lineInfo line.DebugLines
goSymTable *gosym.Table
compileUnits []*compileUnit
types map[string]dwarf.Offset
packageVars map[string]dwarf.Offset
functions []functionDebugInfo
gStructOffset uint64
// Functions is a list of all DW_TAG_subprogram entries in debug_info.
Functions []Function
// Sources is a list of all source files found in debug_line.
Sources []string
// LookupFunc maps function names to a description of the function.
LookupFunc map[string]*Function
typeCache map[dwarf.Offset]godwarf.Type
loadModuleDataOnce sync.Once
@ -52,6 +58,64 @@ var UnsupportedLinuxArchErr = errors.New("unsupported architecture - only linux/
var UnsupportedWindowsArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
var UnsupportedDarwinArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported")
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
type compileUnit struct {
entry *dwarf.Entry // debug_info entry describing this compile unit
isgo bool // true if this is the go compile unit
Name string // univocal name for non-go compile units
lineInfo *line.DebugLineInfo // debug_line segment associated with this compile unit
LowPC, HighPC uint64
}
// Function describes a function in the target program.
type Function struct {
Name string
Entry, End uint64 // same as DW_AT_lowpc and DW_AT_highpc
offset dwarf.Offset
cu *compileUnit
}
// PackageName returns the package part of the symbol name,
// or the empty string if there is none.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) PackageName() string {
pathend := strings.LastIndex(fn.Name, "/")
if pathend < 0 {
pathend = 0
}
if i := strings.Index(fn.Name[pathend:], "."); i != -1 {
return fn.Name[:pathend+i]
}
return ""
}
// ReceiverName returns the receiver type name of this symbol,
// or the empty string if there is none.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) ReceiverName() string {
pathend := strings.LastIndex(fn.Name, "/")
if pathend < 0 {
pathend = 0
}
l := strings.Index(fn.Name[pathend:], ".")
r := strings.LastIndex(fn.Name[pathend:], ".")
if l == -1 || r == -1 || l == r {
return ""
}
return fn.Name[pathend+l+1 : pathend+r]
}
// BaseName returns the symbol name without the package or receiver name.
// Borrowed from $GOROOT/debug/gosym/symtab.go
func (fn *Function) BaseName() string {
if i := strings.LastIndex(fn.Name, "."); i != -1 {
return fn.Name[i+1:]
}
return fn.Name
}
func NewBinaryInfo(goos, goarch string) BinaryInfo {
r := BinaryInfo{GOOS: goos, nameOfRuntimeType: make(map[uintptr]nameOfRuntimeTypeEntry), typeCache: make(map[dwarf.Offset]godwarf.Type)}
@ -96,16 +160,6 @@ func (bi *BinaryInfo) DwarfReader() *reader.Reader {
return reader.New(bi.dwarf)
}
// Sources returns list of source files that comprise the debugged binary.
func (bi *BinaryInfo) Sources() map[string]*gosym.Obj {
return bi.goSymTable.Files
}
// Funcs returns list of functions present in the debugged program.
func (bi *BinaryInfo) Funcs() []gosym.Func {
return bi.goSymTable.Funcs
}
// Types returns list of types present in the debugged program.
func (bi *BinaryInfo) Types() ([]string, error) {
types := make([]string, 0, len(bi.types))
@ -116,18 +170,44 @@ func (bi *BinaryInfo) Types() ([]string, error) {
}
// PCToLine converts an instruction address to a file/line/function.
func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *gosym.Func) {
return bi.goSymTable.PCToLine(pc)
func (bi *BinaryInfo) PCToLine(pc uint64) (string, int, *Function) {
fn := bi.PCToFunc(pc)
if fn == nil {
return "", 0, nil
}
f, ln := fn.cu.lineInfo.PCToLine(pc)
return f, ln, fn
}
// LineToPC converts a file:line into a memory address.
func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *gosym.Func, err error) {
return bi.goSymTable.LineToPC(filename, lineno)
func (bi *BinaryInfo) LineToPC(filename string, lineno int) (pc uint64, fn *Function, err error) {
for _, cu := range bi.compileUnits {
if cu.lineInfo.Lookup[filename] != nil {
pc = cu.lineInfo.LineToPC(filename, lineno)
fn = bi.PCToFunc(pc)
if fn == nil {
err = fmt.Errorf("no code at %s:%d", filename, lineno)
}
return
}
}
err = fmt.Errorf("could not find %s:%d", filename, lineno)
return
}
// PCToFunc returns the function containing the given PC address
func (bi *BinaryInfo) PCToFunc(pc uint64) *gosym.Func {
return bi.goSymTable.PCToFunc(pc)
func (bi *BinaryInfo) PCToFunc(pc uint64) *Function {
i := sort.Search(len(bi.Functions), func(i int) bool {
fn := bi.Functions[i]
return pc <= fn.Entry || (fn.Entry <= pc && pc < fn.End)
})
if i != len(bi.Functions) {
fn := &bi.Functions[i]
if fn.Entry <= pc && pc < fn.End {
return fn
}
}
return nil
}
func (bi *BinaryInfo) Close() error {
@ -164,11 +244,14 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error {
return err
}
wg.Add(5)
debugLineBytes, err := getDebugLineInfoElf(elfFile)
if err != nil {
return err
}
wg.Add(3)
go bi.parseDebugFrameElf(elfFile, wg)
go bi.obtainGoSymbolsElf(elfFile, wg)
go bi.parseDebugLineInfoElf(elfFile, wg)
go bi.loadDebugInfoMaps(wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg)
go bi.setGStructOffsetElf(elfFile, wg)
return nil
}
@ -197,55 +280,15 @@ func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) {
}
}
func (bi *BinaryInfo) obtainGoSymbolsElf(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section(".gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
bi.setLoadError("could not get .gosymtab section: %v", err)
return
}
}
if sec := exe.Section(".gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
bi.setLoadError("could not get .gopclntab section: %v", err)
return
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section(".text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
bi.setLoadError("could not get initialize line table: %v", err)
return
}
bi.goSymTable = tab
}
func (bi *BinaryInfo) parseDebugLineInfoElf(exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
func getDebugLineInfoElf(exe *elf.File) ([]byte, error) {
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := exe.Section(".debug_line").Data()
if err != nil {
bi.setLoadError("could not get .debug_line section: %v", err)
return
return nil, fmt.Errorf("could not get .debug_line section: %v", err)
}
bi.lineInfo = line.Parse(debugLine)
} else {
bi.setLoadError("could not find .debug_line section in binary")
return
return debugLine, nil
}
return nil, errors.New("could not find .debug_line section in binary")
}
func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) {
@ -302,11 +345,14 @@ func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error {
return err
}
wg.Add(4)
debugLineBytes, err := getDebugLineInfoPE(peFile)
if err != nil {
return err
}
wg.Add(2)
go bi.parseDebugFramePE(peFile, wg)
go bi.obtainGoSymbolsPE(peFile, wg)
go bi.parseDebugLineInfoPE(peFile, wg)
go bi.loadDebugInfoMaps(wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg)
// Use ArbitraryUserPointer (0x28) as pointer to pointer
// to G struct per:
@ -356,25 +402,6 @@ func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) {
}
}
func (bi *BinaryInfo) obtainGoSymbolsPE(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
_, symdat, pclndat, err := pclnPE(exe)
if err != nil {
bi.setLoadError("could not get Go symbols: %v", err)
return
}
pcln := gosym.NewLineTable(pclndat, uint64(exe.Section(".text").Offset))
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
bi.setLoadError("could not get initialize line table: %v", err)
return
}
bi.goSymTable = tab
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) {
for _, s := range f.Symbols {
@ -445,23 +472,18 @@ func pclnPE(exe *pe.File) (textStart uint64, symtab, pclntab []byte, err error)
return textStart, symtab, pclntab, nil
}
func (bi *BinaryInfo) parseDebugLineInfoPE(exe *pe.File, wg *sync.WaitGroup) {
defer wg.Done()
func getDebugLineInfoPE(exe *pe.File) ([]byte, error) {
if sec := exe.Section(".debug_line"); sec != nil {
debugLine, err := sec.Data()
if err != nil && uint32(len(debugLine)) < sec.Size {
bi.setLoadError("could not get .debug_line section: %v", err)
return
return nil, fmt.Errorf("could not get .debug_line section: %v", err)
}
if 0 < sec.VirtualSize && sec.VirtualSize < sec.Size {
debugLine = debugLine[:sec.VirtualSize]
}
bi.lineInfo = line.Parse(debugLine)
} else {
bi.setLoadError("could not find .debug_line section in binary")
return
return debugLine, nil
}
return nil, errors.New("could not find .debug_line section in binary")
}
// MACH-O ////////////////////////////////////////////////////////////
@ -480,11 +502,14 @@ func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, wg *sync.WaitGroup) error
return err
}
wg.Add(4)
debugLineBytes, err := getDebugLineInfoMacho(exe)
if err != nil {
return err
}
wg.Add(2)
go bi.parseDebugFrameMacho(exe, wg)
go bi.obtainGoSymbolsMacho(exe, wg)
go bi.parseDebugLineInfoMacho(exe, wg)
go bi.loadDebugInfoMaps(wg)
go bi.loadDebugInfoMaps(debugLineBytes, wg)
bi.gStructOffset = 0x8a0
return nil
}
@ -513,53 +538,13 @@ func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup)
}
}
func (bi *BinaryInfo) obtainGoSymbolsMacho(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
var (
symdat []byte
pclndat []byte
err error
)
if sec := exe.Section("__gosymtab"); sec != nil {
symdat, err = sec.Data()
if err != nil {
bi.setLoadError("could not get .gosymtab section: %v", err)
return
}
}
if sec := exe.Section("__gopclntab"); sec != nil {
pclndat, err = sec.Data()
if err != nil {
bi.setLoadError("could not get .gopclntab section: %v", err)
return
}
}
pcln := gosym.NewLineTable(pclndat, exe.Section("__text").Addr)
tab, err := gosym.NewTable(symdat, pcln)
if err != nil {
bi.setLoadError("could not get initialize line table: %v", err)
return
}
bi.goSymTable = tab
}
func (bi *BinaryInfo) parseDebugLineInfoMacho(exe *macho.File, wg *sync.WaitGroup) {
defer wg.Done()
func getDebugLineInfoMacho(exe *macho.File) ([]byte, error) {
if sec := exe.Section("__debug_line"); sec != nil {
debugLine, err := exe.Section("__debug_line").Data()
if err != nil {
bi.setLoadError("could not get __debug_line section: %v", err)
return
return nil, fmt.Errorf("could not get __debug_line section: %v", err)
}
bi.lineInfo = line.Parse(debugLine)
} else {
bi.setLoadError("could not find __debug_line section in binary")
return
return debugLine, nil
}
return nil, errors.New("could not find __debug_line section in binary")
}

@ -1,7 +1,6 @@
package proc
import (
"debug/gosym"
"encoding/binary"
"golang.org/x/arch/x86/x86asm"
@ -141,7 +140,7 @@ func init() {
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry
func FirstPCAfterPrologue(p Process, fn *gosym.Func, sameline bool) (uint64, error) {
func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) {
var mem MemoryReadWriter = p.CurrentThread()
breakpoints := p.Breakpoints()
bi := p.BinInfo()

@ -1,7 +1,6 @@
package proc
import (
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
@ -11,11 +10,6 @@ import (
"strconv"
)
type functionDebugInfo struct {
lowpc, highpc uint64
offset dwarf.Offset
}
var NotExecutableErr = errors.New("not an executable file")
var NotRecordedErr = errors.New("not a recording")
@ -35,7 +29,7 @@ func (pe ProcessExitedError) Error() string {
// FindFileLocation returns the PC for a given file:line.
// Assumes that `file` is normailzed to lower case and '/' on Windows.
func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
pc, fn, err := p.BinInfo().goSymTable.LineToPC(fileName, lineno)
pc, fn, err := p.BinInfo().LineToPC(fileName, lineno)
if err != nil {
return 0, err
}
@ -53,7 +47,7 @@ func FindFileLocation(p Process, fileName string, lineno int) (uint64, error) {
// https://github.com/derekparker/delve/issues/170
func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset int) (uint64, error) {
bi := p.BinInfo()
origfn := bi.goSymTable.LookupFunc(funcName)
origfn := bi.LookupFunc[funcName]
if origfn == nil {
return 0, fmt.Errorf("Could not find function %s\n", funcName)
}
@ -61,8 +55,8 @@ func FindFunctionLocation(p Process, funcName string, firstLine bool, lineOffset
if firstLine {
return FirstPCAfterPrologue(p, origfn, false)
} else if lineOffset > 0 {
filename, lineno, _ := bi.goSymTable.PCToLine(origfn.Entry)
breakAddr, _, err := bi.goSymTable.LineToPC(filename, lineno+lineOffset)
filename, lineno := origfn.cu.lineInfo.PCToLine(origfn.Entry)
breakAddr, _, err := bi.LineToPC(filename, lineno+lineOffset)
return breakAddr, err
}

@ -32,8 +32,6 @@ type Stackframe struct {
CFA int64
// High address of the stack.
StackHi uint64
// Description of the stack frame.
FDE *frame.FrameDescriptionEntry
// Return address for this stack frame (as read from the stack frame itself).
Ret uint64
// Address to the memory location containing the return address
@ -108,11 +106,11 @@ type savedLR struct {
}
func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, pc, sp, bp, stackhi uint64, stkbar []savedLR, stkbarPos int) *stackIterator {
stackBarrierFunc := bi.goSymTable.LookupFunc(runtimeStackBarrier) // stack barriers were removed in Go 1.9
stackBarrierFunc := bi.LookupFunc[runtimeStackBarrier] // stack barriers were removed in Go 1.9
var stackBarrierPC uint64
if stackBarrierFunc != nil && stkbar != nil {
stackBarrierPC = stackBarrierFunc.Entry
fn := bi.goSymTable.PCToFunc(pc)
fn := bi.PCToFunc(pc)
if fn != nil && fn.Name == runtimeStackBarrier {
// We caught the goroutine as it's executing the stack barrier, we must
// determine whether or not g.stackPos has already been incremented or not.
@ -211,7 +209,7 @@ func (it *stackIterator) newStackframe(pc uint64, cfa int64, retaddr uintptr, fd
if err != nil {
it.err = err
}
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, FDE: fde, Ret: ret, addrret: uint64(retaddr), StackHi: it.stackhi}
r := Stackframe{Current: Location{PC: pc, File: f, Line: l, Fn: fn}, CFA: cfa, Ret: ret, addrret: uint64(retaddr), StackHi: it.stackhi}
if !top {
r.Call.File, r.Call.Line, r.Call.Fn = it.bi.PCToLine(pc - 1)
r.Call.PC = r.Current.PC

@ -1,9 +1,9 @@
package proc
import (
"debug/gosym"
"encoding/binary"
"errors"
"fmt"
"go/ast"
"go/token"
"path/filepath"
@ -42,7 +42,7 @@ type Location struct {
PC uint64
File string
Line int
Fn *gosym.Func
Fn *Function
}
// ThreadBlockedError is returned when the thread
@ -104,6 +104,10 @@ func next(dbp Process, stepInto bool) error {
return err
}
if topframe.Current.Fn == nil {
return fmt.Errorf("no source for pc %#x", topframe.Current.PC)
}
success := false
defer func() {
if !success {
@ -123,7 +127,7 @@ func next(dbp Process, stepInto bool) error {
}
}
text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.FDE.Begin(), topframe.FDE.End())
text, err := disassemble(thread, regs, dbp.Breakpoints(), dbp.BinInfo(), topframe.Current.Fn.Entry, topframe.Current.Fn.End)
if err != nil && stepInto {
return err
}
@ -203,7 +207,7 @@ func next(dbp Process, stepInto bool) error {
}
// Add breakpoints on all the lines in the current function
pcs, err := dbp.BinInfo().lineInfo.AllPCsBetween(topframe.FDE.Begin(), topframe.FDE.End()-1, topframe.Current.File)
pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1)
if err != nil {
return err
}
@ -211,14 +215,14 @@ func next(dbp Process, stepInto bool) error {
if !csource {
var covered bool
for i := range pcs {
if topframe.FDE.Cover(pcs[i]) {
if topframe.Current.Fn.Entry <= pcs[i] && pcs[i] < topframe.Current.Fn.End {
covered = true
break
}
}
if !covered {
fn := dbp.BinInfo().goSymTable.PCToFunc(topframe.Ret)
fn := dbp.BinInfo().PCToFunc(topframe.Ret)
if selg != nil && fn != nil && fn.Name == "runtime.goexit" {
return nil
}

@ -8,6 +8,7 @@ import (
"go/ast"
"go/constant"
"go/token"
"path/filepath"
"reflect"
"sort"
"strconv"
@ -16,6 +17,7 @@ import (
"unsafe"
"github.com/derekparker/delve/pkg/dwarf/godwarf"
"github.com/derekparker/delve/pkg/dwarf/line"
"github.com/derekparker/delve/pkg/dwarf/reader"
)
@ -155,62 +157,128 @@ func (bi *BinaryInfo) loadPackageMap() error {
return nil
}
type sortFunctionsDebugInfoByLowpc []functionDebugInfo
type functionsDebugInfoByEntry []Function
func (v sortFunctionsDebugInfoByLowpc) Len() int { return len(v) }
func (v sortFunctionsDebugInfoByLowpc) Less(i, j int) bool { return v[i].lowpc < v[j].lowpc }
func (v sortFunctionsDebugInfoByLowpc) Swap(i, j int) {
temp := v[i]
v[i] = v[j]
v[j] = temp
}
func (v functionsDebugInfoByEntry) Len() int { return len(v) }
func (v functionsDebugInfoByEntry) Less(i, j int) bool { return v[i].Entry < v[j].Entry }
func (v functionsDebugInfoByEntry) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
func (bi *BinaryInfo) loadDebugInfoMaps(wg *sync.WaitGroup) {
type compileUnitsByLowpc []*compileUnit
func (v compileUnitsByLowpc) Len() int { return len(v) }
func (v compileUnitsByLowpc) Less(i int, j int) bool { return v[i].LowPC < v[j].LowPC }
func (v compileUnitsByLowpc) Swap(i int, j int) { v[i], v[j] = v[j], v[i] }
func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGroup) {
defer wg.Done()
bi.types = make(map[string]dwarf.Offset)
bi.packageVars = make(map[string]dwarf.Offset)
bi.functions = []functionDebugInfo{}
bi.Functions = []Function{}
bi.compileUnits = []*compileUnit{}
reader := bi.DwarfReader()
var cu *compileUnit = nil
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
break
}
switch entry.Tag {
case dwarf.TagCompileUnit:
cu = &compileUnit{}
cu.entry = entry
if lang, _ := entry.Val(dwarf.AttrLanguage).(int64); lang == dwarfGoLanguage {
cu.isgo = true
}
cu.Name, _ = entry.Val(dwarf.AttrName).(string)
compdir, _ := entry.Val(dwarf.AttrCompDir).(string)
if compdir != "" {
cu.Name = filepath.Join(compdir, cu.Name)
}
if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 {
cu.LowPC = ranges[0][0]
cu.HighPC = ranges[0][1]
}
lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64)
if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) {
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]))
}
bi.compileUnits = append(bi.compileUnits, cu)
case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType:
if name, ok := entry.Val(dwarf.AttrName).(string); ok {
if !cu.isgo {
name = "C." + name
}
if _, exists := bi.types[name]; !exists {
bi.types[name] = entry.Offset
}
}
reader.SkipChildren()
case dwarf.TagVariable:
if n, ok := entry.Val(dwarf.AttrName).(string); ok {
if !cu.isgo {
n = "C." + n
}
bi.packageVars[n] = entry.Offset
}
case dwarf.TagSubprogram:
lowpc, ok1 := entry.Val(dwarf.AttrLowpc).(uint64)
highpc, ok2 := entry.Val(dwarf.AttrHighpc).(uint64)
ok1 := false
var lowpc, highpc uint64
if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 {
ok1 = true
lowpc = ranges[0][0]
highpc = ranges[0][1]
}
name, ok2 := entry.Val(dwarf.AttrName).(string)
if ok1 && ok2 {
bi.functions = append(bi.functions, functionDebugInfo{lowpc, highpc, entry.Offset})
if !cu.isgo {
name = "C." + name
}
bi.Functions = append(bi.Functions, Function{
Name: name,
Entry: lowpc, End: highpc,
offset: entry.Offset,
cu: cu,
})
}
reader.SkipChildren()
}
}
sort.Sort(sortFunctionsDebugInfoByLowpc(bi.functions))
sort.Sort(compileUnitsByLowpc(bi.compileUnits))
sort.Sort(functionsDebugInfoByEntry(bi.Functions))
bi.LookupFunc = make(map[string]*Function)
for i := range bi.Functions {
bi.LookupFunc[bi.Functions[i].Name] = &bi.Functions[i]
}
func (bi *BinaryInfo) findFunctionDebugInfo(pc uint64) (dwarf.Offset, error) {
i := sort.Search(len(bi.functions), func(i int) bool {
fn := bi.functions[i]
return pc <= fn.lowpc || (fn.lowpc <= pc && pc < fn.highpc)
})
if i != len(bi.functions) {
fn := bi.functions[i]
if fn.lowpc <= pc && pc < fn.highpc {
return fn.offset, nil
bi.Sources = []string{}
for _, cu := range bi.compileUnits {
if cu.lineInfo != nil {
for _, fileEntry := range cu.lineInfo.FileNames {
bi.Sources = append(bi.Sources, fileEntry.Path)
}
}
return 0, errors.New("unable to find function context")
}
sort.Strings(bi.Sources)
bi.Sources = uniq(bi.Sources)
}
func uniq(s []string) []string {
if len(s) <= 0 {
return s
}
src, dst := 1, 1
for src < len(s) {
if s[src] != s[dst-1] {
s[dst] = s[src]
dst++
}
src++
}
return s[:dst]
}
func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) {

@ -511,7 +511,7 @@ func (g *G) UserCurrent() Location {
// Go returns the location of the 'go' statement
// that spawned this goroutine.
func (g *G) Go() Location {
f, l, fn := g.variable.bi.goSymTable.PCToLine(g.GoPC)
f, l, fn := g.variable.bi.PCToLine(g.GoPC)
return Location{PC: g.GoPC, File: f, Line: l, Fn: fn}
}
@ -1203,7 +1203,7 @@ func (v *Variable) readFunctionPtr() {
}
v.Base = uintptr(binary.LittleEndian.Uint64(val))
fn := v.bi.goSymTable.PCToFunc(uint64(v.Base))
fn := v.bi.PCToFunc(uint64(v.Base))
if fn == nil {
v.Unreadable = fmt.Errorf("could not find function for %#v", v.Base)
return
@ -1660,14 +1660,14 @@ func (v *variablesByDepth) Swap(i int, j int) {
// Fetches all variables of a specific type in the current function scope
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Variable, error) {
off, err := scope.BinInfo.findFunctionDebugInfo(scope.PC)
if err != nil {
return nil, err
fn := scope.BinInfo.PCToFunc(scope.PC)
if fn == nil {
return nil, errors.New("unable to find function context")
}
var vars []*Variable
var depths []int
varReader := reader.Variables(scope.BinInfo.dwarf, off, scope.PC, tag == dwarf.TagVariable)
varReader := reader.Variables(scope.BinInfo.dwarf, fn.offset, scope.PC, tag == dwarf.TagVariable)
hasScopes := false
for varReader.Next() {
entry := varReader.Entry()

@ -2,7 +2,6 @@ package api
import (
"bytes"
"debug/gosym"
"go/constant"
"go/printer"
"go/token"
@ -187,16 +186,20 @@ func ConvertVar(v *proc.Variable) *Variable {
// ConvertFunction converts from gosym.Func to
// api.Function.
func ConvertFunction(fn *gosym.Func) *Function {
func ConvertFunction(fn *proc.Function) *Function {
if fn == nil {
return nil
}
// fn here used to be a *gosym.Func, the fields Type and GoType below
// corresponded to the omonymous field of gosym.Func. Since the contents of
// those fields is not documented their value was replaced with 0 when
// gosym.Func was replaced by debug_info entries.
return &Function{
Name: fn.Name,
Type: fn.Type,
Value: fn.Value,
GoType: fn.GoType,
Type: 0,
Value: fn.Entry,
GoType: 0,
}
}

@ -1,7 +1,6 @@
package debugger
import (
"debug/gosym"
"errors"
"fmt"
"go/parser"
@ -305,7 +304,7 @@ func (d *Debugger) CreateBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoin
if runtime.GOOS == "windows" {
// Accept fileName which is case-insensitive and slash-insensitive match
fileNameNormalized := strings.ToLower(filepath.ToSlash(fileName))
for symFile := range d.target.BinInfo().Sources() {
for _, symFile := range d.target.BinInfo().Sources {
if fileNameNormalized == strings.ToLower(filepath.ToSlash(symFile)) {
fileName = symFile
break
@ -637,7 +636,7 @@ func (d *Debugger) Sources(filter string) ([]string, error) {
}
files := []string{}
for f := range d.target.BinInfo().Sources() {
for _, f := range d.target.BinInfo().Sources {
if regex.Match([]byte(f)) {
files = append(files, f)
}
@ -650,7 +649,7 @@ func (d *Debugger) Functions(filter string) ([]string, error) {
d.processMutex.Lock()
defer d.processMutex.Unlock()
return regexFilterFuncs(filter, d.target.BinInfo().Funcs())
return regexFilterFuncs(filter, d.target.BinInfo().Functions)
}
func (d *Debugger) Types(filter string) ([]string, error) {
@ -677,7 +676,7 @@ func (d *Debugger) Types(filter string) ([]string, error) {
return r, nil
}
func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
func regexFilterFuncs(filter string, allFuncs []proc.Function) ([]string, error) {
regex, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter argument: %s", err.Error())
@ -685,7 +684,7 @@ func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) {
funcs := []string{}
for _, f := range allFuncs {
if f.Sym != nil && regex.Match([]byte(f.Name)) {
if regex.Match([]byte(f.Name)) {
funcs = append(funcs, f.Name)
}
}

@ -1,7 +1,6 @@
package debugger
import (
"debug/gosym"
"fmt"
"go/constant"
"path/filepath"
@ -216,7 +215,7 @@ func stripReceiverDecoration(in string) string {
return in[2 : len(in)-1]
}
func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
func (spec *FuncLocationSpec) Match(sym proc.Function) bool {
if spec.BaseName != sym.BaseName() {
return false
}
@ -243,7 +242,7 @@ func (spec *FuncLocationSpec) Match(sym *gosym.Sym) bool {
}
func (loc *RegexLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
funcs := d.target.BinInfo().Funcs()
funcs := d.target.BinInfo().Functions
matches, err := regexFilterFuncs(loc.FuncRegex, funcs)
if err != nil {
return nil, err
@ -329,7 +328,7 @@ func (ale AmbiguousLocationError) Error() string {
func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr string) ([]api.Location, error) {
limit := maxFindLocationCandidates
var candidateFiles []string
for file := range d.target.BinInfo().Sources() {
for _, file := range d.target.BinInfo().Sources {
if loc.FileMatch(file) {
candidateFiles = append(candidateFiles, file)
if len(candidateFiles) >= limit {
@ -342,11 +341,8 @@ func (loc *NormalLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr s
var candidateFuncs []string
if loc.FuncBase != nil {
for _, f := range d.target.BinInfo().Funcs() {
if f.Sym == nil {
continue
}
if !loc.FuncBase.Match(f.Sym) {
for _, f := range d.target.BinInfo().Functions {
if !loc.FuncBase.Match(f) {
continue
}
if loc.Base == f.Name {