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
This commit is contained in:
parent
3d896ece07
commit
f9c8f7f55b
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user