未验证 提交 f9c8f7f5 编写于 作者: A Alessandro Arzilli 提交者: GitHub

Go 1.15 support (#2011)

* proc: start variable visibility one line after their decl line

In most cases variables shouldn't be visible on their declaration line
because they won't be initialized there.
Function arguments are treated as an exception.

This fix is only applied to programs compiled with Go 1.15 or later as
previous versions of Go did not report the correct declaration line for
variables captured by closures.

Fixes #1134

* proc: silence go vet error

* Makefile: enable PIE tests on windows/Go 1.15

* core: support core files for PIEs on windows

* goversion: add Go 1.15 to supported versions

* proc: fix function call injection for Go 1.15

Go 1.15 changed the call injection protocol so that the runtime will
execute the injected call on a different (new) goroutine.

This commit changes the function call support in delve to:

1. correctly track down the call injection state after the runtime
   switches to a different goroutine.
2. correctly perform the escapeCheck when stack values can come from
   multiple goroutine stacks.

* proc: miscellaneous fixed for call injection under macOS with go 1.15

- create copy of SP in debugCallAXCompleteCall case because the code
  used to assume that regs doesn't change
- fix automatic address calculation for function arguments when an
  argument has a spurious DW_OP_piece at entry
上级 3d896ece
......@@ -2,14 +2,15 @@ package main
import (
"fmt"
"runtime"
)
func main() {
a := 0
runtime.Breakpoint()
a++
b := 0
runtime.Breakpoint()
fmt.Println(a, b)
f1(a, b)
}
func f1(a, b int) {
fmt.Printf("%d %d\n", a, b)
}
......@@ -20,8 +20,8 @@ type Nest struct {
}
func barfoo() {
runtime.Breakpoint()
a1 := "bur"
runtime.Breakpoint()
fmt.Println(a1)
}
......
......@@ -70,15 +70,7 @@ func NewMakeCommands() *cobra.Command {
Short: "Tests delve",
Long: `Tests delve.
Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is equivalent to:
go run _scripts/make.go test -s all -b default
go run _scripts/make.go test -s basic -b lldb # if lldb-server is installed and Go < 1.14
go run _scripts/make.go test -s basic -b rr # if rr is installed
go run _scripts/make.go test -s basic -m pie # only on linux
go run _scripts/make.go test -s core -m pie # only on linux
go run _scripts/make.go test -s
Use the flags -s, -r and -b to specify which tests to run. Specifying nothing will run all tests relevant for the current environment (see testStandard).
`,
Run: testCmd,
}
......@@ -310,25 +302,7 @@ func testCmd(cmd *cobra.Command, args []string) {
os.Exit(1)
}
fmt.Println("Testing default backend")
testCmdIntl("all", "", "default", "normal")
if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
fmt.Println("\nTesting LLDB backend")
testCmdIntl("basic", "", "lldb", "normal")
}
if inpath("rr") {
fmt.Println("\nTesting RR backend")
testCmdIntl("basic", "", "rr", "normal")
}
if runtime.GOOS == "linux" {
fmt.Println("\nTesting PIE buildmode, default backend")
testCmdIntl("basic", "", "default", "pie")
testCmdIntl("core", "", "default", "pie")
}
if runtime.GOOS == "linux" && inpath("rr") {
fmt.Println("\nTesting PIE buildmode, RR backend")
testCmdIntl("basic", "", "rr", "pie")
}
testStandard()
return
}
......@@ -347,6 +321,28 @@ func testCmd(cmd *cobra.Command, args []string) {
testCmdIntl(TestSet, TestRegex, TestBackend, TestBuildMode)
}
func testStandard() {
fmt.Println("Testing default backend")
testCmdIntl("all", "", "default", "normal")
if inpath("lldb-server") && !goversion.VersionAfterOrEqual(runtime.Version(), 1, 14) {
fmt.Println("\nTesting LLDB backend")
testCmdIntl("basic", "", "lldb", "normal")
}
if inpath("rr") {
fmt.Println("\nTesting RR backend")
testCmdIntl("basic", "", "rr", "normal")
}
if runtime.GOOS == "linux" || (runtime.GOOS == "windows" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15)) {
fmt.Println("\nTesting PIE buildmode, default backend")
testCmdIntl("basic", "", "default", "pie")
testCmdIntl("core", "", "default", "pie")
}
if runtime.GOOS == "linux" && inpath("rr") {
fmt.Println("\nTesting PIE buildmode, RR backend")
testCmdIntl("basic", "", "rr", "pie")
}
}
func testCmdIntl(testSet, testRegex, testBackend, testBuildMode string) {
testPackages := testSetToPackages(testSet)
if len(testPackages) == 0 {
......
......@@ -11,29 +11,45 @@ type Variable struct {
Depth int
}
// VariablesFlags specifies some configuration flags for the Variables function.
type VariablesFlags uint8
const (
VariablesOnlyVisible VariablesFlags = 1 << iota
VariablesSkipInlinedSubroutines
VariablesTrustDeclLine
)
// Variables returns a list of variables contained inside 'root'.
// If onlyVisible is true only variables visible at pc will be returned.
// If skipInlinedSubroutines is true inlined subroutines will be skipped
func Variables(root *godwarf.Tree, pc uint64, line int, onlyVisible, skipInlinedSubroutines bool) []Variable {
return variablesInternal(nil, root, 0, pc, line, onlyVisible, skipInlinedSubroutines)
func Variables(root *godwarf.Tree, pc uint64, line int, flags VariablesFlags) []Variable {
return variablesInternal(nil, root, 0, pc, line, flags)
}
func variablesInternal(v []Variable, root *godwarf.Tree, depth int, pc uint64, line int, onlyVisible, skipInlinedSubroutines bool) []Variable {
func variablesInternal(v []Variable, root *godwarf.Tree, depth int, pc uint64, line int, flags VariablesFlags) []Variable {
switch root.Tag {
case dwarf.TagInlinedSubroutine:
if skipInlinedSubroutines {
if flags&VariablesSkipInlinedSubroutines != 0 {
return v
}
fallthrough
case dwarf.TagLexDwarfBlock, dwarf.TagSubprogram:
if !onlyVisible || root.ContainsPC(pc) {
if (flags&VariablesOnlyVisible == 0) || root.ContainsPC(pc) {
for _, child := range root.Children {
v = variablesInternal(v, child, depth+1, pc, line, onlyVisible, skipInlinedSubroutines)
v = variablesInternal(v, child, depth+1, pc, line, flags)
}
}
return v
default:
if declLine, ok := root.Val(dwarf.AttrDeclLine).(int64); !ok || line >= int(declLine) {
o := 0
if root.Tag != dwarf.TagFormalParameter && (flags&VariablesTrustDeclLine != 0) {
// visibility for variables starts the line after declaration line,
// except for formal parameters, which are visible on the same line they
// are defined.
o = 1
}
if declLine, ok := root.Val(dwarf.AttrDeclLine).(int64); !ok || line >= int(declLine)+o {
return append(v, Variable{root, depth})
}
return v
......
......@@ -8,7 +8,7 @@ var (
MinSupportedVersionOfGoMajor = 1
MinSupportedVersionOfGoMinor = 12
MaxSupportedVersionOfGoMajor = 1
MaxSupportedVersionOfGoMinor = 14
MaxSupportedVersionOfGoMinor = 15
goTooOldErr = fmt.Errorf("Version of Go is too old for this version of Delve (minimum supported version %d.%d, suppress this error with --check-go-version=false)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor)
dlvTooOldErr = fmt.Errorf("Version of Delve is too old for this version of Go (maximum supported version %d.%d, suppress this error with --check-go-version=false)", MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor)
)
......
......@@ -28,6 +28,8 @@ func arm64AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw M
asmInst.Kind = RetInstruction
case arm64asm.B, arm64asm.BR:
asmInst.Kind = JmpInstruction
case arm64asm.BRK:
asmInst.Kind = HardBreakInstruction
}
asmInst.DestLoc = resolveCallArgARM64(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
......
......@@ -28,10 +28,16 @@ func readAMD64Minidump(minidumpPath, exePath string) (*process, error) {
memory.Add(m, uintptr(m.Addr), uintptr(len(m.Data)))
}
entryPoint := uint64(0)
if len(mdmp.Modules) > 0 {
entryPoint = mdmp.Modules[0].BaseOfImage
}
p := &process{
mem: memory,
Threads: map[int]*thread{},
bi: proc.NewBinaryInfo("windows", "amd64"),
entryPoint: entryPoint,
breakpoints: proc.NewBreakpointMap(),
pid: int(mdmp.Pid),
}
......
......@@ -23,6 +23,7 @@ const (
CallInstruction
RetInstruction
JmpInstruction
HardBreakInstruction
)
// IsCall is true if instr is a call instruction.
......@@ -40,6 +41,11 @@ func (instr *AsmInstruction) IsJmp() bool {
return instr.Kind == JmpInstruction
}
// IsHardBreak is true if instr is a hardcoded breakpoint instruction.
func (instr *AsmInstruction) IsHardBreak() bool {
return instr.Kind == HardBreakInstruction
}
type archInst interface {
Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string
OpcodeEquals(op uint64) bool
......
......@@ -215,7 +215,12 @@ func (scope *EvalScope) Locals() ([]*Variable, error) {
return nil, err
}
varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, true, false)
variablesFlags := reader.VariablesOnlyVisible
if scope.BinInfo.Producer() != "" && goversion.ProducerAfterOrEqual(scope.BinInfo.Producer(), 1, 15) {
variablesFlags |= reader.VariablesTrustDeclLine
}
varEntries := reader.Variables(dwarfTree, scope.PC, scope.Line, variablesFlags)
vars := make([]*Variable, 0, len(varEntries))
depths := make([]int, 0, len(varEntries))
for _, entry := range varEntries {
......@@ -224,7 +229,7 @@ func (scope *EvalScope) Locals() ([]*Variable, error) {
// skip variables that we can't parse yet
continue
}
if trustArgOrder && val.Unreadable != nil && val.Addr == 0 && entry.Tag == dwarf.TagFormalParameter {
if trustArgOrder && ((val.Unreadable != nil && val.Addr == 0) || val.Flags&VariableFakeAddress != 0) && entry.Tag == dwarf.TagFormalParameter {
addr := afterLastArgAddr(vars)
if addr == 0 {
addr = uintptr(scope.Regs.CFA)
......@@ -653,7 +658,7 @@ func (scope *EvalScope) evalToplevelTypeCast(t ast.Expr, cfg LoadConfig) (*Varia
return v, nil
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint, reflect.Uintptr:
b, _ := constant.Int64Val(argv.Value)
s := string(b)
s := string(rune(b))
v.Value = constant.MakeString(s)
v.Len = int64(len(s))
return v, nil
......
......@@ -107,6 +107,14 @@ type callContext struct {
// continueRequest having cont == false and the return values in ret.
continueRequest chan<- continueRequest
continueCompleted <-chan *G
// injectionThread is the thread to use for nested call injections if the
// original injection goroutine isn't running (because we are in Go 1.15)
injectionThread Thread
// stacks is a slice of known goroutine stacks used to check for
// inappropriate escapes
stacks []stack
}
type continueRequest struct {
......@@ -121,6 +129,7 @@ type callInjection struct {
// pkg/proc/fncall.go for a description of how this works.
continueCompleted chan<- *G
continueRequest <-chan continueRequest
startThreadID int
}
func (callCtx *callContext) doContinue() *G {
......@@ -178,8 +187,9 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
}
t.fncallForG[g.ID] = &callInjection{
continueCompleted,
continueRequest,
continueCompleted: continueCompleted,
continueRequest: continueRequest,
startThreadID: 0,
}
go scope.EvalExpression(expr, retLoadCfg)
......@@ -193,7 +203,7 @@ func EvalExpressionWithCalls(t *Target, g *G, expr string, retLoadCfg LoadConfig
}
func finishEvalExpressionWithCalls(t *Target, g *G, contReq continueRequest, ok bool) error {
fncallLog("stashing return values for %d in thread=%d\n", g.ID, g.Thread.ThreadID())
fncallLog("stashing return values for %d in thread=%d", g.ID, g.Thread.ThreadID())
var err error
if !ok {
err = errors.New("internal error EvalExpressionWithCalls didn't return anything")
......@@ -239,6 +249,23 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if scope.callCtx == nil {
return nil, errFuncCallNotAllowed
}
thread := scope.g.Thread
stacklo := scope.g.stack.lo
if thread == nil {
// We are doing a nested function call and using Go 1.15, the original
// injection goroutine was suspended and now we are using a different
// goroutine, evaluation still happend on the original goroutine but we
// need to use a different thread to do the nested call injection.
thread = scope.callCtx.injectionThread
g2, err := GetG(thread)
if err != nil {
return nil, err
}
stacklo = g2.stack.lo
}
if thread == nil {
return nil, errGoroutineNotRunning
}
p := scope.callCtx.p
bi := scope.BinInfo
......@@ -252,7 +279,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
}
// check that there are at least 256 bytes free on the stack
regs, err := scope.g.Thread.Registers()
regs, err := thread.Registers()
if err != nil {
return nil, err
}
......@@ -260,7 +287,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
if err != nil {
return nil, err
}
if regs.SP()-256 <= scope.g.stack.lo {
if regs.SP()-256 <= stacklo {
return nil, errNotEnoughStack
}
_, err = regs.Get(int(x86asm.RAX))
......@@ -278,22 +305,37 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
return nil, err
}
if err := callOP(bi, scope.g.Thread, regs, dbgcallfn.Entry); err != nil {
if err := callOP(bi, thread, regs, dbgcallfn.Entry); err != nil {
return nil, err
}
// write the desired argument frame size at SP-(2*pointer_size) (the extra pointer is the saved PC)
if err := writePointer(bi, scope.g.Thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
if err := writePointer(bi, thread, regs.SP()-3*uint64(bi.Arch.PtrSize()), uint64(fncall.argFrameSize)); err != nil {
return nil, err
}
fncallLog("function call initiated %v frame size %d", fncall.fn, fncall.argFrameSize)
fncallLog("function call initiated %v frame size %d goroutine %d (thread %d)", fncall.fn, fncall.argFrameSize, scope.g.ID, thread.ThreadID())
p.fncallForG[scope.g.ID].startThreadID = thread.ThreadID()
spoff := int64(scope.Regs.Uint64Val(scope.Regs.SPRegNum)) - int64(scope.g.stack.hi)
bpoff := int64(scope.Regs.Uint64Val(scope.Regs.BPRegNum)) - int64(scope.g.stack.hi)
fboff := scope.Regs.FrameBase - int64(scope.g.stack.hi)
for {
scope.g = scope.callCtx.doContinue()
scope.callCtx.injectionThread = nil
g := scope.callCtx.doContinue()
// Go 1.15 will move call injection execution to a different goroutine,
// but we want to keep evaluation on the original goroutine.
if g.ID == scope.g.ID {
scope.g = g
} else {
// We are in Go 1.15 and we switched to a new goroutine, the original
// goroutine is now parked and therefore does not have a thread
// associated.
scope.g.Thread = nil
scope.g.Status = Gwaiting
scope.callCtx.injectionThread = g.Thread
}
// adjust the value of registers inside scope
pcreg, bpreg, spreg := scope.Regs.Reg(scope.Regs.PCRegNum), scope.Regs.Reg(scope.Regs.BPRegNum), scope.Regs.Reg(scope.Regs.SPRegNum)
......@@ -306,7 +348,7 @@ func evalFunctionCall(scope *EvalScope, node *ast.CallExpr) (*Variable, error) {
scope.Regs.FrameBase = fboff + int64(scope.g.stack.hi)
scope.Regs.CFA = scope.frameOffset + int64(scope.g.stack.hi)
finished := funcCallStep(scope, &fncall)
finished := funcCallStep(scope, &fncall, g.Thread)
if finished {
break
}
......@@ -500,9 +542,14 @@ func funcCallEvalArgs(scope *EvalScope, fncall *functionCallState, argFrameAddr
func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg *Variable, formalArg *funcCallArg, argFrameAddr uint64) error {
if scope.callCtx.checkEscape {
//TODO(aarzilli): only apply the escapeCheck to leaking parameters.
if err := escapeCheck(actualArg, formalArg.name, scope.g); err != nil {
if err := escapeCheck(actualArg, formalArg.name, scope.g.stack); err != nil {
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
}
for _, stack := range scope.callCtx.stacks {
if err := escapeCheck(actualArg, formalArg.name, stack); err != nil {
return fmt.Errorf("cannot use %s as argument %s in function %s: %v", actualArg.Name, formalArg.name, fncall.fn.Name, err)
}
}
}
//TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled
......@@ -524,7 +571,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
return 0, nil, fmt.Errorf("DWARF read error: %v", err)
}
varEntries := reader.Variables(dwarfTree, fn.Entry, int(^uint(0)>>1), false, true)
varEntries := reader.Variables(dwarfTree, fn.Entry, int(^uint(0)>>1), reader.VariablesSkipInlinedSubroutines)
trustArgOrder := bi.Producer() != "" && goversion.ProducerAfterOrEqual(bi.Producer(), 1, 12)
......@@ -590,7 +637,7 @@ func alignAddr(addr, align int64) int64 {
return (addr + int64(align-1)) &^ int64(align-1)
}
func escapeCheck(v *Variable, name string, g *G) error {
func escapeCheck(v *Variable, name string, stack stack) error {
switch v.Kind {
case reflect.Ptr:
var w *Variable
......@@ -600,31 +647,31 @@ func escapeCheck(v *Variable, name string, g *G) error {
} else {
w = v.maybeDereference()
}
return escapeCheckPointer(w.Addr, name, g)
return escapeCheckPointer(w.Addr, name, stack)
case reflect.Chan, reflect.String, reflect.Slice:
return escapeCheckPointer(v.Base, name, g)
return escapeCheckPointer(v.Base, name, stack)
case reflect.Map:
sv := v.clone()
sv.RealType = resolveTypedef(&(v.RealType.(*godwarf.MapType).TypedefType))
sv = sv.maybeDereference()
return escapeCheckPointer(sv.Addr, name, g)
return escapeCheckPointer(sv.Addr, name, stack)
case reflect.Struct:
t := v.RealType.(*godwarf.StructType)
for _, field := range t.Field {
fv, _ := v.toField(field)
if err := escapeCheck(fv, fmt.Sprintf("%s.%s", name, field.Name), g); err != nil {
if err := escapeCheck(fv, fmt.Sprintf("%s.%s", name, field.Name), stack); err != nil {
return err
}
}
case reflect.Array:
for i := int64(0); i < v.Len; i++ {
sv, _ := v.sliceAccess(int(i))
if err := escapeCheck(sv, fmt.Sprintf("%s[%d]", name, i), g); err != nil {
if err := escapeCheck(sv, fmt.Sprintf("%s[%d]", name, i), stack); err != nil {
return err
}
}
case reflect.Func:
if err := escapeCheckPointer(uintptr(v.funcvalAddr()), name, g); err != nil {
if err := escapeCheckPointer(uintptr(v.funcvalAddr()), name, stack); err != nil {
return err
}
}
......@@ -632,8 +679,8 @@ func escapeCheck(v *Variable, name string, g *G) error {
return nil
}
func escapeCheckPointer(addr uintptr, name string, g *G) error {
if uint64(addr) >= g.stack.lo && uint64(addr) < g.stack.hi {
func escapeCheckPointer(addr uintptr, name string, stack stack) error {
if uint64(addr) >= stack.lo && uint64(addr) < stack.hi {
return fmt.Errorf("stack object passed to escaping pointer: %s", name)
}
return nil
......@@ -648,21 +695,15 @@ const (
)
// funcCallStep executes one step of the function call injection protocol.
func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
func funcCallStep(callScope *EvalScope, fncall *functionCallState, thread Thread) bool {
p := callScope.callCtx.p
bi := p.BinInfo()
thread := callScope.g.Thread
regs, err := thread.Registers()
if err != nil {
fncall.err = err
return true
}
regs, err = regs.Copy()
if err != nil {
fncall.err = err
return true
}
rax, _ := regs.Get(int(x86asm.RAX))
......@@ -676,7 +717,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
fnname = loc.Fn.Name
}
}
fncallLog("function call interrupt gid=%d thread=%d rax=%#x (PC=%#x in %s)", callScope.g.ID, thread.ThreadID(), rax, pc, fnname)
fncallLog("function call interrupt gid=%d (original) thread=%d rax=%#x (PC=%#x in %s)", callScope.g.ID, thread.ThreadID(), rax, pc, fnname)
}
switch rax {
......@@ -691,6 +732,7 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
fncall.err = fmt.Errorf("%v", constant.StringVal(errvar.Value))
case debugCallAXCompleteCall:
p.fncallForG[callScope.g.ID].startThreadID = 0
// evaluate arguments of the target function, copy them into its argument frame and call the function
if fncall.fn == nil || fncall.receiver != nil || fncall.closureAddr != 0 {
// if we couldn't figure out which function we are calling before
......@@ -719,13 +761,15 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
// address of the function pointer itself.
thread.SetDX(fncall.closureAddr)
}
cfa := regs.SP()
oldpc := regs.PC()
callOP(bi, thread, regs, fncall.fn.Entry)
err := funcCallEvalArgs(callScope, fncall, regs.SP())
err := funcCallEvalArgs(callScope, fncall, cfa)
if err != nil {
// rolling back the call, note: this works because we called regs.Copy() above
thread.SetSP(regs.SP())
thread.SetPC(regs.PC())
thread.SetSP(cfa)
thread.SetPC(oldpc)
fncall.err = err
fncall.lateCallFailure = true
break
......@@ -777,6 +821,13 @@ func funcCallStep(callScope *EvalScope, fncall *functionCallState) bool {
v.Flags |= VariableFakeAddress
}
// Store the stack span of the currently running goroutine (which in Go >=
// 1.15 might be different from the original injection goroutine) so that
// later on we can use it to perform the escapeCheck
if threadg, _ := GetG(thread); threadg != nil {
callScope.callCtx.stacks = append(callScope.callCtx.stacks, threadg.stack)
}
case debugCallAXReadPanic:
// read panic value from stack
fncall.panicvar, err = readTopstackVariable(thread, regs, "interface {}", callScope.callCtx.retLoadCfg)
......@@ -886,11 +937,18 @@ func allocString(scope *EvalScope, v *Variable) error {
return err
}
func isCallInjectionStop(loc *Location) bool {
func isCallInjectionStop(t *Target, thread Thread, loc *Location) bool {
if loc.Fn == nil {
return false
}
return strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) || strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2)
if !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix1) && !strings.HasPrefix(loc.Fn.Name, debugCallFunctionNamePrefix2) {
return false
}
text, err := disassembleCurrentInstruction(t, thread, -1)
if err != nil || len(text) <= 0 {
return false
}
return text[0].IsHardBreak()
}
// callInjectionProtocol is the function called from Continue to progress
......@@ -906,19 +964,16 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
if err != nil {
continue
}
if !isCallInjectionStop(loc) {
if !isCallInjectionStop(t, thread, loc) {
continue
}
g, err := GetG(thread)
g, callinj, err := findCallInjectionStateForThread(t, thread)
if err != nil {
return done, fmt.Errorf("could not determine running goroutine for thread %#x currently executing the function call injection protocol: %v", thread.ThreadID(), err)
return false, err
}
callinj := t.fncallForG[g.ID]
if callinj == nil || callinj.continueCompleted == nil {
return false, fmt.Errorf("could not recover call injection state for goroutine %d", g.ID)
}
fncallLog("step for injection on goroutine %d thread=%d (location %s)", g.ID, thread.ThreadID(), loc.Fn.Name)
fncallLog("step for injection on goroutine %d (current) thread=%d (location %s)", g.ID, thread.ThreadID(), loc.Fn.Name)
callinj.continueCompleted <- g
contReq, ok := <-callinj.continueRequest
if !contReq.cont {
......@@ -931,3 +986,36 @@ func callInjectionProtocol(t *Target, threads []Thread) (done bool, err error) {
}
return done, nil
}
func findCallInjectionStateForThread(t *Target, thread Thread) (*G, *callInjection, error) {
g, err := GetG(thread)
if err != nil {
return nil, nil, fmt.Errorf("could not determine running goroutine for thread %#x currently executing the function call injection protocol: %v", thread.ThreadID(), err)
}
fncallLog("findCallInjectionStateForThread thread=%d goroutine=%d", thread.ThreadID(), g.ID)
notfound := func() error {
return fmt.Errorf("could not recover call injection state for goroutine %d (thread %d)", g.ID, thread.ThreadID())
}
callinj := t.fncallForG[g.ID]
if callinj != nil {
if callinj.continueCompleted == nil {
return nil, nil, notfound()
}
return g, callinj, nil
}
// In Go 1.15 and later the call injection protocol will switch to a
// different goroutine.
// Here we try to recover the injection goroutine by checking the injection
// thread.
for goid, callinj := range t.fncallForG {
if callinj != nil && callinj.continueCompleted != nil && callinj.startThreadID != 0 && callinj.startThreadID == thread.ThreadID() {
t.fncallForG[g.ID] = callinj
fncallLog("goroutine %d is the goroutine executing the call injection started in goroutine %d", g.ID, goid)
return g, callinj, nil
}
}
return nil, nil, notfound()
}
......@@ -15,6 +15,7 @@ import (
"path/filepath"
"reflect"
"runtime"
"sort"
"strconv"
"strings"
"testing"
......@@ -3507,6 +3508,30 @@ func TestIssue1008(t *testing.T) {
})
}
func testDeclLineCount(t *testing.T, p *proc.Target, lineno int, tgtvars []string) {
sort.Strings(tgtvars)
assertLineNumber(p, t, lineno, "Program did not continue to correct next location")
scope, err := proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, fmt.Sprintf("GoroutineScope (:%d)", lineno))
vars, err := scope.Locals()
assertNoError(err, t, fmt.Sprintf("Locals (:%d)", lineno))
if len(vars) != len(tgtvars) {
t.Fatalf("wrong number of variables %d (:%d)", len(vars), lineno)
}
outvars := make([]string, len(vars))
for i, v := range vars {
outvars[i] = v.Name
}
sort.Strings(outvars)
for i := range outvars {
if tgtvars[i] != outvars[i] {
t.Fatalf("wrong variables, got: %q expected %q\n", outvars, tgtvars)
}
}
}
func TestDeclLine(t *testing.T) {
ver, _ := goversion.Parse(runtime.Version())
if ver.Major > 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) {
......@@ -3514,24 +3539,34 @@ func TestDeclLine(t *testing.T) {
}
withTestProcess("decllinetest", t, func(p *proc.Target, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture.Source, 8)
setFileBreakpoint(p, t, fixture.Source, 9)
setFileBreakpoint(p, t, fixture.Source, 10)
setFileBreakpoint(p, t, fixture.Source, 11)
setFileBreakpoint(p, t, fixture.Source, 14)
assertNoError(p.Continue(), t, "Continue")
scope, err := proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, "GoroutineScope (1)")
vars, err := scope.LocalVariables(normalLoadConfig)
assertNoError(err, t, "LocalVariables (1)")
if len(vars) != 1 {
t.Fatalf("wrong number of variables %d", len(vars))
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
testDeclLineCount(t, p, 8, []string{})
} else {
testDeclLineCount(t, p, 8, []string{"a"})
}
assertNoError(p.Continue(), t, "Continue")
scope, err = proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, "GoroutineScope (2)")
scope.LocalVariables(normalLoadConfig)
vars, err = scope.LocalVariables(normalLoadConfig)
assertNoError(err, t, "LocalVariables (2)")
if len(vars) != 2 {
t.Fatalf("wrong number of variables %d", len(vars))
testDeclLineCount(t, p, 9, []string{"a"})
assertNoError(p.Continue(), t, "Continue")
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
testDeclLineCount(t, p, 10, []string{"a"})
} else {
testDeclLineCount(t, p, 10, []string{"a", "b"})
}
assertNoError(p.Continue(), t, "Continue")
testDeclLineCount(t, p, 11, []string{"a", "b"})
assertNoError(p.Continue(), t, "Continue")
testDeclLineCount(t, p, 14, []string{"a", "b"})
})
}
......
......@@ -157,7 +157,7 @@ func (dbp *Target) Continue() error {
return err
}
if dbp.GetDirection() == Forward {
text, err := disassembleCurrentInstruction(dbp, curthread)
text, err := disassembleCurrentInstruction(dbp, curthread, 0)
if err != nil {
return err
}
......@@ -248,12 +248,12 @@ func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error {
return dbp.SwitchThread(trapthread.ThreadID())
}
func disassembleCurrentInstruction(p Process, thread Thread) ([]AsmInstruction, error) {
func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) {
regs, err := thread.Registers()
if err != nil {
return nil, err
}
pc := regs.PC()
pc := regs.PC() + uint64(off)
return disassemble(thread, regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()), true)
}
......
......@@ -135,6 +135,8 @@ func BuildFixture(name string, flags BuildFlags) Fixture {
}
if flags&BuildModePIE != 0 {
buildFlags = append(buildFlags, "-buildmode=pie")
} else {
buildFlags = append(buildFlags, "-buildmode=exe")
}
if flags&BuildModePlugin != 0 {
buildFlags = append(buildFlags, "-buildmode=plugin")
......
......@@ -30,6 +30,8 @@ func x86AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw Mem
asmInst.Kind = CallInstruction
case x86asm.RET, x86asm.LRET:
asmInst.Kind = RetInstruction
case x86asm.INT:
asmInst.Kind = HardBreakInstruction
}
asmInst.DestLoc = resolveCallArgX86(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
......
......@@ -687,7 +687,7 @@ func TestListCmd(t *testing.T) {
withTestTerminal("testvariables", t, func(term *FakeTerminal) {
term.MustExec("continue")
term.MustExec("continue")
listIsAt(t, term, "list", 24, 19, 29)
listIsAt(t, term, "list", 25, 20, 30)
listIsAt(t, term, "list 69", 69, 64, 70)
listIsAt(t, term, "frame 1 list", 62, 57, 67)
listIsAt(t, term, "frame 1 list 69", 69, 64, 70)
......
......@@ -417,7 +417,7 @@ func Test1ClientServer_switchThread(t *testing.T) {
func Test1ClientServer_infoLocals(t *testing.T) {
withTestClient1("testnextprog", t, func(c *rpc1.RPCClient) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 24})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......
......@@ -548,7 +548,7 @@ func TestClientServer_infoLocals(t *testing.T) {
protest.AllowRecording(t)
withTestClient2("testnextprog", t, func(c service.Client) {
fp := testProgPath(t, "testnextprog")
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 23})
_, err := c.CreateBreakpoint(&api.Breakpoint{File: fp, Line: 24})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
......@@ -1771,6 +1771,9 @@ func TestClientServerFunctionCallStacktrace(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support FunctionCall for now")
}
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) {
t.Skip("Go 1.15 executes function calls in a different goroutine so the stack trace will not contain main.main or runtime.main")
}
protest.MustSupportFunctionCalls(t, testBackend)
withTestClient2("fncall", t, func(c service.Client) {
mustHaveDebugCalls(t, c)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册