提交 f3b149bd 编写于 作者: A Alessandro Arzilli 提交者: Derek Parker

proc: support debugging plugins (#1414)

This change splits the BinaryInfo object into a slice of Image objects
containing information about the base executable and each loaded shared
library (note: go plugins are shared libraries).

Delve backens are supposed to call BinaryInfo.AddImage whenever they
detect that a new shared library has been loaded.

Member fields of BinaryInfo that are used to speed up access to dwarf
(Functions, packageVars, consts, etc...) remain part of BinaryInfo and
are updated to reference the correct image object. This simplifies this
change.

This approach has a few shortcomings:

1. Multiple shared libraries can define functions or globals with the
   same name and we have no way to disambiguate between them.

2. We don't have a way to handle library unloading.

Both of those affect C shared libraries much more than they affect go
plugins. Go plugins can't be unloaded at all and a lot of name
collisions are prevented by import paths.

There's only one problem that is concerning: if two plugins both import
the same package they will end up with multiple definition for the same
function.
For example if two plugins use fmt.Printf the final in-memory image
(and therefore our BinaryInfo object) will end up with two copies of
fmt.Printf at different memory addresses. If a user types
  break fmt.Printf
a breakpoint should be created at *both* locations.
Allowing this is a relatively complex change that should be done in a
different PR than this.

For this reason I consider this approach an acceptable and sustainable
stopgap.

Updates #865
上级 71a7fe04
......@@ -75,3 +75,12 @@ func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry,
}
return fdes[idx], nil
}
// Append appends otherFDEs to fdes and returns the result.
func (fdes FrameDescriptionEntries) Append(otherFDEs FrameDescriptionEntries) FrameDescriptionEntries {
r := append(fdes, otherFDEs...)
sort.Slice(r, func(i, j int) bool {
return r[i].Begin() < r[j].Begin()
})
return r
}
......@@ -71,6 +71,7 @@ type Type interface {
// If a field is not known or not applicable for a given type,
// the zero value is used.
type CommonType struct {
Index int // index supplied by caller of ReadType
ByteSize int64 // size of value of this type, in bytes
Name string // name that can be used to refer to type
ReflectKind reflect.Kind // the reflect kind of the type.
......@@ -478,8 +479,12 @@ func (t *ChanType) stringIntl(recCheck recCheck) string {
}
// Type reads the type at off in the DWARF ``info'' section.
func ReadType(d *dwarf.Data, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) {
return readType(d, "info", d.Reader(), off, typeCache)
func ReadType(d *dwarf.Data, index int, off dwarf.Offset, typeCache map[dwarf.Offset]Type) (Type, error) {
typ, err := readType(d, "info", d.Reader(), off, typeCache)
if typ != nil {
typ.Common().Index = index
}
return typ, err
}
func getKind(e *dwarf.Entry) reflect.Kind {
......
......@@ -17,7 +17,7 @@ type Arch interface {
DerefTLS() bool
FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext
RegSize(uint64) int
RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters
RegistersToDwarfRegisters(bi *BinaryInfo, regs Registers) op.DwarfRegisters
GoroutineToDwarfRegisters(*G) op.DwarfRegisters
}
......@@ -270,7 +270,7 @@ func maxAmd64DwarfRegister() int {
// RegistersToDwarfRegisters converts hardware registers to the format used
// by the DWARF expression interpreter.
func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters {
func (a *AMD64) RegistersToDwarfRegisters(bi *BinaryInfo, regs Registers) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, maxAmd64DwarfRegister()+1)
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC())
......@@ -292,7 +292,9 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.
}
}
return op.DwarfRegisters{StaticBase: staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
so := bi.pcToImage(regs.PC())
return op.DwarfRegisters{StaticBase: so.StaticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
}
// GoroutineToDwarfRegisters extract the saved DWARF registers from a parked
......@@ -302,5 +304,8 @@ func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters {
dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC)
dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP)
dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP)
return op.DwarfRegisters{StaticBase: g.variable.bi.staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
so := g.variable.bi.pcToImage(g.PC)
return op.DwarfRegisters{StaticBase: so.StaticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum}
}
此差异已折叠。
......@@ -28,7 +28,7 @@ func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) *proc.BinaryInfo {
assertNoError(err, t, "creating dwarf")
bi := proc.NewBinaryInfo("linux", "amd64")
bi.LoadFromData(dwdata, frame, line, loc)
bi.LoadImageFromData(dwdata, frame, line, loc)
return bi
}
......@@ -88,9 +88,9 @@ func dwarfExprCheck(t *testing.T, mem proc.MemoryReadWriter, regs op.DwarfRegist
return scope
}
func dwarfRegisters(regs *linutil.AMD64Registers) op.DwarfRegisters {
func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) op.DwarfRegisters {
a := proc.AMD64Arch("linux")
dwarfRegs := a.RegistersToDwarfRegisters(regs, 0)
dwarfRegs := a.RegistersToDwarfRegisters(bi, regs)
dwarfRegs.CFA = defaultCFA
dwarfRegs.FrameBase = defaultCFA
return dwarfRegs
......@@ -123,7 +123,7 @@ func TestDwarfExprRegisters(t *testing.T) {
regs.Regs.Rax = uint64(testCases["a"])
regs.Regs.Rdx = uint64(testCases["c"])
dwarfExprCheck(t, mem, dwarfRegisters(&regs), bi, testCases, mainfn)
dwarfExprCheck(t, mem, dwarfRegisters(bi, &regs), bi, testCases, mainfn)
}
func TestDwarfExprComposite(t *testing.T) {
......@@ -178,7 +178,7 @@ func TestDwarfExprComposite(t *testing.T) {
regs.Regs.Rcx = uint64(testCases["pair.k"])
regs.Regs.Rbx = uint64(testCases["n"])
scope := dwarfExprCheck(t, mem, dwarfRegisters(&regs), bi, testCases, mainfn)
scope := dwarfExprCheck(t, mem, dwarfRegisters(bi, &regs), bi, testCases, mainfn)
thevar, err := scope.EvalExpression("s", normalLoadConfig)
assertNoError(err, t, fmt.Sprintf("EvalExpression(%s)", "s"))
......@@ -213,7 +213,7 @@ func TestDwarfExprLoclist(t *testing.T) {
mem := newFakeMemory(defaultCFA, uint16(before), uint16(after))
regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}}
scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: dwarfRegisters(&regs), Mem: mem, Gvar: nil, BinInfo: bi}
scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: dwarfRegisters(bi, &regs), Mem: mem, Gvar: nil, BinInfo: bi}
uintExprCheck(t, scope, "a", before)
scope.PC = 0x40800
......
......@@ -295,7 +295,7 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo
func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) {
const CFA = 0x1000
vrdr := reader.Variables(bi.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, bi.staticBase), int(^uint(0)>>1), false)
vrdr := reader.Variables(fn.cu.image.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, fn.cu.image.StaticBase), int(^uint(0)>>1), false)
// typechecks arguments, calculates argument frame size
for vrdr.Next() {
......@@ -303,7 +303,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
if e.Tag != dwarf.TagFormalParameter {
continue
}
entry, argname, typ, err := readVarEntry(e, bi)
entry, argname, typ, err := readVarEntry(e, fn.cu.image)
if err != nil {
return 0, nil, err
}
......@@ -548,8 +548,8 @@ func fakeFunctionEntryScope(scope *EvalScope, fn *Function, cfa int64, sp uint64
scope.Regs.CFA = cfa
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = sp
scope.BinInfo.dwarfReader.Seek(fn.offset)
e, err := scope.BinInfo.dwarfReader.Next()
fn.cu.image.dwarfReader.Seek(fn.offset)
e, err := fn.cu.image.dwarfReader.Next()
if err != nil {
return err
}
......
......@@ -7,76 +7,82 @@ import (
// delve counterpart to runtime.moduledata
type moduleData struct {
text, etext uintptr
types, etypes uintptr
typemapVar *Variable
}
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) (err error) {
bi.loadModuleDataOnce.Do(func() {
scope := globalScope(bi, mem)
var md *Variable
md, err = scope.findGlobal("runtime.firstmoduledata")
if err != nil {
return
}
for md.Addr != 0 {
var typesVar, etypesVar, nextVar, typemapVar *Variable
var types, etypes uint64
func loadModuleData(bi *BinaryInfo, mem MemoryReadWriter) ([]moduleData, error) {
scope := globalScope(bi, bi.Images[0], mem)
var md *Variable
md, err := scope.findGlobal("runtime.firstmoduledata")
if err != nil {
return nil, err
}
if typesVar, err = md.structMember("types"); err != nil {
return
}
if etypesVar, err = md.structMember("etypes"); err != nil {
return
}
if nextVar, err = md.structMember("next"); err != nil {
return
}
if typemapVar, err = md.structMember("typemap"); err != nil {
return
}
if types, err = typesVar.asUint(); err != nil {
return
}
if etypes, err = etypesVar.asUint(); err != nil {
return
r := []moduleData{}
for md.Addr != 0 {
const (
typesField = "types"
etypesField = "etypes"
textField = "text"
etextField = "etext"
nextField = "next"
typemapField = "typemap"
)
vars := map[string]*Variable{}
for _, fieldName := range []string{typesField, etypesField, textField, etextField, nextField, typemapField} {
var err error
vars[fieldName], err = md.structMember(fieldName)
if err != nil {
return nil, err
}
bi.moduleData = append(bi.moduleData, moduleData{uintptr(types), uintptr(etypes), typemapVar})
}
var err error
md = nextVar.maybeDereference()
if md.Unreadable != nil {
err = md.Unreadable
return
touint := func(name string) (ret uintptr) {
if err == nil {
var n uint64
n, err = vars[name].asUint()
ret = uintptr(n)
}
return ret
}
})
return
}
r = append(r, moduleData{
types: touint(typesField), etypes: touint(etypesField),
text: touint(textField), etext: touint(etextField),
typemapVar: vars[typemapField],
})
if err != nil {
return nil, err
}
func findModuleDataForType(bi *BinaryInfo, typeAddr uintptr, mem MemoryReadWriter) (*moduleData, error) {
if err := loadModuleData(bi, mem); err != nil {
return nil, err
md = vars[nextField].maybeDereference()
if md.Unreadable != nil {
return nil, md.Unreadable
}
}
var md *moduleData
for i := range bi.moduleData {
if typeAddr >= bi.moduleData[i].types && typeAddr < bi.moduleData[i].etypes {
md = &bi.moduleData[i]
return r, nil
}
func findModuleDataForType(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, mem MemoryReadWriter) *moduleData {
for i := range mds {
if typeAddr >= mds[i].types && typeAddr < mds[i].etypes {
return &mds[i]
}
}
return md, nil
return nil
}
func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (*Variable, error) {
func resolveTypeOff(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (*Variable, error) {
// See runtime.(*_type).typeOff in $GOROOT/src/runtime/type.go
md, err := findModuleDataForType(bi, typeAddr, mem)
if err != nil {
return nil, err
}
md := findModuleDataForType(bi, mds, typeAddr, mem)
rtyp, err := bi.findType("runtime._type")
if err != nil {
......@@ -102,13 +108,9 @@ func resolveTypeOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
return newVariable("", res, rtyp, bi, mem), nil
}
func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
func resolveNameOff(bi *BinaryInfo, mds []moduleData, typeAddr uintptr, off uintptr, mem MemoryReadWriter) (name, tag string, pkgpathoff int32, err error) {
// See runtime.resolveNameOff in $GOROOT/src/runtime/type.go
if err = loadModuleData(bi, mem); err != nil {
return "", "", 0, err
}
for _, md := range bi.moduleData {
for _, md := range mds {
if typeAddr >= md.types && typeAddr < md.etypes {
return loadName(bi, md.types+off, mem)
}
......@@ -128,7 +130,7 @@ func resolveNameOff(bi *BinaryInfo, typeAddr uintptr, off uintptr, mem MemoryRea
}
func reflectOffsMapAccess(bi *BinaryInfo, off uintptr, mem MemoryReadWriter) (*Variable, error) {
scope := globalScope(bi, mem)
scope := globalScope(bi, bi.Images[0], mem)
reflectOffs, err := scope.findGlobal("runtime.reflectOffs")
if err != nil {
return nil, err
......
......@@ -58,12 +58,14 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
}
err = p.BinInfo().LoadBinaryInfo(path, entryPoint, debugInfoDirs)
if err == nil {
err = p.BinInfo().LoadError()
}
if err != nil {
return err
}
for _, image := range p.BinInfo().Images {
if image.loadErr != nil {
return image.loadErr
}
}
g, _ := GetG(p.CurrentThread())
p.SetSelectedGoroutine(g)
......@@ -515,10 +517,12 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
}
}
exeimage := dbp.BinInfo().Images[0] // Image corresponding to the executable file
var (
threadg = map[int]*G{}
allg []*G
rdr = dbp.BinInfo().DwarfReader()
rdr = exeimage.DwarfReader()
)
threads := dbp.ThreadList()
......@@ -532,7 +536,7 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
}
}
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
addr, err := rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
if err != nil {
return nil, -1, err
}
......@@ -544,10 +548,10 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
allglen := binary.LittleEndian.Uint64(allglenBytes)
rdr.Seek(0)
allgentryaddr, err := rdr.AddrFor("runtime.allgs", dbp.BinInfo().staticBase)
allgentryaddr, err := rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
if err != nil {
// try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
allgentryaddr, err = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
if err != nil {
return nil, -1, err
}
......
......@@ -15,6 +15,7 @@ import (
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
"testing"
"time"
......@@ -184,7 +185,11 @@ func setFunctionBreakpoint(p proc.Process, fname string) (*proc.Breakpoint, erro
}
func setFileBreakpoint(p proc.Process, t *testing.T, fixture protest.Fixture, lineno int) *proc.Breakpoint {
addr, err := proc.FindFileLocation(p, fixture.Source, lineno)
return setFileLineBreakpoint(p, t, fixture.Source, lineno)
}
func setFileLineBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *proc.Breakpoint {
addr, err := proc.FindFileLocation(p, path, lineno)
if err != nil {
t.Fatalf("FindFileLocation: %v", err)
}
......@@ -366,7 +371,7 @@ const (
type seqTest struct {
cf contFunc
pos int
pos interface{}
}
func testseq(program string, contFunc contFunc, testcases []nextTest, initialLocation string, t *testing.T) {
......@@ -398,7 +403,7 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te
bp, err = setFunctionBreakpoint(p, initialLocation)
} else if testcases[0].cf == contContinue {
var pc uint64
pc, err = proc.FindFileLocation(p, fixture.Source, testcases[0].pos)
pc, err = proc.FindFileLocation(p, fixture.Source, testcases[0].pos.(int))
assertNoError(err, t, "FindFileLocation()")
bp, err = p.SetBreakpoint(pc, proc.UserBreakpoint, nil)
} else {
......@@ -449,9 +454,19 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te
if traceTestseq2 {
t.Logf("at %#x %s:%d", pc, f, ln)
fmt.Printf("at %#x %s:%d", pc, f, ln)
}
if ln != tc.pos {
t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", tc.pos, filepath.Base(f), ln, pc, i)
switch pos := tc.pos.(type) {
case int:
if ln != pos {
t.Fatalf("Program did not continue to correct next location expected %d was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i)
}
case string:
v := strings.Split(pos, ":")
tgtln, _ := strconv.Atoi(v[1])
if !strings.HasSuffix(f, v[0]) || (ln != tgtln) {
t.Fatalf("Program did not continue to correct next location, expected %s was %s:%d (%#x) (testcase %d)", pos, filepath.Base(f), ln, pc, i)
}
}
}
......@@ -4220,10 +4235,11 @@ func TestListImages(t *testing.T) {
withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "first continue")
f, l := currentLineNumber(p, t)
plugin1Found := false
t.Logf("Libraries before:")
t.Logf("Libraries before %s:%d:", f, l)
for _, image := range p.BinInfo().Images {
t.Logf("\t%#v", image)
t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError())
if image.Path == pluginFixtures[0].Path {
plugin1Found = true
}
......@@ -4232,10 +4248,11 @@ func TestListImages(t *testing.T) {
t.Fatalf("Could not find plugin1")
}
assertNoError(proc.Continue(p), t, "second continue")
f, l = currentLineNumber(p, t)
plugin1Found, plugin2Found := false, false
t.Logf("Libraries after:")
t.Logf("Libraries after %s:%d:", f, l)
for _, image := range p.BinInfo().Images {
t.Logf("\t%#v", image)
t.Logf("\t%#x %q err:%v", image.StaticBase, image.Path, image.LoadError())
switch image.Path {
case pluginFixtures[0].Path:
plugin1Found = true
......@@ -4332,3 +4349,20 @@ func TestCallConcurrent(t *testing.T) {
proc.Continue(p)
})
}
func TestPluginStepping(t *testing.T) {
pluginFixtures := protest.WithPlugins(t, "plugin1/", "plugin2/")
testseq2Args(".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, t, "plugintest2", "", []seqTest{
{contContinue, 41},
{contStep, "plugin1.go:9"},
{contStep, "plugin1.go:10"},
{contStep, "plugin1.go:11"},
{contNext, "plugin1.go:12"},
{contNext, "plugintest2.go:41"},
{contNext, "plugintest2.go:42"},
{contStep, "plugin2.go:22"},
{contNext, "plugin2.go:23"},
{contNext, "plugin2.go:26"},
{contNext, "plugintest2.go:42"}})
}
......@@ -100,7 +100,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) {
if err != nil {
return nil, err
}
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs, thread.BinInfo().staticBase), 0, nil, -1, nil)
it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(thread.BinInfo(), regs), 0, nil, -1, nil)
return it.stacktrace(depth)
}
return g.Stacktrace(depth, false)
......@@ -117,7 +117,7 @@ func (g *G) stackIterator() (*stackIterator, error) {
if err != nil {
return nil, err
}
return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs, g.variable.bi.staticBase), g.stackhi, stkbar, g.stkbarPos, g), nil
return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(g.variable.bi, regs), g.stackhi, stkbar, g.stkbarPos, g), nil
}
return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil
}
......@@ -168,8 +168,6 @@ type stackIterator struct {
g *G // the goroutine being stacktraced, nil if we are stacktracing a goroutine-less thread
g0_sched_sp uint64 // value of g0.sched.sp (see comments around its use)
dwarfReader *dwarf.Reader
}
type savedLR struct {
......@@ -209,7 +207,7 @@ func newStackIterator(bi *BinaryInfo, mem MemoryReadWriter, regs op.DwarfRegiste
}
}
}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp, dwarfReader: bi.dwarf.Reader()}
return &stackIterator{pc: regs.PC(), regs: regs, top: true, bi: bi, mem: mem, err: nil, atend: false, stackhi: stackhi, stackBarrierPC: stackBarrierPC, stkbar: stkbar, systemstack: systemstack, g: g, g0_sched_sp: g0_sched_sp}
}
// Next points the iterator to the next stack frame.
......@@ -353,8 +351,9 @@ func (it *stackIterator) Err() error {
// frameBase calculates the frame base pseudo-register for DWARF for fn and
// the current frame.
func (it *stackIterator) frameBase(fn *Function) int64 {
it.dwarfReader.Seek(fn.offset)
e, err := it.dwarfReader.Next()
rdr := fn.cu.image.dwarfReader
rdr.Seek(fn.offset)
e, err := rdr.Next()
if err != nil {
return 0
}
......@@ -427,9 +426,11 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe
callpc--
}
irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, it.bi.staticBase))
image := frame.Call.Fn.cu.image
irdr := reader.InlineStack(image.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, image.StaticBase))
for irdr.Next() {
entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), it.dwarfReader)
entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), image.dwarfReader)
fnname, okname := entry.Val(dwarf.AttrName).(string)
fileidx, okfileidx := entry.Val(dwarf.AttrCallFile).(int64)
......@@ -483,11 +484,13 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0)
if cfareg == nil {
it.err = fmt.Errorf("CFA becomes undefined at PC %#x", it.pc)
return op.DwarfRegisters{StaticBase: it.bi.staticBase}, 0, 0
return op.DwarfRegisters{}, 0, 0
}
it.regs.CFA = int64(cfareg.Uint64Val)
callFrameRegs = op.DwarfRegisters{StaticBase: it.bi.staticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
callimage := it.bi.pcToImage(it.pc)
callFrameRegs = op.DwarfRegisters{StaticBase: callimage.StaticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum}
// According to the standard the compiler should be responsible for emitting
// rules for the RSP register so that it can then be used to calculate CFA,
......@@ -699,8 +702,9 @@ func (d *Defer) EvalScope(thread Thread) (*EvalScope, error) {
scope.Regs.CFA = (int64(d.variable.Addr) + d.variable.RealType.Common().ByteSize)
scope.Regs.Regs[scope.Regs.SPRegNum].Uint64Val = uint64(scope.Regs.CFA - int64(bi.Arch.PtrSize()))
bi.dwarfReader.Seek(scope.Fn.offset)
e, err := bi.dwarfReader.Next()
rdr := scope.Fn.cu.image.dwarfReader
rdr.Seek(scope.Fn.offset)
e, err := rdr.Next()
if err != nil {
return nil, fmt.Errorf("could not read DWARF function entry: %v", err)
}
......
......@@ -343,19 +343,20 @@ func findDeferReturnCalls(text []AsmInstruction) []uint64 {
// If includeCurrentFn is true it will also remove all instructions
// belonging to the current function.
func removeInlinedCalls(dbp Process, pcs []uint64, topframe Stackframe) ([]uint64, error) {
bi := dbp.BinInfo()
irdr := reader.InlineStack(bi.dwarf, topframe.Call.Fn.offset, 0)
image := topframe.Call.Fn.cu.image
dwarf := image.dwarf
irdr := reader.InlineStack(dwarf, topframe.Call.Fn.offset, 0)
for irdr.Next() {
e := irdr.Entry()
if e.Offset == topframe.Call.Fn.offset {
continue
}
ranges, err := bi.dwarf.Ranges(e)
ranges, err := dwarf.Ranges(e)
if err != nil {
return pcs, err
}
for _, rng := range ranges {
pcs = removePCsBetween(pcs, rng[0], rng[1], bi.staticBase)
pcs = removePCsBetween(pcs, rng[0], rng[1], image.StaticBase)
}
irdr.SkipChildren()
}
......
此差异已折叠。
......@@ -232,8 +232,8 @@ func (err *IsNilErr) Error() string {
return fmt.Sprintf("%s is nil", err.name)
}
func globalScope(bi *BinaryInfo, mem MemoryReadWriter) *EvalScope {
return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: bi.staticBase}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0}
func globalScope(bi *BinaryInfo, image *Image, mem MemoryReadWriter) *EvalScope {
return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: image.StaticBase}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0}
}
func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType godwarf.Type, mem MemoryReadWriter) *Variable {
......@@ -434,10 +434,23 @@ func (v *Variable) toField(field *godwarf.StructField) (*Variable, error) {
return v.newVariable(name, uintptr(int64(v.Addr)+field.ByteOffset), field.Type, v.mem), nil
}
// image returns the image containing the current function.
func (scope *EvalScope) image() *Image {
return scope.BinInfo.funcToImage(scope.Fn)
}
// globalFor returns a global scope for 'image' with the register values of 'scope'.
func (scope *EvalScope) globalFor(image *Image) *EvalScope {
r := *scope
r.Regs.StaticBase = image.StaticBase
r.Fn = &Function{cu: &compileUnit{image: image}}
return &r
}
// DwarfReader returns the DwarfReader containing the
// Dwarf information for the target process.
func (scope *EvalScope) DwarfReader() *reader.Reader {
return scope.BinInfo.DwarfReader()
return scope.image().DwarfReader()
}
// PtrSize returns the size of a pointer.
......@@ -630,7 +643,7 @@ var errTracebackAncestorsDisabled = errors.New("tracebackancestors is disabled")
// Ancestors returns the list of ancestors for g.
func (g *G) Ancestors(n int) ([]Ancestor, error) {
scope := globalScope(g.Thread.BinInfo(), g.Thread)
scope := globalScope(g.Thread.BinInfo(), g.Thread.BinInfo().Images[0], g.Thread)
tbav, err := scope.EvalExpression("runtime.debug.tracebackancestors", loadSingleValue)
if err == nil && tbav.Unreadable == nil && tbav.Kind == reflect.Int {
tba, _ := constant.Int64Val(tbav.Value)
......@@ -820,30 +833,35 @@ func filterVariables(vars []*Variable, pred func(v *Variable) bool) []*Variable
// PackageVariables returns the name, value, and type of all package variables in the application.
func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
var vars []*Variable
reader := scope.DwarfReader()
var utypoff dwarf.Offset
utypentry, err := reader.SeekToTypeNamed("<unspecified>")
if err == nil {
utypoff = utypentry.Offset
}
for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() {
if err != nil {
return nil, err
for _, image := range scope.BinInfo.Images {
if image.loadErr != nil {
continue
}
reader := reader.New(image.dwarf)
if typoff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset); !ok || typoff == utypoff {
continue
var utypoff dwarf.Offset
utypentry, err := reader.SeekToTypeNamed("<unspecified>")
if err == nil {
utypoff = utypentry.Offset
}
// Ignore errors trying to extract values
val, err := scope.extractVarInfoFromEntry(entry)
if err != nil {
continue
for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() {
if err != nil {
return nil, err
}
if typoff, ok := entry.Val(dwarf.AttrType).(dwarf.Offset); !ok || typoff == utypoff {
continue
}
// Ignore errors trying to extract values
val, err := scope.globalFor(image).extractVarInfoFromEntry(entry)
if err != nil {
continue
}
val.loadValue(cfg)
vars = append(vars, val)
}
val.loadValue(cfg)
vars = append(vars, val)
}
return vars, nil
......@@ -852,33 +870,33 @@ func (scope *EvalScope) PackageVariables(cfg LoadConfig) ([]*Variable, error) {
func (scope *EvalScope) findGlobal(name string) (*Variable, error) {
for _, pkgvar := range scope.BinInfo.packageVars {
if pkgvar.name == name || strings.HasSuffix(pkgvar.name, "/"+name) {
reader := scope.DwarfReader()
reader := pkgvar.cu.image.dwarfReader
reader.Seek(pkgvar.offset)
entry, err := reader.Next()
if err != nil {
return nil, err
}
return scope.extractVarInfoFromEntry(entry)
return scope.globalFor(pkgvar.cu.image).extractVarInfoFromEntry(entry)
}
}
for _, fn := range scope.BinInfo.Functions {
if fn.Name == name || strings.HasSuffix(fn.Name, "/"+name) {
//TODO(aarzilli): convert function entry into a function type?
r := scope.newVariable(fn.Name, uintptr(fn.Entry), &godwarf.FuncType{}, scope.Mem)
r := scope.globalFor(fn.cu.image).newVariable(fn.Name, uintptr(fn.Entry), &godwarf.FuncType{}, scope.Mem)
r.Value = constant.MakeString(fn.Name)
r.Base = uintptr(fn.Entry)
r.loaded = true
return r, nil
}
}
for offset, ctyp := range scope.BinInfo.consts {
for dwref, ctyp := range scope.BinInfo.consts {
for _, cval := range ctyp.values {
if cval.fullName == name || strings.HasSuffix(cval.fullName, "/"+name) {
t, err := scope.BinInfo.Type(offset)
t, err := scope.BinInfo.Images[dwref.imageIndex].Type(dwref.offset)
if err != nil {
return nil, err
}
v := scope.newVariable(name, 0x0, t, scope.Mem)
v := scope.globalFor(scope.BinInfo.Images[0]).newVariable(name, 0x0, t, scope.Mem)
switch v.Kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v.Value = constant.MakeInt64(cval.value)
......@@ -966,8 +984,8 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
}
}
func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, name string, typ godwarf.Type, err error) {
entry, _ = reader.LoadAbstractOrigin(varEntry, bi.dwarfReader)
func readVarEntry(varEntry *dwarf.Entry, image *Image) (entry reader.Entry, name string, typ godwarf.Type, err error) {
entry, _ = reader.LoadAbstractOrigin(varEntry, image.dwarfReader)
name, ok := entry.Val(dwarf.AttrName).(string)
if !ok {
......@@ -979,7 +997,7 @@ func readVarEntry(varEntry *dwarf.Entry, bi *BinaryInfo) (entry reader.Entry, na
return nil, "", nil, fmt.Errorf("malformed variable DIE (offset)")
}
typ, err = bi.Type(offset)
typ, err = image.Type(offset)
if err != nil {
return nil, "", nil, err
}
......@@ -998,7 +1016,7 @@ func (scope *EvalScope) extractVarInfoFromEntry(varEntry *dwarf.Entry) (*Variabl
return nil, fmt.Errorf("invalid entry tag, only supports FormalParameter and Variable, got %s", varEntry.Tag.String())
}
entry, n, t, err := readVarEntry(varEntry, scope.BinInfo)
entry, n, t, err := readVarEntry(varEntry, scope.image())
if err != nil {
return nil, err
}
......@@ -2139,7 +2157,7 @@ func popcnt(x uint64) int {
}
func (cm constantsMap) Get(typ godwarf.Type) *constantType {
ctyp := cm[typ.Common().Offset]
ctyp := cm[dwarfRef{typ.Common().Index, typ.Common().Offset}]
if ctyp == nil {
return nil
}
......@@ -2211,7 +2229,7 @@ func (scope *EvalScope) Locals() ([]*Variable, error) {
var vars []*Variable
var depths []int
varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, reader.ToRelAddr(scope.PC, scope.BinInfo.staticBase), scope.Line, true)
varReader := reader.Variables(scope.image().dwarf, scope.Fn.offset, reader.ToRelAddr(scope.PC, scope.image().StaticBase), scope.Line, true)
hasScopes := false
for varReader.Next() {
entry := varReader.Entry()
......
......@@ -1636,7 +1636,7 @@ func libraries(t *Term, ctx callContext, args string) error {
}
d := digits(len(libs))
for i := range libs {
fmt.Printf("%"+strconv.Itoa(d)+"d. %s\n", i, libs[i].Path)
fmt.Printf("%"+strconv.Itoa(d)+"d. %#x %s\n", i, libs[i].Address, libs[i].Path)
}
return nil
}
......
......@@ -318,5 +318,5 @@ func ConvertCheckpoint(in proc.Checkpoint) (out Checkpoint) {
}
func ConvertImage(image *proc.Image) Image {
return Image{Path: image.Path}
return Image{Path: image.Path, Address: image.StaticBase}
}
......@@ -445,7 +445,8 @@ type Checkpoint struct {
// Image represents a loaded shared object (go plugin or shared library)
type Image struct {
Path string
Path string
Address uint64
}
// Ancestor represents a goroutine ancestor
......
......@@ -1184,9 +1184,10 @@ func (d *Debugger) ListDynamicLibraries() []api.Image {
d.processMutex.Lock()
defer d.processMutex.Unlock()
bi := d.target.BinInfo()
r := make([]api.Image, len(bi.Images))
for i := range bi.Images {
r[i] = api.ConvertImage(bi.Images[i])
r := make([]api.Image, 0, len(bi.Images)-1)
// skips the first image because it's the executable file
for i := range bi.Images[1:] {
r = append(r, api.ConvertImage(bi.Images[i]))
}
return r
}
......
......@@ -72,20 +72,19 @@ func findFirstNonRuntimeFrame(p proc.Process) (proc.Stackframe, error) {
return proc.Stackframe{}, fmt.Errorf("non-runtime frame not found")
}
func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
var scope *proc.EvalScope
var err error
if testBackend == "rr" {
var frame proc.Stackframe
frame, err = findFirstNonRuntimeFrame(p)
if err == nil {
scope = proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame)
}
} else {
scope, err = proc.GoroutineScope(p.CurrentThread())
func evalScope(p proc.Process) (*proc.EvalScope, error) {
if testBackend != "rr" {
return proc.GoroutineScope(p.CurrentThread())
}
frame, err := findFirstNonRuntimeFrame(p)
if err != nil {
return nil, err
}
return proc.FrameToScope(p.BinInfo(), p.CurrentThread(), nil, frame), nil
}
func evalVariable(p proc.Process, symbol string, cfg proc.LoadConfig) (*proc.Variable, error) {
scope, err := evalScope(p)
if err != nil {
return nil, err
}
......@@ -108,9 +107,12 @@ func setVariable(p proc.Process, symbol, value string) error {
}
func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture protest.Fixture)) {
var buildFlags protest.BuildFlags
withTestProcessArgs(name, t, ".", []string{}, 0, fn)
}
func withTestProcessArgs(name string, t *testing.T, wd string, args []string, buildFlags protest.BuildFlags, fn func(p proc.Process, fixture protest.Fixture)) {
if buildMode == "pie" {
buildFlags = protest.BuildModePIE
buildFlags |= protest.BuildModePIE
}
fixture := protest.BuildFixture(name, buildFlags)
var p proc.Process
......@@ -118,13 +120,13 @@ func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture
var tracedir string
switch testBackend {
case "native":
p, err = native.Launch([]string{fixture.Path}, ".", false, []string{})
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{})
case "lldb":
p, err = gdbserial.LLDBLaunch([]string{fixture.Path}, ".", false, []string{})
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{})
case "rr":
protest.MustHaveRecordingAllowed(t)
t.Log("recording")
p, tracedir, err = gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{})
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{})
t.Logf("replaying %q", tracedir)
default:
t.Fatalf("unknown backend %q", testBackend)
......@@ -1240,3 +1242,99 @@ func TestIssue1531(t *testing.T) {
hasKeys(mmv, "r", "t", "v")
})
}
func setFileLineBreakpoint(p proc.Process, t *testing.T, path string, lineno int) *proc.Breakpoint {
addr, err := proc.FindFileLocation(p, path, lineno)
if err != nil {
t.Fatalf("FindFileLocation: %v", err)
}
bp, err := p.SetBreakpoint(addr, proc.UserBreakpoint, nil)
if err != nil {
t.Fatalf("SetBreakpoint: %v", err)
}
return bp
}
func currentLocation(p proc.Process, t *testing.T) (pc uint64, f string, ln int, fn *proc.Function) {
regs, err := p.CurrentThread().Registers(false)
if err != nil {
t.Fatalf("Registers error: %v", err)
}
f, l, fn := p.BinInfo().PCToLine(regs.PC())
t.Logf("at %#x %s:%d %v", regs.PC(), f, l, fn)
return regs.PC(), f, l, fn
}
func assertCurrentLocationFunction(p proc.Process, t *testing.T, fnname string) {
_, _, _, fn := currentLocation(p, t)
if fn == nil {
t.Fatalf("Not in a function")
}
if fn.Name != fnname {
t.Fatalf("Wrong function %s %s", fn.Name, fnname)
}
}
func TestPluginVariables(t *testing.T) {
pluginFixtures := protest.WithPlugins(t, "plugin1/", "plugin2/")
withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, 0, func(p proc.Process, fixture protest.Fixture) {
setFileLineBreakpoint(p, t, fixture.Source, 41)
assertNoError(proc.Continue(p), t, "Continue 1")
bp, err := setFunctionBreakpoint(p, "github.com/go-delve/delve/_fixtures/plugin2.TypesTest")
assertNoError(err, t, "SetBreakpoint(TypesTest)")
t.Logf("bp.Addr = %#x", bp.Addr)
_, err = setFunctionBreakpoint(p, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil")
assertNoError(err, t, "SetBreakpoint(aIsNotNil)")
for _, image := range p.BinInfo().Images {
t.Logf("%#x %s\n", image.StaticBase, image.Path)
}
assertNoError(proc.Continue(p), t, "Continue 2")
// test that PackageVariables returns variables from the executable and plugins
scope, err := evalScope(p)
assertNoError(err, t, "evalScope")
allvars, err := scope.PackageVariables(pnormalLoadConfig)
assertNoError(err, t, "PackageVariables")
var plugin2AFound, mainExeGlobalFound bool
for _, v := range allvars {
switch v.Name {
case "github.com/go-delve/delve/_fixtures/plugin2.A":
plugin2AFound = true
case "main.ExeGlobal":
mainExeGlobalFound = true
}
}
if !plugin2AFound {
t.Fatalf("variable plugin2.A not found in the output of PackageVariables")
}
if !mainExeGlobalFound {
t.Fatalf("variable main.ExeGlobal not found in the output of PackageVariables")
}
// read interface variable, inside plugin code, with a concrete type defined in the executable
vs, err := evalVariable(p, "s", pnormalLoadConfig)
assertNoError(err, t, "Eval(s)")
assertVariable(t, vs, varTest{"s", true, `github.com/go-delve/delve/_fixtures/internal/pluginsupport.Something(*main.asomething) *{n: 2}`, ``, `github.com/go-delve/delve/_fixtures/internal/pluginsupport.Something`, nil})
// test that the concrete type -> interface{} conversion works across plugins (mostly tests proc.dwarfToRuntimeType)
assertNoError(setVariable(p, "plugin2.A", "main.ExeGlobal"), t, "setVariable(plugin2.A = main.ExeGlobal)")
assertNoError(proc.Continue(p), t, "Continue 3")
assertCurrentLocationFunction(p, t, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil")
vstr, err := evalVariable(p, "str", pnormalLoadConfig)
assertNoError(err, t, "Eval(str)")
assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil})
assertNoError(proc.StepOut(p), t, "StepOut")
assertNoError(proc.StepOut(p), t, "StepOut")
assertNoError(proc.Next(p), t, "Next")
// read interface variable, inside executable code, with a concrete type defined in a plugin
vb, err := evalVariable(p, "b", pnormalLoadConfig)
assertNoError(err, t, "Eval(b)")
assertVariable(t, vb, varTest{"b", true, `github.com/go-delve/delve/_fixtures/internal/pluginsupport.SomethingElse(*github.com/go-delve/delve/_fixtures/plugin2.asomethingelse) *{x: 1, y: 4}`, ``, `github.com/go-delve/delve/_fixtures/internal/pluginsupport.SomethingElse`, nil})
})
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册