From 95a151379b5e01a05374c2ade6df7e4563766ab4 Mon Sep 17 00:00:00 2001 From: gfszr <99412607+gfszr@users.noreply.github.com> Date: Tue, 25 Oct 2022 03:44:47 +0300 Subject: [PATCH 01/72] Add type to call evaluation and expression evaluation (#3172) --- service/dap/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/dap/server.go b/service/dap/server.go index d8b67a2fd8..07300295a0 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -2694,7 +2694,7 @@ func (s *Session) onEvaluateRequest(request *dap.EvaluateRequest) { opts |= showFullValue } exprVal, exprRef := s.convertVariableWithOpts(exprVar, fmt.Sprintf("(%s)", request.Arguments.Expression), opts) - response.Body = dap.EvaluateResponseBody{Result: exprVal, VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)} + response.Body = dap.EvaluateResponseBody{Result: exprVal, Type: s.getTypeIfSupported(exprVar), VariablesReference: exprRef, IndexedVariables: getIndexedVariableCount(exprVar), NamedVariables: getNamedVariableCount(exprVar)} } s.send(response) } From 63ef73a0dd5cf9ec24552894f52bb74311cb92e9 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Fri, 28 Oct 2022 05:42:06 -0700 Subject: [PATCH 02/72] pkg/terminal: Improve error when unable to read history file (#3169) This patch includes the path to the history file itself so that users can better fix the issue when an error arises. Fixes #3168 --- pkg/terminal/terminal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 5f7b9701ea..e04b77add5 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -292,7 +292,7 @@ func (t *Term) Run() (int, error) { fmt.Printf("Unable to open history file: %v. History will not be saved for this session.", err) } if _, err := t.line.ReadHistory(t.historyFile); err != nil { - fmt.Printf("Unable to read history file: %v", err) + fmt.Printf("Unable to read history file %s: %v\n", fullHistoryFile, err) } fmt.Println("Type 'help' for list of commands.") From b072f61bf853fb8a22e6d09b1c89e209358831fc Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Fri, 28 Oct 2022 05:59:09 -0700 Subject: [PATCH 03/72] service/debugger: Assume current dir for exec (#3167) This patch modifies the behavior of the exec subcommand such that you don't necessarily have to write the "./" prefix when trying to debug a precompiled binary in your working directory. For example (given foo.test in working dir), before this change: dlv exec foo.test Would result in an error, forcing the user to type: dlv exec ./foo.test This just makes things a bit more convenient. --- pkg/proc/native/proc_windows.go | 6 +---- service/debugger/debugger.go | 44 +++++++++++++++---------------- service/debugger/debugger_test.go | 33 +++++++++++++++++++++++ 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 5c5ffcf25f..d37249ed0d 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -3,7 +3,6 @@ package native import ( "fmt" "os" - "path/filepath" "syscall" "unsafe" @@ -25,10 +24,7 @@ func (os *osProcessDetails) Close() {} // Launch creates and begins debugging a new process. func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.Target, error) { - argv0Go, err := filepath.Abs(cmd[0]) - if err != nil { - return nil, err - } + argv0Go := cmd[0] env := proc.DisableAsyncPreemptEnv() diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index cabba03bfb..091f46294e 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -9,6 +9,7 @@ import ( "fmt" "go/parser" "go/token" + "io" "os" "os/exec" "path/filepath" @@ -246,9 +247,11 @@ func (d *Debugger) TargetGoVersion() string { // Launch will start a process with the given args and working directory. func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) { - if err := verifyBinaryFormat(processArgs[0]); err != nil { + fullpath, err := verifyBinaryFormat(processArgs[0]) + if err != nil { return nil, err } + processArgs[0] = fullpath launchFlags := proc.LaunchFlags(0) if d.config.Foreground { @@ -2259,34 +2262,31 @@ func noDebugErrorWarning(err error) error { return err } -func verifyBinaryFormat(exePath string) error { - f, err := os.Open(exePath) +func verifyBinaryFormat(exePath string) (string, error) { + fullpath, err := filepath.Abs(exePath) if err != nil { - return err + return "", err + } + + f, err := os.Open(fullpath) + if err != nil { + return "", err } defer f.Close() - switch runtime.GOOS { - case "windows": - // Make sure the binary exists and is an executable file - if filepath.Base(exePath) == exePath { - if _, err := exec.LookPath(exePath); err != nil { - return err - } - } - default: - fi, err := f.Stat() + // Skip this check on Windows. + // TODO(derekparker) exec.LookPath looks for valid Windows extensions. + // We don't create our binaries with valid extensions, even though we should. + // Skip this check for now. + if runtime.GOOS != "windows" { + _, err = exec.LookPath(fullpath) if err != nil { - return err - } - if (fi.Mode() & 0111) == 0 { - return api.ErrNotExecutable + return "", api.ErrNotExecutable } } // check that the binary format is what we expect for the host system - var exe interface{ Close() error } - + var exe io.Closer switch runtime.GOOS { case "darwin": exe, err = macho.NewFile(f) @@ -2299,10 +2299,10 @@ func verifyBinaryFormat(exePath string) error { } if err != nil { - return api.ErrNotExecutable + return "", api.ErrNotExecutable } exe.Close() - return nil + return fullpath, nil } var attachErrorMessage = attachErrorMessageDefault diff --git a/service/debugger/debugger_test.go b/service/debugger/debugger_test.go index 275e1a5cab..b8b05ece55 100644 --- a/service/debugger/debugger_test.go +++ b/service/debugger/debugger_test.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "runtime" + "strings" "testing" "github.com/go-delve/delve/pkg/gobuild" @@ -68,3 +69,35 @@ func TestDebugger_LaunchInvalidFormat(t *testing.T) { t.Fatalf("expected error \"%s\" got \"%v\"", api.ErrNotExecutable, err) } } + +func TestDebugger_LaunchCurrentDir(t *testing.T) { + fixturesDir := protest.FindFixturesDir() + testDir := filepath.Join(fixturesDir, "buildtest") + debugname := "debug" + exepath := filepath.Join(testDir, debugname) + originalPath, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + defer os.Chdir(originalPath) + defer func() { + if err := os.Remove(exepath); err != nil { + t.Fatalf("error removing executable %v", err) + } + }() + if err := gobuild.GoBuild(debugname, []string{testDir}, fmt.Sprintf("-o %s", exepath)); err != nil { + t.Fatalf("go build error %v", err) + } + + os.Chdir(testDir) + + d := new(Debugger) + d.config = &Config{} + _, err = d.Launch([]string{debugname}, ".") + if err == nil { + t.Fatal("expected error but none was generated") + } + if err != nil && !strings.Contains(err.Error(), "unknown backend") { + t.Fatal(err) + } +} From cba16f92e8362b2498e0cd30af9b07ffefea5dd3 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 8 Nov 2022 00:22:12 +0100 Subject: [PATCH 04/72] proc: fix index access to already-loaded string values (#3184) Fixes #3176 --- _fixtures/fncall.go | 5 ++++- pkg/proc/eval.go | 16 +++++++++++++++- pkg/proc/variables_test.go | 4 ++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/_fixtures/fncall.go b/_fixtures/fncall.go index 34063d64d4..f1839ca4ad 100644 --- a/_fixtures/fncall.go +++ b/_fixtures/fncall.go @@ -231,6 +231,9 @@ func main() { d := &Derived{3, Base{4}} + var ref strings.Builder + fmt.Fprintf(&ref, "blah") + runtime.Breakpoint() // breakpoint here call1(one, two) fn2clos(2) @@ -238,5 +241,5 @@ func main() { d.Method() d.Base.Method() x.CallMe() - fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String(), regabistacktest3, rast3, floatsum) + fmt.Println(one, two, zero, call, call0, call2, callexit, callpanic, callbreak, callstacktrace, stringsJoin, intslice, stringslice, comma, a.VRcvr, a.PRcvr, pa, vable_a, vable_pa, pable_pa, fn2clos, fn2glob, fn2valmeth, fn2ptrmeth, fn2nil, ga, escapeArg, a2, square, intcallpanic, onetwothree, curriedAdd, getAStruct, getAStructPtr, getVRcvrableFromAStruct, getPRcvrableFromAStructPtr, getVRcvrableFromAStructPtr, pa2, noreturncall, str, d, x, x2.CallMe(5), longstrs, regabistacktest, regabistacktest2, issue2698.String(), regabistacktest3, rast3, floatsum, ref) } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index f01c4d2ca7..5433badcb2 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -2084,7 +2084,21 @@ func (v *Variable) sliceAccess(idx int) (*Variable, error) { return nil, fmt.Errorf("index out of bounds") } if v.loaded { - return &v.Children[idx], nil + if v.Kind == reflect.String { + s := constant.StringVal(v.Value) + if idx >= len(s) { + return nil, fmt.Errorf("index out of bounds") + } + r := v.newVariable("", v.Base+uint64(int64(idx)*v.stride), v.fieldType, v.mem) + r.loaded = true + r.Value = constant.MakeInt64(int64(s[idx])) + return r, nil + } else { + if idx >= len(v.Children) { + return nil, fmt.Errorf("index out of bounds") + } + return &v.Children[idx], nil + } } mem := v.mem if v.Kind != reflect.Array { diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 22cc223e50..1c84f7eb1f 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -1206,6 +1206,10 @@ func TestCallFunction(t *testing.T) { // Issue 1577 {"1+2", []string{`::3`}, nil}, {`"de"+"mo"`, []string{`::"demo"`}, nil}, + + // Issue 3176 + {`ref.String()[0]`, []string{`:byte:98`}, nil}, + {`ref.String()[20]`, nil, errors.New("index out of bounds")}, } var testcases112 = []testCaseCallFunction{ From 350dd6afe33a76048030099186d11df210f63aa7 Mon Sep 17 00:00:00 2001 From: Aviram Hassan Date: Tue, 8 Nov 2022 01:26:10 +0200 Subject: [PATCH 05/72] proc/gdbserial/gdbserver: Don't pass DYLD_INSERT_LIBRARIES to debugserver (#3181) Filter out DYLD_INSERT_LIBRARIES on Darwin when launching debugserver. This is needed since macOS Ventura, loading custom dylib into debugserver using DYLD_INSERT_LIBRARIES leads to a crash. This is unlike other protected processes, where they just strip it out. --- pkg/proc/gdbserial/gdbserver.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index f898eb4e73..0151a631c3 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -547,6 +547,17 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ if runtime.GOOS == "darwin" { process.Env = proc.DisableAsyncPreemptEnv() + // Filter out DYLD_INSERT_LIBRARIES on Darwin. + // This is needed since macOS Ventura, loading custom dylib into debugserver + // using DYLD_INSERT_LIBRARIES leads to a crash. + // This is unlike other protected processes, where they just strip it out. + env := make([]string, 0, len(process.Env)) + for _, v := range process.Env { + if !(v == "DYLD_INSERT_LIBRARIES") { + env = append(env, v) + } + } + process.Env = env } if err = process.Start(); err != nil { From 2c10bb3e2500dc6aeb44141f68b901991606c2e3 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 8 Nov 2022 19:33:25 +0100 Subject: [PATCH 06/72] service/debugger: allow setting a breakpoint with a specific ID (#3185) Before 4c5b111a it was possible for a client to create a breakpoint with a specific logical ID, restore this ability for backwards compatibility. --- service/debugger/debugger.go | 8 +++++--- service/test/integration2_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 091f46294e..f86de29e16 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -772,14 +772,16 @@ func (d *Debugger) ConvertThreadBreakpoint(thread proc.Thread) *api.Breakpoint { func createLogicalBreakpoint(d *Debugger, requestedBp *api.Breakpoint, setbp *proc.SetBreakpoint, suspended bool) (*api.Breakpoint, error) { id := requestedBp.ID - var lbp *proc.LogicalBreakpoint if id <= 0 { d.breakpointIDCounter++ id = d.breakpointIDCounter - lbp = &proc.LogicalBreakpoint{LogicalID: id, HitCount: make(map[int64]uint64), Enabled: true} - d.target.LogicalBreakpoints[id] = lbp + } else { + d.breakpointIDCounter = id } + lbp := &proc.LogicalBreakpoint{LogicalID: id, HitCount: make(map[int64]uint64), Enabled: true} + d.target.LogicalBreakpoints[id] = lbp + err := copyLogicalBreakpointInfo(lbp, requestedBp) if err != nil { return nil, err diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 61617a8f8e..69c81ef585 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -2961,3 +2961,20 @@ func TestPluginSuspendedBreakpoint(t *testing.T) { cont("Continue 4", "plugin2.go", 9) }) } + +func TestClientServer_createBreakpointWithID(t *testing.T) { + protest.AllowRecording(t) + withTestClient2("continuetestprog", t, func(c service.Client) { + bp, err := c.CreateBreakpoint(&api.Breakpoint{ID: 2, FunctionName: "main.main", Line: 1}) + assertNoError(err, t, "CreateBreakpoint()") + if bp.ID != 2 { + t.Errorf("wrong ID for breakpoint %d", bp.ID) + } + + bp2, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 2}) + assertNoError(err, t, "CreateBreakpoint()") + if bp2.ID != 3 { + t.Errorf("wrong ID for breakpoint %d", bp2.ID) + } + }) +} From c7fa713c34275b2486a0a33e891f423e12bee941 Mon Sep 17 00:00:00 2001 From: Daniel Lipovetsky <3445370+dlipovetsky@users.noreply.github.com> Date: Thu, 10 Nov 2022 09:41:42 -0800 Subject: [PATCH 07/72] bininfo: Log when the file listed in .gnu_debuglink is not found (#3188) --- pkg/proc/bininfo.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 894e57c45a..d5c743cf21 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1291,6 +1291,9 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn suffix := filepath.Join(filepath.Dir(exePath)[1:], debugLink) find(nil, suffix) } + if debugFilePath == "" { + bi.logger.Warnf("gnu_debuglink link %q not found in any debug info directory", debugLink) + } } if debugFilePath != "" { From 824e0a81e80f5b8bbeec9b3a37ef832f3c580cbe Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 10 Nov 2022 18:53:28 +0100 Subject: [PATCH 08/72] Two fixes to type cast evaluation (#3186) * proc: allow casts form unsafe.Pointer to any pointer and vice versa We've allowed doing this with uintptr but we should allow unsafe.Pointer to be used like Go uses it. * proc: fix type casts to ptr-to-ptr types Fix type casts to **type. --- pkg/proc/eval.go | 21 ++++++++++++++++++--- pkg/proc/variables_test.go | 9 +++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 5433badcb2..94ef278433 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -739,9 +739,14 @@ func (scope *EvalScope) evalTypeCastOrFuncCall(node *ast.CallExpr) (*Variable, e return v, err } - fnnode := removeParen(node.Fun) - if n, _ := fnnode.(*ast.StarExpr); n != nil { - fnnode = removeParen(n.X) + fnnode := node.Fun + for { + fnnode = removeParen(fnnode) + n, _ := fnnode.(*ast.StarExpr) + if n == nil { + break + } + fnnode = n.X } switch n := fnnode.(type) { @@ -811,6 +816,11 @@ func (scope *EvalScope) evalTypeCast(node *ast.CallExpr) (*Variable, error) { // compatible underlying types if typeCastCompatibleTypes(argv.RealType, typ) { + if ptyp, isptr := typ.(*godwarf.PtrType); argv.Kind == reflect.Ptr && argv.loaded && len(argv.Children) > 0 && isptr { + cv := argv.Children[0] + argv.Children[0] = *newVariable(cv.Name, cv.Addr, ptyp.Type, cv.bi, cv.mem) + argv.Children[0].OnlyAddr = true + } argv.RealType = typ argv.DwarfType = styp return argv, nil @@ -1028,6 +1038,11 @@ func typeCastCompatibleTypes(typ1, typ2 godwarf.Type) bool { switch ttyp1 := typ1.(type) { case *godwarf.PtrType: if ttyp2, ok := typ2.(*godwarf.PtrType); ok { + _, isvoid1 := ttyp1.Type.(*godwarf.VoidType) + _, isvoid2 := ttyp2.Type.(*godwarf.VoidType) + if isvoid1 || isvoid2 { + return true + } // pointer types are compatible if their element types are compatible return typeCastCompatibleTypes(resolveTypedef(ttyp1.Type), resolveTypedef(ttyp2.Type)) } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 1c84f7eb1f..22c350375e 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -820,6 +820,15 @@ func TestEvalExpression(t *testing.T) { {`(*string)(&typedstringvar)`, false, `(*string)(…`, `(*string)(…`, "*string", nil}, {`(*main.astruct)(&namedA1)`, false, `(*main.astruct)(…`, `(*main.astruct)(…`, "*main.astruct", nil}, {`(*main.astructName2)(&namedA1)`, false, `(*main.astructName2)(…`, `(*main.astructName2)(…`, "*main.astructName2", nil}, + + // Conversions to and from uintptr/unsafe.Pointer + {`*(*uint)(uintptr(p1))`, false, `1`, `1`, "uint", nil}, + {`*(*uint)(uintptr(&i1))`, false, `1`, `1`, "uint", nil}, + {`*(*uint)(unsafe.Pointer(p1))`, false, `1`, `1`, "uint", nil}, + {`*(*uint)(unsafe.Pointer(&i1))`, false, `1`, `1`, "uint", nil}, + + // Conversions to ptr-to-ptr types + {`**(**runtime.hmap)(uintptr(&m1))`, false, `…`, `…`, "runtime.hmap", nil}, } ver, _ := goversion.Parse(runtime.Version()) From 18ebd9195a753c255baff03b997c432d791ce2e8 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 15 Nov 2022 00:05:43 -0800 Subject: [PATCH 09/72] pkg/proc: fix arm64 linux cgo stacktrace (#3192) This patch introduces some changes, particularly to arm64SwitchStack which fixes the test when running on linux/arm64. The changes causes the same test to fail on darwin/m1 so temporarily keeping both versions. Next step should be to refactor and unify the two so they both work with the same function. Fixes #2340 --- Documentation/backend_test_health.md | 2 - pkg/proc/amd64_arch.go | 2 +- pkg/proc/arm64_arch.go | 179 ++++++++++++++++++++------- pkg/proc/proc_test.go | 28 +++-- pkg/proc/stack.go | 9 +- pkg/proc/variables.go | 4 +- 6 files changed, 165 insertions(+), 59 deletions(-) diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md index cca2178257..eff970fe29 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -16,8 +16,6 @@ Tests skipped by each supported backend: * 4 not implemented * linux/386/pie skipped = 1 * 1 broken -* linux/arm64 skipped = 1 - * 1 broken - cgo stacktraces * pie skipped = 2 * 2 upstream issue - https://github.com/golang/go/issues/29322 * windows skipped = 5 diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index 0cc06df322..a39e94c91d 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -179,7 +179,6 @@ func amd64SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool { // switch from the system stack back into the goroutine stack // Since we are going backwards on the stack here we see the transition // as goroutine stack -> system stack. - if it.top || it.systemstack { return false } @@ -198,6 +197,7 @@ func amd64SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool { it.pc = frameOnSystemStack.Ret it.regs = callFrameRegs it.systemstack = true + return true case "runtime.goexit", "runtime.rt0_go", "runtime.mcall": diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index ddd9797eef..580539ff8d 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "fmt" + "runtime" "strings" "github.com/go-delve/delve/pkg/dwarf/frame" @@ -84,15 +85,15 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary return &frame.FrameContext{ RetAddrReg: regnum.ARM64_PC, Regs: map[uint64]frame.DWRule{ - regnum.ARM64_PC: frame.DWRule{ + regnum.ARM64_PC: { Rule: frame.RuleOffset, Offset: int64(-a.PtrSize()), }, - regnum.ARM64_BP: frame.DWRule{ + regnum.ARM64_BP: { Rule: frame.RuleOffset, Offset: int64(-2 * a.PtrSize()), }, - regnum.ARM64_SP: frame.DWRule{ + regnum.ARM64_SP: { Rule: frame.RuleValOffset, Offset: 0, }, @@ -130,7 +131,7 @@ func arm64FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *Binary } if fctxt.Regs[regnum.ARM64_LR].Rule == frame.RuleUndefined { fctxt.Regs[regnum.ARM64_LR] = frame.DWRule{ - Rule: frame.RuleFramePointer, + Rule: frame.RuleRegister, Reg: regnum.ARM64_LR, Offset: 0, } @@ -143,52 +144,142 @@ const arm64cgocallSPOffsetSaveSlot = 0x8 const prevG0schedSPOffsetSaveSlot = 0x10 func arm64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool { - if it.frame.Current.Fn == nil && it.systemstack && it.g != nil && it.top { - it.switchToGoroutineStack() - return true + linux := runtime.GOOS == "linux" + if it.frame.Current.Fn == nil { + if it.systemstack && it.g != nil && it.top { + it.switchToGoroutineStack() + return true + } + return false } - if it.frame.Current.Fn != nil { - switch it.frame.Current.Fn.Name { - case "runtime.asmcgocall", "runtime.cgocallback_gofunc", "runtime.sigpanic", "runtime.cgocallback": - //do nothing - case "runtime.goexit", "runtime.rt0_go", "runtime.mcall": - // Look for "top of stack" functions. - it.atend = true + switch it.frame.Current.Fn.Name { + case "runtime.cgocallback_gofunc", "runtime.cgocallback": + if linux { + // For a detailed description of how this works read the long comment at + // the start of $GOROOT/src/runtime/cgocall.go and the source code of + // runtime.cgocallback_gofunc in $GOROOT/src/runtime/asm_arm64.s + // + // When a C function calls back into go it will eventually call into + // runtime.cgocallback_gofunc which is the function that does the stack + // switch from the system stack back into the goroutine stack + // Since we are going backwards on the stack here we see the transition + // as goroutine stack -> system stack. + if it.top || it.systemstack { + return false + } + + it.loadG0SchedSP() + if it.g0_sched_sp <= 0 { + return false + } + // Entering the system stack. + it.regs.Reg(callFrameRegs.SPRegNum).Uint64Val = it.g0_sched_sp + // Reads the previous value of g0.sched.sp that runtime.cgocallback_gofunc saved on the stack. + it.g0_sched_sp, _ = readUintRaw(it.mem, uint64(it.regs.SP()+prevG0schedSPOffsetSaveSlot), int64(it.bi.Arch.PtrSize())) + it.top = false + callFrameRegs, ret, retaddr := it.advanceRegs() + frameOnSystemStack := it.newStackframe(ret, retaddr) + it.pc = frameOnSystemStack.Ret + it.regs = callFrameRegs + it.systemstack = true + return true - case "crosscall2": - //The offsets get from runtime/cgo/asm_arm64.s:10 - bpoff := uint64(14) - lroff := uint64(15) - if producer := it.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 19) { - // In Go 1.19 (specifically eee6f9f82) the order registers are saved was changed. - bpoff = 22 - lroff = 23 + } + + case "runtime.asmcgocall": + if linux { + if it.top || !it.systemstack { + return false } - newsp, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*24), int64(it.bi.Arch.PtrSize())) - newbp, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*bpoff), int64(it.bi.Arch.PtrSize())) - newlr, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*lroff), int64(it.bi.Arch.PtrSize())) - if it.regs.Reg(it.regs.BPRegNum) != nil { - it.regs.Reg(it.regs.BPRegNum).Uint64Val = uint64(newbp) - } else { - reg, _ := it.readRegisterAt(it.regs.BPRegNum, it.regs.SP()+8*bpoff) - it.regs.AddReg(it.regs.BPRegNum, reg) + + // This function is called by a goroutine to execute a C function and + // switches from the goroutine stack to the system stack. + // Since we are unwinding the stack from callee to caller we have to switch + // from the system stack to the goroutine stack. + off, _ := readIntRaw(it.mem, uint64(it.regs.SP()+arm64cgocallSPOffsetSaveSlot), + int64(it.bi.Arch.PtrSize())) + oldsp := it.regs.SP() + newsp := uint64(int64(it.stackhi) - off) + + it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(int64(newsp)) + // runtime.asmcgocall can also be called from inside the system stack, + // in that case no stack switch actually happens + if it.regs.SP() == oldsp { + return false } - it.regs.Reg(it.regs.LRRegNum).Uint64Val = uint64(newlr) - it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(newsp) - it.pc = newlr + + it.top = false + it.systemstack = false + // The return value is stored in the LR register which is saved at 24(SP). + it.frame.addrret = uint64(int64(it.regs.SP()) + int64(it.bi.Arch.PtrSize()*3)) + it.frame.Ret, _ = readUintRaw(it.mem, it.frame.addrret, int64(it.bi.Arch.PtrSize())) + it.pc = it.frame.Ret + return true - default: - if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.throw" && it.frame.Current.Fn.Name != "runtime.fatalthrow" { - // The runtime switches to the system stack in multiple places. - // This usually happens through a call to runtime.systemstack but there - // are functions that switch to the system stack manually (for example - // runtime.morestack). - // Since we are only interested in printing the system stack for cgo - // calls we switch directly to the goroutine stack if we detect that the - // function at the top of the stack is a runtime function. - it.switchToGoroutineStack() - return true + } + + case "runtime.goexit", "runtime.rt0_go", "runtime.mcall": + // Look for "top of stack" functions. + it.atend = true + return true + case "crosscall2": + //The offsets get from runtime/cgo/asm_arm64.s:10 + bpoff := uint64(14) + lroff := uint64(15) + if producer := it.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 19) { + // In Go 1.19 (specifically eee6f9f82) the order registers are saved was changed. + bpoff = 22 + lroff = 23 + } + newsp, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*24), int64(it.bi.Arch.PtrSize())) + newbp, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*bpoff), int64(it.bi.Arch.PtrSize())) + newlr, _ := readUintRaw(it.mem, uint64(it.regs.SP()+8*lroff), int64(it.bi.Arch.PtrSize())) + if it.regs.Reg(it.regs.BPRegNum) != nil { + it.regs.Reg(it.regs.BPRegNum).Uint64Val = uint64(newbp) + } else { + reg, _ := it.readRegisterAt(it.regs.BPRegNum, it.regs.SP()+8*bpoff) + it.regs.AddReg(it.regs.BPRegNum, reg) + } + it.regs.Reg(it.regs.LRRegNum).Uint64Val = uint64(newlr) + if linux { + it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(newbp) + } else { + it.regs.Reg(it.regs.SPRegNum).Uint64Val = uint64(newsp) + } + it.pc = newlr + return true + case "runtime.mstart": + if linux { + // Calls to runtime.systemstack will switch to the systemstack then: + // 1. alter the goroutine stack so that it looks like systemstack_switch + // was called + // 2. alter the system stack so that it looks like the bottom-most frame + // belongs to runtime.mstart + // If we find a runtime.mstart frame on the system stack of a goroutine + // parked on runtime.systemstack_switch we assume runtime.systemstack was + // called and continue tracing from the parked position. + + if it.top || !it.systemstack || it.g == nil { + return false } + if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" { + return false + } + + it.switchToGoroutineStack() + return true + } + default: + if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.throw" && it.frame.Current.Fn.Name != "runtime.fatalthrow" { + // The runtime switches to the system stack in multiple places. + // This usually happens through a call to runtime.systemstack but there + // are functions that switch to the system stack manually (for example + // runtime.morestack). + // Since we are only interested in printing the system stack for cgo + // calls we switch directly to the goroutine stack if we detect that the + // function at the top of the stack is a runtime function. + it.switchToGoroutineStack() + return true } } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 21d241e4ee..a044944fc4 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -21,6 +21,7 @@ import ( "strconv" "strings" "testing" + "text/tabwriter" "time" "github.com/go-delve/delve/pkg/dwarf/frame" @@ -3312,6 +3313,8 @@ func TestIssue844(t *testing.T) { } func logStacktrace(t *testing.T, p *proc.Target, frames []proc.Stackframe) { + w := tabwriter.NewWriter(os.Stderr, 0, 0, 3, ' ', 0) + fmt.Fprintf(w, "\n%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n", "Call PC", "Frame Offset", "Frame Pointer Offset", "PC", "Return", "Function", "Location", "Top Defer", "Defers") for j := range frames { name := "?" if frames[j].Current.Fn != nil { @@ -3321,25 +3324,33 @@ func logStacktrace(t *testing.T, p *proc.Target, frames []proc.Stackframe) { name = fmt.Sprintf("%s inlined in %s", frames[j].Call.Fn.Name, frames[j].Current.Fn.Name) } - t.Logf("\t%#x %#x %#x %s at %s:%d\n", frames[j].Call.PC, frames[j].FrameOffset(), frames[j].FramePointerOffset(), name, filepath.Base(frames[j].Call.File), frames[j].Call.Line) + topmostdefer := "" if frames[j].TopmostDefer != nil { _, _, fn := frames[j].TopmostDefer.DeferredFunc(p) fnname := "" if fn != nil { fnname = fn.Name } - t.Logf("\t\ttopmost defer: %#x %s\n", frames[j].TopmostDefer.DwrapPC, fnname) + topmostdefer = fmt.Sprintf("%#x %s", frames[j].TopmostDefer.DwrapPC, fnname) } + + defers := "" for deferIdx, _defer := range frames[j].Defers { _, _, fn := _defer.DeferredFunc(p) fnname := "" if fn != nil { fnname = fn.Name } - t.Logf("\t\t%d defer: %#x %s\n", deferIdx, _defer.DwrapPC, fnname) - + defers += fmt.Sprintf("%d %#x %s |", deferIdx, _defer.DwrapPC, fnname) } + + frame := frames[j] + fmt.Fprintf(w, "%#x\t%#x\t%#x\t%#x\t%#x\t%s\t%s:%d\t%s\t%s\t\n", + frame.Call.PC, frame.FrameOffset(), frame.FramePointerOffset(), frame.Current.PC, frame.Ret, + name, filepath.Base(frame.Call.File), frame.Call.Line, topmostdefer, defers) + } + w.Flush() } // stacktraceCheck checks that all the functions listed in tc appear in @@ -3413,7 +3424,6 @@ func TestCgoStacktrace(t *testing.T) { } skipOn(t, "broken - cgo stacktraces", "386") - skipOn(t, "broken - cgo stacktraces", "linux", "arm64") protest.MustHaveCgo(t) // Tests that: @@ -3440,6 +3450,8 @@ func TestCgoStacktrace(t *testing.T) { withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { for itidx, tc := range testCases { + t.Logf("iteration step %d", itidx) + assertNoError(p.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) g, err := proc.GetG(p.CurrentThread()) @@ -3456,7 +3468,6 @@ func TestCgoStacktrace(t *testing.T) { frames, err := g.Stacktrace(100, 0) assertNoError(err, t, fmt.Sprintf("Stacktrace at iteration step %d", itidx)) - t.Logf("iteration step %d", itidx) logStacktrace(t, p, frames) m := stacktraceCheck(t, tc, frames) @@ -3475,7 +3486,7 @@ func TestCgoStacktrace(t *testing.T) { t.Logf("frame %s offset mismatch", tc[i]) } if framePointerOffs[tc[i]] != frames[j].FramePointerOffset() { - t.Logf("frame %s pointer offset mismatch", tc[i]) + t.Logf("frame %s pointer offset mismatch, expected: %#v actual: %#v", tc[i], framePointerOffs[tc[i]], frames[j].FramePointerOffset()) } } else { frameOffs[tc[i]] = frames[j].FrameOffset() @@ -3828,9 +3839,8 @@ func checkFrame(frame proc.Stackframe, fnname, file string, line int, inlined bo if frame.Inlined != inlined { if inlined { return fmt.Errorf("not inlined") - } else { - return fmt.Errorf("inlined") } + return fmt.Errorf("inlined") } return nil } diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 9d847f4980..1c52494d62 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -407,7 +407,14 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin 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, LRRegNum: it.regs.LRRegNum} + callFrameRegs = op.DwarfRegisters{ + StaticBase: callimage.StaticBase, + ByteOrder: it.regs.ByteOrder, + PCRegNum: it.regs.PCRegNum, + SPRegNum: it.regs.SPRegNum, + BPRegNum: it.regs.BPRegNum, + LRRegNum: it.regs.LRRegNum, + } // 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, diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 9b6a70d143..425aecb086 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -874,8 +874,8 @@ func (v *Variable) parseG() (*G, error) { if bpvar := schedVar.fieldVariable("bp"); /* +rtype -opt uintptr */ bpvar != nil && bpvar.Value != nil { bp, _ = constant.Int64Val(bpvar.Value) } - if bpvar := schedVar.fieldVariable("lr"); /* +rtype -opt uintptr */ bpvar != nil && bpvar.Value != nil { - lr, _ = constant.Int64Val(bpvar.Value) + if lrvar := schedVar.fieldVariable("lr"); /* +rtype -opt uintptr */ lrvar != nil && lrvar.Value != nil { + lr, _ = constant.Int64Val(lrvar.Value) } unreadable := false From d15c86e0cf6f69e78801fff0c406253b8bf1e9b1 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Wed, 16 Nov 2022 18:31:33 +0100 Subject: [PATCH 10/72] proc: prefer addresses when setting a breakpoint (#3193) If a breakpoint has both a list of addresses and an expression prefer the list of addresses, otherwise it is impossible to set breakpoint with expressions that depend on the current scope, like 'break +0' which sets a breakpoint on the current line. --- pkg/proc/target_group.go | 4 ++-- pkg/terminal/command_test.go | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go index 07a464625a..be608f3043 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -211,14 +211,14 @@ func enableBreakpointOnTarget(p *Target, lbp *LogicalBreakpoint) error { addrs, err = FindFileLocation(p, lbp.Set.File, lbp.Set.Line) case lbp.Set.FunctionName != "": addrs, err = FindFunctionLocation(p, lbp.Set.FunctionName, lbp.Set.Line) - case lbp.Set.Expr != nil: - addrs = lbp.Set.Expr(p) case len(lbp.Set.PidAddrs) > 0: for _, pidAddr := range lbp.Set.PidAddrs { if pidAddr.Pid == p.Pid() { addrs = append(addrs, pidAddr.Addr) } } + case lbp.Set.Expr != nil: + addrs = lbp.Set.Expr(p) default: return fmt.Errorf("breakpoint %d can not be enabled", lbp.LogicalID) } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 1411956da1..706a83db08 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -1342,3 +1342,17 @@ func TestDisassPosCmd(t *testing.T) { } }) } + +func TestCreateBreakpointByLocExpr(t *testing.T) { + withTestTerminal("math", t, func(term *FakeTerminal) { + out := term.MustExec("break main.main") + position1 := strings.Split(out, " set at ")[1] + term.MustExec("continue") + term.MustExec("clear 1") + out = term.MustExec("break +0") + position2 := strings.Split(out, " set at ")[1] + if position1 != position2 { + t.Fatalf("mismatched positions %q and %q\n", position1, position2) + } + }) +} From f439987f14ec63ca62df6df4cee939320d2df072 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Wed, 16 Nov 2022 18:44:27 +0100 Subject: [PATCH 11/72] G struct offset is a pointer to an offset since go1.20 (#3196) --- pkg/proc/bininfo.go | 55 ++++++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index d5c743cf21..b84108cd9b 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -94,7 +94,8 @@ type BinaryInfo struct { types map[string]dwarfRef packageVars []packageVar // packageVars is a list of all global/package variables in debug_info, sorted by address - gStructOffset uint64 + gStructOffset uint64 + gStructOffsetIsPtr bool // consts[off] lists all the constants with the type defined at offset off. consts constantsMap @@ -700,7 +701,7 @@ func loadBinaryInfo(bi *BinaryInfo, image *Image, path string, entryPoint uint64 // struct in thread local storage. func (bi *BinaryInfo) GStructOffset(mem MemoryReadWriter) (uint64, error) { offset := bi.gStructOffset - if bi.GOOS == "windows" && bi.Arch.Name == "arm64" { + if bi.gStructOffsetIsPtr { // The G struct offset from the TLS section is a pointer // and the address must be dereferenced to find to actual G struct offset. var err error @@ -1645,38 +1646,52 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint wg.Add(2) go bi.parseDebugFramePE(image, peFile, debugInfoBytes, wg) - go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, nil) - if image.index == 0 { - // determine g struct offset only when loading the executable file - wg.Add(1) - go bi.setGStructOffsetPE(entryPoint, peFile, wg) - } + go bi.loadDebugInfoMaps(image, debugInfoBytes, debugLineBytes, wg, func() { + // setGStructOffsetPE requires the image compile units to be loaded, + // so it can't be called concurrently with loadDebugInfoMaps. + if image.index == 0 { + // determine g struct offset only when loading the executable file. + bi.setGStructOffsetPE(entryPoint, peFile) + } + }) return nil } -func (bi *BinaryInfo) setGStructOffsetPE(entryPoint uint64, peFile *pe.File, wg *sync.WaitGroup) { - defer wg.Done() - switch _PEMachine(peFile.Machine) { - case _IMAGE_FILE_MACHINE_AMD64: - // Use ArbitraryUserPointer (0x28) as pointer to pointer - // to G struct per: - // https://golang.org/src/runtime/cgo/gcc_windows_amd64.c - bi.gStructOffset = 0x28 - case _IMAGE_FILE_MACHINE_ARM64: - // Use runtime.tls_g as pointer to offset from R18 to G struct: - // https://golang.org/src/runtime/sys_windows_arm64.s:runtime·wintls +func (bi *BinaryInfo) setGStructOffsetPE(entryPoint uint64, peFile *pe.File) { + readtls_g := func() uint64 { for _, s := range peFile.Symbols { if s.Name == "runtime.tls_g" { i := int(s.SectionNumber) - 1 if 0 <= i && i < len(peFile.Sections) { sect := peFile.Sections[i] if s.Value < sect.VirtualSize { - bi.gStructOffset = entryPoint + uint64(sect.VirtualAddress) + uint64(s.Value) + return entryPoint + uint64(sect.VirtualAddress) + uint64(s.Value) } } break } } + return 0 + } + switch _PEMachine(peFile.Machine) { + case _IMAGE_FILE_MACHINE_AMD64: + producer := bi.Producer() + if producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 20) { + // Use runtime.tls_g as pointer to offset from GS to G struct: + // https://golang.org/src/runtime/sys_windows_amd64.s + bi.gStructOffset = readtls_g() + bi.gStructOffsetIsPtr = true + } else { + // Use ArbitraryUserPointer (0x28) as pointer to pointer + // to G struct per: + // https://golang.org/src/runtime/cgo/gcc_windows_amd64.c + bi.gStructOffset = 0x28 + } + case _IMAGE_FILE_MACHINE_ARM64: + // Use runtime.tls_g as pointer to offset from R18 to G struct: + // https://golang.org/src/runtime/sys_windows_arm64.s + bi.gStructOffset = readtls_g() + bi.gStructOffsetIsPtr = true } } From 2391601038b70a15c25bb3b4767b7ea1136a025f Mon Sep 17 00:00:00 2001 From: Morten Linderud Date: Thu, 17 Nov 2022 09:13:25 +0100 Subject: [PATCH 12/72] cmd/dlv: Include hidden flag -c for coredumpctl support (#3195) `coredumpctl` attempts to pass the core file to the debugger through the `-c` flag. However delve does not support such a flag. This patch makes it a bool flag so we can receive the coredump file from `coredumpctl` $ GOTRACEBACK=crash ./main panic: goroutine 1 [running]: [....] zsh: IOT instruction (core dumped) GOTRACEBACK=crash ./main $ coredumpctl list main TIME PID UID GID SIG COREFILE EXE SIZE Tue 2022-11-15 23:29:07 CET 2047401 1000 1000 SIGABRT present /tmp/go-test/main 60.2K $ coredumpctl gdb --debugger=dlv -A core main PID: 2047401 (main) Signal: 6 (ABRT) Timestamp: Tue 2022-11-15 23:29:07 CET (1min 27s ago) Command Line: ./main Executable: /tmp/go-test/main Owner UID: 1000 (fox) Size on Disk: 60.2K Message: Process 2047401 (main) of user 1000 dumped core. Module /tmp/go-test/main without build-id. Stack trace of thread 2047401: #0 0x000000000045fa01 n/a (/tmp/go-test/main + 0x5fa01) #1 0x0000000000446d3e n/a (/tmp/go-test/main + 0x46d3e) #2 0x0000000000445487 n/a (/tmp/go-test/main + 0x45487) #3 0x000000000045fce6 n/a (/tmp/go-test/main + 0x5fce6) #4 0x000000000045fde0 n/a (/tmp/go-test/main + 0x5fde0) #5 0x0000000000432a49 n/a (/tmp/go-test/main + 0x32a49) #6 0x000000000043211a n/a (/tmp/go-test/main + 0x3211a) #7 0x000000000048d405 n/a (/tmp/go-test/main + 0x8d405) #8 0x0000000000434db2 n/a (/tmp/go-test/main + 0x34db2) #9 0x000000000045e0e1 n/a (/tmp/go-test/main + 0x5e0e1) ELF object binary architecture: AMD x86-64 [dlv core /tmp/go-test/main -c /var/tmp/coredump-JizL2g] Type 'help' for list of commands. (dlv) list main.main Showing /tmp/go-test/main.go:3 (PC: 0x457c26) 1: package main 2: 3: func main() { 4: panic() 5: } (dlv) Signed-off-by: Morten Linderud --- cmd/dlv/cmds/commands.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index c52c7784bb..d76ffe79b0 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -327,6 +327,10 @@ Currently supports linux/amd64 and linux/arm64 core files, windows/amd64 minidum }, Run: coreCmd, } + // -c is unused and exists so delve can be used with coredumpctl + core := false + coreCommand.Flags().BoolVarP(&core, "core", "c", false, "") + coreCommand.Flags().MarkHidden("core") rootCommand.AddCommand(coreCommand) // 'version' subcommand. From a185d0eac1f5b84a3f7e35cc725f2151b4420dbd Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Thu, 17 Nov 2022 13:52:38 +0100 Subject: [PATCH 13/72] Run CI tests on windows/arm64 agents (#3198) --- .teamcity/settings.kts | 3 +++ _scripts/test_windows.ps1 | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index 1901c601ca..d0f060dc4d 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -48,6 +48,9 @@ val targets = arrayOf( "windows/amd64/1.19", "windows/amd64/tip", + "windows/arm64/1.19", + "windows/arm64/tip", + "mac/amd64/1.19", "mac/amd64/tip", diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index 782542173e..e5c5db09bc 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -5,6 +5,11 @@ param ( Set-MpPreference -DisableRealtimeMonitoring $true +if ($arch -eq "arm64") { + # TODO: Remove when TeamCity subproject for windows/arm64 is set up. + Exit 0 +} + # Install Chocolatey #Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) From b9a8bd7f41aa31afe2e82a200255bfa01d3a05d8 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 5 Dec 2022 18:02:22 +0100 Subject: [PATCH 14/72] *: early fixes for go1.20 (#3180) - updated go/packages to support new export format - rewrite testinline.go fixture because the compiler got too smart with constant folding - temporarily disable staticcheck on go1.20 because it doesn't support the new export format. - workaround for go.dev/cl/429601 --- _fixtures/testinline.go | 7 +- cmd/dlv/dlv_test.go | 15 +- go.mod | 2 +- go.sum | 9 + pkg/proc/bininfo.go | 7 +- pkg/proc/proc_test.go | 3 + vendor/golang.org/x/tools/AUTHORS | 3 - vendor/golang.org/x/tools/CONTRIBUTORS | 3 - .../x/tools/go/gcexportdata/gcexportdata.go | 48 +- .../x/tools/go/gcexportdata/importer.go | 3 + .../go/internal/gcimporter/gcimporter.go | 28 +- .../x/tools/go/internal/gcimporter/iimport.go | 60 +- .../go/internal/gcimporter/unified_no.go | 10 + .../go/internal/gcimporter/unified_yes.go | 10 + .../go/internal/gcimporter/ureader_no.go | 19 + .../go/internal/gcimporter/ureader_yes.go | 612 ++++++++++++++++++ .../x/tools/go/internal/pkgbits/codes.go | 77 +++ .../x/tools/go/internal/pkgbits/decoder.go | 433 +++++++++++++ .../x/tools/go/internal/pkgbits/doc.go | 32 + .../x/tools/go/internal/pkgbits/encoder.go | 379 +++++++++++ .../x/tools/go/internal/pkgbits/flags.go | 9 + .../x/tools/go/internal/pkgbits/frames_go1.go | 21 + .../tools/go/internal/pkgbits/frames_go17.go | 28 + .../x/tools/go/internal/pkgbits/reloc.go | 42 ++ .../x/tools/go/internal/pkgbits/support.go | 17 + .../x/tools/go/internal/pkgbits/sync.go | 113 ++++ .../go/internal/pkgbits/syncmarker_string.go | 89 +++ .../golang.org/x/tools/go/packages/golist.go | 7 +- vendor/modules.txt | 3 +- 29 files changed, 2021 insertions(+), 68 deletions(-) delete mode 100644 vendor/golang.org/x/tools/AUTHORS delete mode 100644 vendor/golang.org/x/tools/CONTRIBUTORS create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/unified_no.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/unified_yes.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/ureader_no.go create mode 100644 vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/codes.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/doc.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/flags.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/frames_go1.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/frames_go17.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/support.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/sync.go create mode 100644 vendor/golang.org/x/tools/go/internal/pkgbits/syncmarker_string.go diff --git a/_fixtures/testinline.go b/_fixtures/testinline.go index 4848b90d94..9692dc7c81 100644 --- a/_fixtures/testinline.go +++ b/_fixtures/testinline.go @@ -4,7 +4,7 @@ import "fmt" func inlineThis(a int) int { z := a * a - return z + a + return f(z + a) } func initialize(a, b *int) { @@ -19,3 +19,8 @@ func main() { b = inlineThis(b) fmt.Printf("%d %d\n", a, b) } + +//go:noinline +func f(x int) int { + return x +} diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 562ade5e7f..a7ed67dd14 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -701,8 +701,15 @@ func TestDAPCmd(t *testing.T) { client.DisconnectRequest() client.ExpectDisconnectResponse(t) client.ExpectTerminatedEvent(t) - if _, err := client.ReadMessage(); err != io.EOF { - t.Errorf("got %q, want \"EOF\"\n", err) + _, err = client.ReadMessage() + if runtime.GOOS == "windows" { + if err == nil { + t.Errorf("got %q, want non-nil\n", err) + } + } else { + if err != io.EOF { + t.Errorf("got %q, want \"EOF\"\n", err) + } } client.Close() cmd.Wait() @@ -1290,6 +1297,10 @@ func TestStaticcheck(t *testing.T) { if err != nil { t.Skip("staticcheck not installed") } + if goversion.VersionAfterOrEqual(runtime.Version(), 1, 20) { + //TODO(aarzilli): remove this when there is a version of staticcheck that can support go1.20 + t.Skip("staticcheck does not support go1.20 currently") + } // default checks minus SA1019 which complains about deprecated identifiers, which change between versions of Go. args := []string{"-tests=false", "-checks=all,-SA1019,-ST1000,-ST1003,-ST1016,-S1021,-ST1023", "github.com/go-delve/delve/..."} // * SA1019 is disabled because new deprecations get added on every version diff --git a/go.mod b/go.mod index 922b3bc882..000ace364f 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( go.starlark.net v0.0.0-20220816155156-cfacd8902214 golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 golang.org/x/sys v0.0.0-20220908164124-27713097b956 - golang.org/x/tools v0.1.11 + golang.org/x/tools v0.1.12 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index a4d4497902..400c4568bf 100644 --- a/go.sum +++ b/go.sum @@ -219,6 +219,8 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -273,6 +275,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -282,6 +285,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -304,6 +308,8 @@ golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= @@ -311,6 +317,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho= golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -340,6 +347,8 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index b84108cd9b..b40ad65690 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1520,7 +1520,12 @@ func (bi *BinaryInfo) parseDebugFrameElf(image *Image, dwarfFile, exeFile *elf.F var ehFrameAddr uint64 if ehFrameSection != nil { ehFrameAddr = ehFrameSection.Addr - ehFrameData, _ = ehFrameSection.Data() + // Workaround for go.dev/cl/429601 + if ehFrameSection.Type == elf.SHT_NOBITS { + ehFrameData = make([]byte, ehFrameSection.Size) + } else { + ehFrameData, _ = ehFrameSection.Data() + } } bi.parseDebugFrameGeneral(image, debugFrameData, ".debug_frame", debugFrameErr, ehFrameData, ehFrameAddr, ".eh_frame", frame.DwarfEndian(debugInfoBytes)) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index a044944fc4..0e30923f50 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -3994,6 +3994,9 @@ func TestInlineStep(t *testing.T) { {contContinue, 18}, {contStep, 6}, {contStep, 7}, + {contStep, 24}, + {contStep, 25}, + {contStep, 7}, {contStep, 18}, {contStep, 19}, }) diff --git a/vendor/golang.org/x/tools/AUTHORS b/vendor/golang.org/x/tools/AUTHORS deleted file mode 100644 index 15167cd746..0000000000 --- a/vendor/golang.org/x/tools/AUTHORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code refers to The Go Authors for copyright purposes. -# The master list of authors is in the main Go distribution, -# visible at http://tip.golang.org/AUTHORS. diff --git a/vendor/golang.org/x/tools/CONTRIBUTORS b/vendor/golang.org/x/tools/CONTRIBUTORS deleted file mode 100644 index 1c4577e968..0000000000 --- a/vendor/golang.org/x/tools/CONTRIBUTORS +++ /dev/null @@ -1,3 +0,0 @@ -# This source code was written by the Go contributors. -# The master list of contributors is in the main Go distribution, -# visible at http://tip.golang.org/CONTRIBUTORS. diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index d50826dbf7..2ed25a7502 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -22,26 +22,42 @@ package gcexportdata // import "golang.org/x/tools/go/gcexportdata" import ( "bufio" "bytes" + "encoding/json" "fmt" "go/token" "go/types" "io" "io/ioutil" + "os/exec" "golang.org/x/tools/go/internal/gcimporter" ) // Find returns the name of an object (.o) or archive (.a) file // containing type information for the specified import path, -// using the workspace layout conventions of go/build. +// using the go command. // If no file was found, an empty filename is returned. // // A relative srcDir is interpreted relative to the current working directory. // // Find also returns the package's resolved (canonical) import path, // reflecting the effects of srcDir and vendoring on importPath. +// +// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages, +// which is more efficient. func Find(importPath, srcDir string) (filename, path string) { - return gcimporter.FindPkg(importPath, srcDir) + cmd := exec.Command("go", "list", "-json", "-export", "--", importPath) + cmd.Dir = srcDir + out, err := cmd.CombinedOutput() + if err != nil { + return "", "" + } + var data struct { + ImportPath string + Export string + } + json.Unmarshal(out, &data) + return data.Export, data.ImportPath } // NewReader returns a reader for the export data section of an object @@ -100,13 +116,29 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, // The indexed export format starts with an 'i'; the older // binary export format starts with a 'c', 'd', or 'v' // (from "version"). Select appropriate importer. - if len(data) > 0 && data[0] == 'i' { - _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) - return pkg, err - } + if len(data) > 0 { + switch data[0] { + case 'i': + _, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path) + return pkg, err + + case 'v', 'c', 'd': + _, pkg, err := gcimporter.BImportData(fset, imports, data, path) + return pkg, err - _, pkg, err := gcimporter.BImportData(fset, imports, data, path) - return pkg, err + case 'u': + _, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path) + return pkg, err + + default: + l := len(data) + if l > 10 { + l = 10 + } + return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), path) + } + } + return nil, fmt.Errorf("empty export data for %s", path) } // Write writes encoded type information for the specified package to out. diff --git a/vendor/golang.org/x/tools/go/gcexportdata/importer.go b/vendor/golang.org/x/tools/go/gcexportdata/importer.go index fe6ed93215..37a7247e26 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/importer.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/importer.go @@ -22,6 +22,9 @@ import ( // version-skew problems described in the documentation of this package, // or to control the FileSet or access the imports map populated during // package loading. +// +// Deprecated: Use the higher-level API in golang.org/x/tools/go/packages, +// which is more efficient. func NewImporter(fset *token.FileSet, imports map[string]*types.Package) types.ImporterFrom { return importer{fset, imports} } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go index 493bfa03b0..e96c39600d 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go @@ -181,8 +181,9 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func defer rc.Close() var hdr string + var size int64 buf := bufio.NewReader(rc) - if hdr, _, err = FindExportData(buf); err != nil { + if hdr, size, err = FindExportData(buf); err != nil { return } @@ -210,10 +211,27 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func // The indexed export format starts with an 'i'; the older // binary export format starts with a 'c', 'd', or 'v' // (from "version"). Select appropriate importer. - if len(data) > 0 && data[0] == 'i' { - _, pkg, err = IImportData(fset, packages, data[1:], id) - } else { - _, pkg, err = BImportData(fset, packages, data, id) + if len(data) > 0 { + switch data[0] { + case 'i': + _, pkg, err := IImportData(fset, packages, data[1:], id) + return pkg, err + + case 'v', 'c', 'd': + _, pkg, err := BImportData(fset, packages, data, id) + return pkg, err + + case 'u': + _, pkg, err := UImportData(fset, packages, data[1:size], id) + return pkg, err + + default: + l := len(data) + if l > 10 { + l = 10 + } + return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), id) + } } default: diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go index 28b91b8656..4caa0f55d9 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -17,6 +17,7 @@ import ( "go/token" "go/types" "io" + "math/big" "sort" "strings" @@ -512,7 +513,9 @@ func (r *importReader) value() (typ types.Type, val constant.Value) { val = constant.MakeString(r.string()) case types.IsInteger: - val = r.mpint(b) + var x big.Int + r.mpint(&x, b) + val = constant.Make(&x) case types.IsFloat: val = r.mpfloat(b) @@ -561,8 +564,8 @@ func intSize(b *types.Basic) (signed bool, maxBytes uint) { return } -func (r *importReader) mpint(b *types.Basic) constant.Value { - signed, maxBytes := intSize(b) +func (r *importReader) mpint(x *big.Int, typ *types.Basic) { + signed, maxBytes := intSize(typ) maxSmall := 256 - maxBytes if signed { @@ -581,7 +584,8 @@ func (r *importReader) mpint(b *types.Basic) constant.Value { v = ^v } } - return constant.MakeInt64(v) + x.SetInt64(v) + return } v := -n @@ -591,47 +595,23 @@ func (r *importReader) mpint(b *types.Basic) constant.Value { if v < 1 || uint(v) > maxBytes { errorf("weird decoding: %v, %v => %v", n, signed, v) } - - buf := make([]byte, v) - io.ReadFull(&r.declReader, buf) - - // convert to little endian - // TODO(gri) go/constant should have a more direct conversion function - // (e.g., once it supports a big.Float based implementation) - for i, j := 0, len(buf)-1; i < j; i, j = i+1, j-1 { - buf[i], buf[j] = buf[j], buf[i] - } - - x := constant.MakeFromBytes(buf) + b := make([]byte, v) + io.ReadFull(&r.declReader, b) + x.SetBytes(b) if signed && n&1 != 0 { - x = constant.UnaryOp(token.SUB, x, 0) + x.Neg(x) } - return x } -func (r *importReader) mpfloat(b *types.Basic) constant.Value { - x := r.mpint(b) - if constant.Sign(x) == 0 { - return x - } - - exp := r.int64() - switch { - case exp > 0: - x = constant.Shift(x, token.SHL, uint(exp)) - // Ensure that the imported Kind is Float, else this constant may run into - // bitsize limits on overlarge integers. Eventually we can instead adopt - // the approach of CL 288632, but that CL relies on go/constant APIs that - // were introduced in go1.13. - // - // TODO(rFindley): sync the logic here with tip Go once we no longer - // support go1.12. - x = constant.ToFloat(x) - case exp < 0: - d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp)) - x = constant.BinaryOp(x, token.QUO, d) +func (r *importReader) mpfloat(typ *types.Basic) constant.Value { + var mant big.Int + r.mpint(&mant, typ) + var f big.Float + f.SetInt(&mant) + if f.Sign() != 0 { + f.SetMantExp(&f, int(r.int64())) } - return x + return constant.Make(&f) } func (r *importReader) ident() string { diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/unified_no.go b/vendor/golang.org/x/tools/go/internal/gcimporter/unified_no.go new file mode 100644 index 0000000000..286bf44548 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/unified_no.go @@ -0,0 +1,10 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(go1.18 && goexperiment.unified) +// +build !go1.18 !goexperiment.unified + +package gcimporter + +const unifiedIR = false diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/unified_yes.go b/vendor/golang.org/x/tools/go/internal/gcimporter/unified_yes.go new file mode 100644 index 0000000000..b5d69ffbe6 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/unified_yes.go @@ -0,0 +1,10 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 && goexperiment.unified +// +build go1.18,goexperiment.unified + +package gcimporter + +const unifiedIR = true diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_no.go b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_no.go new file mode 100644 index 0000000000..8eb20729c2 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_no.go @@ -0,0 +1,19 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.18 +// +build !go1.18 + +package gcimporter + +import ( + "fmt" + "go/token" + "go/types" +) + +func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + err = fmt.Errorf("go/tools compiled with a Go version earlier than 1.18 cannot read unified IR export data") + return +} diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go new file mode 100644 index 0000000000..3c1a437543 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/ureader_yes.go @@ -0,0 +1,612 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Derived from go/internal/gcimporter/ureader.go + +//go:build go1.18 +// +build go1.18 + +package gcimporter + +import ( + "go/token" + "go/types" + "strings" + + "golang.org/x/tools/go/internal/pkgbits" +) + +// A pkgReader holds the shared state for reading a unified IR package +// description. +type pkgReader struct { + pkgbits.PkgDecoder + + fake fakeFileSet + + ctxt *types.Context + imports map[string]*types.Package // previously imported packages, indexed by path + + // lazily initialized arrays corresponding to the unified IR + // PosBase, Pkg, and Type sections, respectively. + posBases []string // position bases (i.e., file names) + pkgs []*types.Package + typs []types.Type + + // laterFns holds functions that need to be invoked at the end of + // import reading. + laterFns []func() +} + +// later adds a function to be invoked at the end of import reading. +func (pr *pkgReader) later(fn func()) { + pr.laterFns = append(pr.laterFns, fn) +} + +// See cmd/compile/internal/noder.derivedInfo. +type derivedInfo struct { + idx pkgbits.Index + needed bool +} + +// See cmd/compile/internal/noder.typeInfo. +type typeInfo struct { + idx pkgbits.Index + derived bool +} + +func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { + s := string(data) + s = s[:strings.LastIndex(s, "\n$$\n")] + input := pkgbits.NewPkgDecoder(path, s) + pkg = readUnifiedPackage(fset, nil, imports, input) + return +} + +// readUnifiedPackage reads a package description from the given +// unified IR export data decoder. +func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[string]*types.Package, input pkgbits.PkgDecoder) *types.Package { + pr := pkgReader{ + PkgDecoder: input, + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*fileInfo), + }, + + ctxt: ctxt, + imports: imports, + + posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)), + pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)), + typs: make([]types.Type, input.NumElems(pkgbits.RelocType)), + } + defer pr.fake.setLines() + + r := pr.newReader(pkgbits.RelocMeta, pkgbits.PublicRootIdx, pkgbits.SyncPublic) + pkg := r.pkg() + r.Bool() // has init + + for i, n := 0, r.Len(); i < n; i++ { + // As if r.obj(), but avoiding the Scope.Lookup call, + // to avoid eager loading of imports. + r.Sync(pkgbits.SyncObject) + assert(!r.Bool()) + r.p.objIdx(r.Reloc(pkgbits.RelocObj)) + assert(r.Len() == 0) + } + + r.Sync(pkgbits.SyncEOF) + + for _, fn := range pr.laterFns { + fn() + } + + pkg.MarkComplete() + return pkg +} + +// A reader holds the state for reading a single unified IR element +// within a package. +type reader struct { + pkgbits.Decoder + + p *pkgReader + + dict *readerDict +} + +// A readerDict holds the state for type parameters that parameterize +// the current unified IR element. +type readerDict struct { + // bounds is a slice of typeInfos corresponding to the underlying + // bounds of the element's type parameters. + bounds []typeInfo + + // tparams is a slice of the constructed TypeParams for the element. + tparams []*types.TypeParam + + // devived is a slice of types derived from tparams, which may be + // instantiated while reading the current element. + derived []derivedInfo + derivedTypes []types.Type // lazily instantiated from derived +} + +func (pr *pkgReader) newReader(k pkgbits.RelocKind, idx pkgbits.Index, marker pkgbits.SyncMarker) *reader { + return &reader{ + Decoder: pr.NewDecoder(k, idx, marker), + p: pr, + } +} + +// @@@ Positions + +func (r *reader) pos() token.Pos { + r.Sync(pkgbits.SyncPos) + if !r.Bool() { + return token.NoPos + } + + // TODO(mdempsky): Delta encoding. + posBase := r.posBase() + line := r.Uint() + col := r.Uint() + return r.p.fake.pos(posBase, int(line), int(col)) +} + +func (r *reader) posBase() string { + return r.p.posBaseIdx(r.Reloc(pkgbits.RelocPosBase)) +} + +func (pr *pkgReader) posBaseIdx(idx pkgbits.Index) string { + if b := pr.posBases[idx]; b != "" { + return b + } + + r := pr.newReader(pkgbits.RelocPosBase, idx, pkgbits.SyncPosBase) + + // Within types2, position bases have a lot more details (e.g., + // keeping track of where //line directives appeared exactly). + // + // For go/types, we just track the file name. + + filename := r.String() + + if r.Bool() { // file base + // Was: "b = token.NewTrimmedFileBase(filename, true)" + } else { // line base + pos := r.pos() + line := r.Uint() + col := r.Uint() + + // Was: "b = token.NewLineBase(pos, filename, true, line, col)" + _, _, _ = pos, line, col + } + + b := filename + pr.posBases[idx] = b + return b +} + +// @@@ Packages + +func (r *reader) pkg() *types.Package { + r.Sync(pkgbits.SyncPkg) + return r.p.pkgIdx(r.Reloc(pkgbits.RelocPkg)) +} + +func (pr *pkgReader) pkgIdx(idx pkgbits.Index) *types.Package { + // TODO(mdempsky): Consider using some non-nil pointer to indicate + // the universe scope, so we don't need to keep re-reading it. + if pkg := pr.pkgs[idx]; pkg != nil { + return pkg + } + + pkg := pr.newReader(pkgbits.RelocPkg, idx, pkgbits.SyncPkgDef).doPkg() + pr.pkgs[idx] = pkg + return pkg +} + +func (r *reader) doPkg() *types.Package { + path := r.String() + switch path { + case "": + path = r.p.PkgPath() + case "builtin": + return nil // universe + case "unsafe": + return types.Unsafe + } + + if pkg := r.p.imports[path]; pkg != nil { + return pkg + } + + name := r.String() + + pkg := types.NewPackage(path, name) + r.p.imports[path] = pkg + + imports := make([]*types.Package, r.Len()) + for i := range imports { + imports[i] = r.pkg() + } + pkg.SetImports(imports) + + return pkg +} + +// @@@ Types + +func (r *reader) typ() types.Type { + return r.p.typIdx(r.typInfo(), r.dict) +} + +func (r *reader) typInfo() typeInfo { + r.Sync(pkgbits.SyncType) + if r.Bool() { + return typeInfo{idx: pkgbits.Index(r.Len()), derived: true} + } + return typeInfo{idx: r.Reloc(pkgbits.RelocType), derived: false} +} + +func (pr *pkgReader) typIdx(info typeInfo, dict *readerDict) types.Type { + idx := info.idx + var where *types.Type + if info.derived { + where = &dict.derivedTypes[idx] + idx = dict.derived[idx].idx + } else { + where = &pr.typs[idx] + } + + if typ := *where; typ != nil { + return typ + } + + r := pr.newReader(pkgbits.RelocType, idx, pkgbits.SyncTypeIdx) + r.dict = dict + + typ := r.doTyp() + assert(typ != nil) + + // See comment in pkgReader.typIdx explaining how this happens. + if prev := *where; prev != nil { + return prev + } + + *where = typ + return typ +} + +func (r *reader) doTyp() (res types.Type) { + switch tag := pkgbits.CodeType(r.Code(pkgbits.SyncType)); tag { + default: + errorf("unhandled type tag: %v", tag) + panic("unreachable") + + case pkgbits.TypeBasic: + return types.Typ[r.Len()] + + case pkgbits.TypeNamed: + obj, targs := r.obj() + name := obj.(*types.TypeName) + if len(targs) != 0 { + t, _ := types.Instantiate(r.p.ctxt, name.Type(), targs, false) + return t + } + return name.Type() + + case pkgbits.TypeTypeParam: + return r.dict.tparams[r.Len()] + + case pkgbits.TypeArray: + len := int64(r.Uint64()) + return types.NewArray(r.typ(), len) + case pkgbits.TypeChan: + dir := types.ChanDir(r.Len()) + return types.NewChan(dir, r.typ()) + case pkgbits.TypeMap: + return types.NewMap(r.typ(), r.typ()) + case pkgbits.TypePointer: + return types.NewPointer(r.typ()) + case pkgbits.TypeSignature: + return r.signature(nil, nil, nil) + case pkgbits.TypeSlice: + return types.NewSlice(r.typ()) + case pkgbits.TypeStruct: + return r.structType() + case pkgbits.TypeInterface: + return r.interfaceType() + case pkgbits.TypeUnion: + return r.unionType() + } +} + +func (r *reader) structType() *types.Struct { + fields := make([]*types.Var, r.Len()) + var tags []string + for i := range fields { + pos := r.pos() + pkg, name := r.selector() + ftyp := r.typ() + tag := r.String() + embedded := r.Bool() + + fields[i] = types.NewField(pos, pkg, name, ftyp, embedded) + if tag != "" { + for len(tags) < i { + tags = append(tags, "") + } + tags = append(tags, tag) + } + } + return types.NewStruct(fields, tags) +} + +func (r *reader) unionType() *types.Union { + terms := make([]*types.Term, r.Len()) + for i := range terms { + terms[i] = types.NewTerm(r.Bool(), r.typ()) + } + return types.NewUnion(terms) +} + +func (r *reader) interfaceType() *types.Interface { + methods := make([]*types.Func, r.Len()) + embeddeds := make([]types.Type, r.Len()) + implicit := len(methods) == 0 && len(embeddeds) == 1 && r.Bool() + + for i := range methods { + pos := r.pos() + pkg, name := r.selector() + mtyp := r.signature(nil, nil, nil) + methods[i] = types.NewFunc(pos, pkg, name, mtyp) + } + + for i := range embeddeds { + embeddeds[i] = r.typ() + } + + iface := types.NewInterfaceType(methods, embeddeds) + if implicit { + iface.MarkImplicit() + } + return iface +} + +func (r *reader) signature(recv *types.Var, rtparams, tparams []*types.TypeParam) *types.Signature { + r.Sync(pkgbits.SyncSignature) + + params := r.params() + results := r.params() + variadic := r.Bool() + + return types.NewSignatureType(recv, rtparams, tparams, params, results, variadic) +} + +func (r *reader) params() *types.Tuple { + r.Sync(pkgbits.SyncParams) + + params := make([]*types.Var, r.Len()) + for i := range params { + params[i] = r.param() + } + + return types.NewTuple(params...) +} + +func (r *reader) param() *types.Var { + r.Sync(pkgbits.SyncParam) + + pos := r.pos() + pkg, name := r.localIdent() + typ := r.typ() + + return types.NewParam(pos, pkg, name, typ) +} + +// @@@ Objects + +func (r *reader) obj() (types.Object, []types.Type) { + r.Sync(pkgbits.SyncObject) + + assert(!r.Bool()) + + pkg, name := r.p.objIdx(r.Reloc(pkgbits.RelocObj)) + obj := pkgScope(pkg).Lookup(name) + + targs := make([]types.Type, r.Len()) + for i := range targs { + targs[i] = r.typ() + } + + return obj, targs +} + +func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { + rname := pr.newReader(pkgbits.RelocName, idx, pkgbits.SyncObject1) + + objPkg, objName := rname.qualifiedIdent() + assert(objName != "") + + tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj)) + + if tag == pkgbits.ObjStub { + assert(objPkg == nil || objPkg == types.Unsafe) + return objPkg, objName + } + + if objPkg.Scope().Lookup(objName) == nil { + dict := pr.objDictIdx(idx) + + r := pr.newReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1) + r.dict = dict + + declare := func(obj types.Object) { + objPkg.Scope().Insert(obj) + } + + switch tag { + default: + panic("weird") + + case pkgbits.ObjAlias: + pos := r.pos() + typ := r.typ() + declare(types.NewTypeName(pos, objPkg, objName, typ)) + + case pkgbits.ObjConst: + pos := r.pos() + typ := r.typ() + val := r.Value() + declare(types.NewConst(pos, objPkg, objName, typ, val)) + + case pkgbits.ObjFunc: + pos := r.pos() + tparams := r.typeParamNames() + sig := r.signature(nil, nil, tparams) + declare(types.NewFunc(pos, objPkg, objName, sig)) + + case pkgbits.ObjType: + pos := r.pos() + + obj := types.NewTypeName(pos, objPkg, objName, nil) + named := types.NewNamed(obj, nil, nil) + declare(obj) + + named.SetTypeParams(r.typeParamNames()) + + // TODO(mdempsky): Rewrite receiver types to underlying is an + // Interface? The go/types importer does this (I think because + // unit tests expected that), but cmd/compile doesn't care + // about it, so maybe we can avoid worrying about that here. + rhs := r.typ() + r.p.later(func() { + underlying := rhs.Underlying() + named.SetUnderlying(underlying) + }) + + for i, n := 0, r.Len(); i < n; i++ { + named.AddMethod(r.method()) + } + + case pkgbits.ObjVar: + pos := r.pos() + typ := r.typ() + declare(types.NewVar(pos, objPkg, objName, typ)) + } + } + + return objPkg, objName +} + +func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { + r := pr.newReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1) + + var dict readerDict + + if implicits := r.Len(); implicits != 0 { + errorf("unexpected object with %v implicit type parameter(s)", implicits) + } + + dict.bounds = make([]typeInfo, r.Len()) + for i := range dict.bounds { + dict.bounds[i] = r.typInfo() + } + + dict.derived = make([]derivedInfo, r.Len()) + dict.derivedTypes = make([]types.Type, len(dict.derived)) + for i := range dict.derived { + dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()} + } + + // function references follow, but reader doesn't need those + + return &dict +} + +func (r *reader) typeParamNames() []*types.TypeParam { + r.Sync(pkgbits.SyncTypeParamNames) + + // Note: This code assumes it only processes objects without + // implement type parameters. This is currently fine, because + // reader is only used to read in exported declarations, which are + // always package scoped. + + if len(r.dict.bounds) == 0 { + return nil + } + + // Careful: Type parameter lists may have cycles. To allow for this, + // we construct the type parameter list in two passes: first we + // create all the TypeNames and TypeParams, then we construct and + // set the bound type. + + r.dict.tparams = make([]*types.TypeParam, len(r.dict.bounds)) + for i := range r.dict.bounds { + pos := r.pos() + pkg, name := r.localIdent() + + tname := types.NewTypeName(pos, pkg, name, nil) + r.dict.tparams[i] = types.NewTypeParam(tname, nil) + } + + typs := make([]types.Type, len(r.dict.bounds)) + for i, bound := range r.dict.bounds { + typs[i] = r.p.typIdx(bound, r.dict) + } + + // TODO(mdempsky): This is subtle, elaborate further. + // + // We have to save tparams outside of the closure, because + // typeParamNames() can be called multiple times with the same + // dictionary instance. + // + // Also, this needs to happen later to make sure SetUnderlying has + // been called. + // + // TODO(mdempsky): Is it safe to have a single "later" slice or do + // we need to have multiple passes? See comments on CL 386002 and + // go.dev/issue/52104. + tparams := r.dict.tparams + r.p.later(func() { + for i, typ := range typs { + tparams[i].SetConstraint(typ) + } + }) + + return r.dict.tparams +} + +func (r *reader) method() *types.Func { + r.Sync(pkgbits.SyncMethod) + pos := r.pos() + pkg, name := r.selector() + + rparams := r.typeParamNames() + sig := r.signature(r.param(), rparams, nil) + + _ = r.pos() // TODO(mdempsky): Remove; this is a hacker for linker.go. + return types.NewFunc(pos, pkg, name, sig) +} + +func (r *reader) qualifiedIdent() (*types.Package, string) { return r.ident(pkgbits.SyncSym) } +func (r *reader) localIdent() (*types.Package, string) { return r.ident(pkgbits.SyncLocalIdent) } +func (r *reader) selector() (*types.Package, string) { return r.ident(pkgbits.SyncSelector) } + +func (r *reader) ident(marker pkgbits.SyncMarker) (*types.Package, string) { + r.Sync(marker) + return r.pkg(), r.String() +} + +// pkgScope returns pkg.Scope(). +// If pkg is nil, it returns types.Universe instead. +// +// TODO(mdempsky): Remove after x/tools can depend on Go 1.19. +func pkgScope(pkg *types.Package) *types.Scope { + if pkg != nil { + return pkg.Scope() + } + return types.Universe +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/codes.go b/vendor/golang.org/x/tools/go/internal/pkgbits/codes.go new file mode 100644 index 0000000000..f0cabde96e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/codes.go @@ -0,0 +1,77 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +// A Code is an enum value that can be encoded into bitstreams. +// +// Code types are preferable for enum types, because they allow +// Decoder to detect desyncs. +type Code interface { + // Marker returns the SyncMarker for the Code's dynamic type. + Marker() SyncMarker + + // Value returns the Code's ordinal value. + Value() int +} + +// A CodeVal distinguishes among go/constant.Value encodings. +type CodeVal int + +func (c CodeVal) Marker() SyncMarker { return SyncVal } +func (c CodeVal) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + ValBool CodeVal = iota + ValString + ValInt64 + ValBigInt + ValBigRat + ValBigFloat +) + +// A CodeType distinguishes among go/types.Type encodings. +type CodeType int + +func (c CodeType) Marker() SyncMarker { return SyncType } +func (c CodeType) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + TypeBasic CodeType = iota + TypeNamed + TypePointer + TypeSlice + TypeArray + TypeChan + TypeMap + TypeSignature + TypeStruct + TypeInterface + TypeUnion + TypeTypeParam +) + +// A CodeObj distinguishes among go/types.Object encodings. +type CodeObj int + +func (c CodeObj) Marker() SyncMarker { return SyncCodeObj } +func (c CodeObj) Value() int { return int(c) } + +// Note: These values are public and cannot be changed without +// updating the go/types importers. + +const ( + ObjAlias CodeObj = iota + ObjConst + ObjType + ObjFunc + ObjVar + ObjStub +) diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go b/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go new file mode 100644 index 0000000000..2bc793668e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/decoder.go @@ -0,0 +1,433 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +import ( + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "math/big" + "os" + "runtime" + "strings" +) + +// A PkgDecoder provides methods for decoding a package's Unified IR +// export data. +type PkgDecoder struct { + // version is the file format version. + version uint32 + + // sync indicates whether the file uses sync markers. + sync bool + + // pkgPath is the package path for the package to be decoded. + // + // TODO(mdempsky): Remove; unneeded since CL 391014. + pkgPath string + + // elemData is the full data payload of the encoded package. + // Elements are densely and contiguously packed together. + // + // The last 8 bytes of elemData are the package fingerprint. + elemData string + + // elemEnds stores the byte-offset end positions of element + // bitstreams within elemData. + // + // For example, element I's bitstream data starts at elemEnds[I-1] + // (or 0, if I==0) and ends at elemEnds[I]. + // + // Note: elemEnds is indexed by absolute indices, not + // section-relative indices. + elemEnds []uint32 + + // elemEndsEnds stores the index-offset end positions of relocation + // sections within elemEnds. + // + // For example, section K's end positions start at elemEndsEnds[K-1] + // (or 0, if K==0) and end at elemEndsEnds[K]. + elemEndsEnds [numRelocs]uint32 +} + +// PkgPath returns the package path for the package +// +// TODO(mdempsky): Remove; unneeded since CL 391014. +func (pr *PkgDecoder) PkgPath() string { return pr.pkgPath } + +// SyncMarkers reports whether pr uses sync markers. +func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync } + +// NewPkgDecoder returns a PkgDecoder initialized to read the Unified +// IR export data from input. pkgPath is the package path for the +// compilation unit that produced the export data. +// +// TODO(mdempsky): Remove pkgPath parameter; unneeded since CL 391014. +func NewPkgDecoder(pkgPath, input string) PkgDecoder { + pr := PkgDecoder{ + pkgPath: pkgPath, + } + + // TODO(mdempsky): Implement direct indexing of input string to + // avoid copying the position information. + + r := strings.NewReader(input) + + assert(binary.Read(r, binary.LittleEndian, &pr.version) == nil) + + switch pr.version { + default: + panic(fmt.Errorf("unsupported version: %v", pr.version)) + case 0: + // no flags + case 1: + var flags uint32 + assert(binary.Read(r, binary.LittleEndian, &flags) == nil) + pr.sync = flags&flagSyncMarkers != 0 + } + + assert(binary.Read(r, binary.LittleEndian, pr.elemEndsEnds[:]) == nil) + + pr.elemEnds = make([]uint32, pr.elemEndsEnds[len(pr.elemEndsEnds)-1]) + assert(binary.Read(r, binary.LittleEndian, pr.elemEnds[:]) == nil) + + pos, err := r.Seek(0, os.SEEK_CUR) + assert(err == nil) + + pr.elemData = input[pos:] + assert(len(pr.elemData)-8 == int(pr.elemEnds[len(pr.elemEnds)-1])) + + return pr +} + +// NumElems returns the number of elements in section k. +func (pr *PkgDecoder) NumElems(k RelocKind) int { + count := int(pr.elemEndsEnds[k]) + if k > 0 { + count -= int(pr.elemEndsEnds[k-1]) + } + return count +} + +// TotalElems returns the total number of elements across all sections. +func (pr *PkgDecoder) TotalElems() int { + return len(pr.elemEnds) +} + +// Fingerprint returns the package fingerprint. +func (pr *PkgDecoder) Fingerprint() [8]byte { + var fp [8]byte + copy(fp[:], pr.elemData[len(pr.elemData)-8:]) + return fp +} + +// AbsIdx returns the absolute index for the given (section, index) +// pair. +func (pr *PkgDecoder) AbsIdx(k RelocKind, idx Index) int { + absIdx := int(idx) + if k > 0 { + absIdx += int(pr.elemEndsEnds[k-1]) + } + if absIdx >= int(pr.elemEndsEnds[k]) { + errorf("%v:%v is out of bounds; %v", k, idx, pr.elemEndsEnds) + } + return absIdx +} + +// DataIdx returns the raw element bitstream for the given (section, +// index) pair. +func (pr *PkgDecoder) DataIdx(k RelocKind, idx Index) string { + absIdx := pr.AbsIdx(k, idx) + + var start uint32 + if absIdx > 0 { + start = pr.elemEnds[absIdx-1] + } + end := pr.elemEnds[absIdx] + + return pr.elemData[start:end] +} + +// StringIdx returns the string value for the given string index. +func (pr *PkgDecoder) StringIdx(idx Index) string { + return pr.DataIdx(RelocString, idx) +} + +// NewDecoder returns a Decoder for the given (section, index) pair, +// and decodes the given SyncMarker from the element bitstream. +func (pr *PkgDecoder) NewDecoder(k RelocKind, idx Index, marker SyncMarker) Decoder { + r := pr.NewDecoderRaw(k, idx) + r.Sync(marker) + return r +} + +// NewDecoderRaw returns a Decoder for the given (section, index) pair. +// +// Most callers should use NewDecoder instead. +func (pr *PkgDecoder) NewDecoderRaw(k RelocKind, idx Index) Decoder { + r := Decoder{ + common: pr, + k: k, + Idx: idx, + } + + // TODO(mdempsky) r.data.Reset(...) after #44505 is resolved. + r.Data = *strings.NewReader(pr.DataIdx(k, idx)) + + r.Sync(SyncRelocs) + r.Relocs = make([]RelocEnt, r.Len()) + for i := range r.Relocs { + r.Sync(SyncReloc) + r.Relocs[i] = RelocEnt{RelocKind(r.Len()), Index(r.Len())} + } + + return r +} + +// A Decoder provides methods for decoding an individual element's +// bitstream data. +type Decoder struct { + common *PkgDecoder + + Relocs []RelocEnt + Data strings.Reader + + k RelocKind + Idx Index +} + +func (r *Decoder) checkErr(err error) { + if err != nil { + errorf("unexpected decoding error: %w", err) + } +} + +func (r *Decoder) rawUvarint() uint64 { + x, err := binary.ReadUvarint(&r.Data) + r.checkErr(err) + return x +} + +func (r *Decoder) rawVarint() int64 { + ux := r.rawUvarint() + + // Zig-zag decode. + x := int64(ux >> 1) + if ux&1 != 0 { + x = ^x + } + return x +} + +func (r *Decoder) rawReloc(k RelocKind, idx int) Index { + e := r.Relocs[idx] + assert(e.Kind == k) + return e.Idx +} + +// Sync decodes a sync marker from the element bitstream and asserts +// that it matches the expected marker. +// +// If r.common.sync is false, then Sync is a no-op. +func (r *Decoder) Sync(mWant SyncMarker) { + if !r.common.sync { + return + } + + pos, _ := r.Data.Seek(0, os.SEEK_CUR) // TODO(mdempsky): io.SeekCurrent after #44505 is resolved + mHave := SyncMarker(r.rawUvarint()) + writerPCs := make([]int, r.rawUvarint()) + for i := range writerPCs { + writerPCs[i] = int(r.rawUvarint()) + } + + if mHave == mWant { + return + } + + // There's some tension here between printing: + // + // (1) full file paths that tools can recognize (e.g., so emacs + // hyperlinks the "file:line" text for easy navigation), or + // + // (2) short file paths that are easier for humans to read (e.g., by + // omitting redundant or irrelevant details, so it's easier to + // focus on the useful bits that remain). + // + // The current formatting favors the former, as it seems more + // helpful in practice. But perhaps the formatting could be improved + // to better address both concerns. For example, use relative file + // paths if they would be shorter, or rewrite file paths to contain + // "$GOROOT" (like objabi.AbsFile does) if tools can be taught how + // to reliably expand that again. + + fmt.Printf("export data desync: package %q, section %v, index %v, offset %v\n", r.common.pkgPath, r.k, r.Idx, pos) + + fmt.Printf("\nfound %v, written at:\n", mHave) + if len(writerPCs) == 0 { + fmt.Printf("\t[stack trace unavailable; recompile package %q with -d=syncframes]\n", r.common.pkgPath) + } + for _, pc := range writerPCs { + fmt.Printf("\t%s\n", r.common.StringIdx(r.rawReloc(RelocString, pc))) + } + + fmt.Printf("\nexpected %v, reading at:\n", mWant) + var readerPCs [32]uintptr // TODO(mdempsky): Dynamically size? + n := runtime.Callers(2, readerPCs[:]) + for _, pc := range fmtFrames(readerPCs[:n]...) { + fmt.Printf("\t%s\n", pc) + } + + // We already printed a stack trace for the reader, so now we can + // simply exit. Printing a second one with panic or base.Fatalf + // would just be noise. + os.Exit(1) +} + +// Bool decodes and returns a bool value from the element bitstream. +func (r *Decoder) Bool() bool { + r.Sync(SyncBool) + x, err := r.Data.ReadByte() + r.checkErr(err) + assert(x < 2) + return x != 0 +} + +// Int64 decodes and returns an int64 value from the element bitstream. +func (r *Decoder) Int64() int64 { + r.Sync(SyncInt64) + return r.rawVarint() +} + +// Int64 decodes and returns a uint64 value from the element bitstream. +func (r *Decoder) Uint64() uint64 { + r.Sync(SyncUint64) + return r.rawUvarint() +} + +// Len decodes and returns a non-negative int value from the element bitstream. +func (r *Decoder) Len() int { x := r.Uint64(); v := int(x); assert(uint64(v) == x); return v } + +// Int decodes and returns an int value from the element bitstream. +func (r *Decoder) Int() int { x := r.Int64(); v := int(x); assert(int64(v) == x); return v } + +// Uint decodes and returns a uint value from the element bitstream. +func (r *Decoder) Uint() uint { x := r.Uint64(); v := uint(x); assert(uint64(v) == x); return v } + +// Code decodes a Code value from the element bitstream and returns +// its ordinal value. It's the caller's responsibility to convert the +// result to an appropriate Code type. +// +// TODO(mdempsky): Ideally this method would have signature "Code[T +// Code] T" instead, but we don't allow generic methods and the +// compiler can't depend on generics yet anyway. +func (r *Decoder) Code(mark SyncMarker) int { + r.Sync(mark) + return r.Len() +} + +// Reloc decodes a relocation of expected section k from the element +// bitstream and returns an index to the referenced element. +func (r *Decoder) Reloc(k RelocKind) Index { + r.Sync(SyncUseReloc) + return r.rawReloc(k, r.Len()) +} + +// String decodes and returns a string value from the element +// bitstream. +func (r *Decoder) String() string { + r.Sync(SyncString) + return r.common.StringIdx(r.Reloc(RelocString)) +} + +// Strings decodes and returns a variable-length slice of strings from +// the element bitstream. +func (r *Decoder) Strings() []string { + res := make([]string, r.Len()) + for i := range res { + res[i] = r.String() + } + return res +} + +// Value decodes and returns a constant.Value from the element +// bitstream. +func (r *Decoder) Value() constant.Value { + r.Sync(SyncValue) + isComplex := r.Bool() + val := r.scalar() + if isComplex { + val = constant.BinaryOp(val, token.ADD, constant.MakeImag(r.scalar())) + } + return val +} + +func (r *Decoder) scalar() constant.Value { + switch tag := CodeVal(r.Code(SyncVal)); tag { + default: + panic(fmt.Errorf("unexpected scalar tag: %v", tag)) + + case ValBool: + return constant.MakeBool(r.Bool()) + case ValString: + return constant.MakeString(r.String()) + case ValInt64: + return constant.MakeInt64(r.Int64()) + case ValBigInt: + return constant.Make(r.bigInt()) + case ValBigRat: + num := r.bigInt() + denom := r.bigInt() + return constant.Make(new(big.Rat).SetFrac(num, denom)) + case ValBigFloat: + return constant.Make(r.bigFloat()) + } +} + +func (r *Decoder) bigInt() *big.Int { + v := new(big.Int).SetBytes([]byte(r.String())) + if r.Bool() { + v.Neg(v) + } + return v +} + +func (r *Decoder) bigFloat() *big.Float { + v := new(big.Float).SetPrec(512) + assert(v.UnmarshalText([]byte(r.String())) == nil) + return v +} + +// @@@ Helpers + +// TODO(mdempsky): These should probably be removed. I think they're a +// smell that the export data format is not yet quite right. + +// PeekPkgPath returns the package path for the specified package +// index. +func (pr *PkgDecoder) PeekPkgPath(idx Index) string { + r := pr.NewDecoder(RelocPkg, idx, SyncPkgDef) + path := r.String() + if path == "" { + path = pr.pkgPath + } + return path +} + +// PeekObj returns the package path, object name, and CodeObj for the +// specified object index. +func (pr *PkgDecoder) PeekObj(idx Index) (string, string, CodeObj) { + r := pr.NewDecoder(RelocName, idx, SyncObject1) + r.Sync(SyncSym) + r.Sync(SyncPkg) + path := pr.PeekPkgPath(r.Reloc(RelocPkg)) + name := r.String() + assert(name != "") + + tag := CodeObj(r.Code(SyncCodeObj)) + + return path, name, tag +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/doc.go b/vendor/golang.org/x/tools/go/internal/pkgbits/doc.go new file mode 100644 index 0000000000..c8a2796b5e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/doc.go @@ -0,0 +1,32 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pkgbits implements low-level coding abstractions for +// Unified IR's export data format. +// +// At a low-level, a package is a collection of bitstream elements. +// Each element has a "kind" and a dense, non-negative index. +// Elements can be randomly accessed given their kind and index. +// +// Individual elements are sequences of variable-length values (e.g., +// integers, booleans, strings, go/constant values, cross-references +// to other elements). Package pkgbits provides APIs for encoding and +// decoding these low-level values, but the details of mapping +// higher-level Go constructs into elements is left to higher-level +// abstractions. +// +// Elements may cross-reference each other with "relocations." For +// example, an element representing a pointer type has a relocation +// referring to the element type. +// +// Go constructs may be composed as a constellation of multiple +// elements. For example, a declared function may have one element to +// describe the object (e.g., its name, type, position), and a +// separate element to describe its function body. This allows readers +// some flexibility in efficiently seeking or re-reading data (e.g., +// inlining requires re-reading the function body for each inlined +// call, without needing to re-read the object-level details). +// +// This is a copy of internal/pkgbits in the Go implementation. +package pkgbits diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go b/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go new file mode 100644 index 0000000000..c50c838caa --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/encoder.go @@ -0,0 +1,379 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +import ( + "bytes" + "crypto/md5" + "encoding/binary" + "go/constant" + "io" + "math/big" + "runtime" +) + +// currentVersion is the current version number. +// +// - v0: initial prototype +// +// - v1: adds the flags uint32 word +const currentVersion uint32 = 1 + +// A PkgEncoder provides methods for encoding a package's Unified IR +// export data. +type PkgEncoder struct { + // elems holds the bitstream for previously encoded elements. + elems [numRelocs][]string + + // stringsIdx maps previously encoded strings to their index within + // the RelocString section, to allow deduplication. That is, + // elems[RelocString][stringsIdx[s]] == s (if present). + stringsIdx map[string]Index + + // syncFrames is the number of frames to write at each sync + // marker. A negative value means sync markers are omitted. + syncFrames int +} + +// SyncMarkers reports whether pw uses sync markers. +func (pw *PkgEncoder) SyncMarkers() bool { return pw.syncFrames >= 0 } + +// NewPkgEncoder returns an initialized PkgEncoder. +// +// syncFrames is the number of caller frames that should be serialized +// at Sync points. Serializing additional frames results in larger +// export data files, but can help diagnosing desync errors in +// higher-level Unified IR reader/writer code. If syncFrames is +// negative, then sync markers are omitted entirely. +func NewPkgEncoder(syncFrames int) PkgEncoder { + return PkgEncoder{ + stringsIdx: make(map[string]Index), + syncFrames: syncFrames, + } +} + +// DumpTo writes the package's encoded data to out0 and returns the +// package fingerprint. +func (pw *PkgEncoder) DumpTo(out0 io.Writer) (fingerprint [8]byte) { + h := md5.New() + out := io.MultiWriter(out0, h) + + writeUint32 := func(x uint32) { + assert(binary.Write(out, binary.LittleEndian, x) == nil) + } + + writeUint32(currentVersion) + + var flags uint32 + if pw.SyncMarkers() { + flags |= flagSyncMarkers + } + writeUint32(flags) + + // Write elemEndsEnds. + var sum uint32 + for _, elems := range &pw.elems { + sum += uint32(len(elems)) + writeUint32(sum) + } + + // Write elemEnds. + sum = 0 + for _, elems := range &pw.elems { + for _, elem := range elems { + sum += uint32(len(elem)) + writeUint32(sum) + } + } + + // Write elemData. + for _, elems := range &pw.elems { + for _, elem := range elems { + _, err := io.WriteString(out, elem) + assert(err == nil) + } + } + + // Write fingerprint. + copy(fingerprint[:], h.Sum(nil)) + _, err := out0.Write(fingerprint[:]) + assert(err == nil) + + return +} + +// StringIdx adds a string value to the strings section, if not +// already present, and returns its index. +func (pw *PkgEncoder) StringIdx(s string) Index { + if idx, ok := pw.stringsIdx[s]; ok { + assert(pw.elems[RelocString][idx] == s) + return idx + } + + idx := Index(len(pw.elems[RelocString])) + pw.elems[RelocString] = append(pw.elems[RelocString], s) + pw.stringsIdx[s] = idx + return idx +} + +// NewEncoder returns an Encoder for a new element within the given +// section, and encodes the given SyncMarker as the start of the +// element bitstream. +func (pw *PkgEncoder) NewEncoder(k RelocKind, marker SyncMarker) Encoder { + e := pw.NewEncoderRaw(k) + e.Sync(marker) + return e +} + +// NewEncoderRaw returns an Encoder for a new element within the given +// section. +// +// Most callers should use NewEncoder instead. +func (pw *PkgEncoder) NewEncoderRaw(k RelocKind) Encoder { + idx := Index(len(pw.elems[k])) + pw.elems[k] = append(pw.elems[k], "") // placeholder + + return Encoder{ + p: pw, + k: k, + Idx: idx, + } +} + +// An Encoder provides methods for encoding an individual element's +// bitstream data. +type Encoder struct { + p *PkgEncoder + + Relocs []RelocEnt + Data bytes.Buffer // accumulated element bitstream data + + encodingRelocHeader bool + + k RelocKind + Idx Index // index within relocation section +} + +// Flush finalizes the element's bitstream and returns its Index. +func (w *Encoder) Flush() Index { + var sb bytes.Buffer // TODO(mdempsky): strings.Builder after #44505 is resolved + + // Backup the data so we write the relocations at the front. + var tmp bytes.Buffer + io.Copy(&tmp, &w.Data) + + // TODO(mdempsky): Consider writing these out separately so they're + // easier to strip, along with function bodies, so that we can prune + // down to just the data that's relevant to go/types. + if w.encodingRelocHeader { + panic("encodingRelocHeader already true; recursive flush?") + } + w.encodingRelocHeader = true + w.Sync(SyncRelocs) + w.Len(len(w.Relocs)) + for _, rEnt := range w.Relocs { + w.Sync(SyncReloc) + w.Len(int(rEnt.Kind)) + w.Len(int(rEnt.Idx)) + } + + io.Copy(&sb, &w.Data) + io.Copy(&sb, &tmp) + w.p.elems[w.k][w.Idx] = sb.String() + + return w.Idx +} + +func (w *Encoder) checkErr(err error) { + if err != nil { + errorf("unexpected encoding error: %v", err) + } +} + +func (w *Encoder) rawUvarint(x uint64) { + var buf [binary.MaxVarintLen64]byte + n := binary.PutUvarint(buf[:], x) + _, err := w.Data.Write(buf[:n]) + w.checkErr(err) +} + +func (w *Encoder) rawVarint(x int64) { + // Zig-zag encode. + ux := uint64(x) << 1 + if x < 0 { + ux = ^ux + } + + w.rawUvarint(ux) +} + +func (w *Encoder) rawReloc(r RelocKind, idx Index) int { + // TODO(mdempsky): Use map for lookup; this takes quadratic time. + for i, rEnt := range w.Relocs { + if rEnt.Kind == r && rEnt.Idx == idx { + return i + } + } + + i := len(w.Relocs) + w.Relocs = append(w.Relocs, RelocEnt{r, idx}) + return i +} + +func (w *Encoder) Sync(m SyncMarker) { + if !w.p.SyncMarkers() { + return + } + + // Writing out stack frame string references requires working + // relocations, but writing out the relocations themselves involves + // sync markers. To prevent infinite recursion, we simply trim the + // stack frame for sync markers within the relocation header. + var frames []string + if !w.encodingRelocHeader && w.p.syncFrames > 0 { + pcs := make([]uintptr, w.p.syncFrames) + n := runtime.Callers(2, pcs) + frames = fmtFrames(pcs[:n]...) + } + + // TODO(mdempsky): Save space by writing out stack frames as a + // linked list so we can share common stack frames. + w.rawUvarint(uint64(m)) + w.rawUvarint(uint64(len(frames))) + for _, frame := range frames { + w.rawUvarint(uint64(w.rawReloc(RelocString, w.p.StringIdx(frame)))) + } +} + +// Bool encodes and writes a bool value into the element bitstream, +// and then returns the bool value. +// +// For simple, 2-alternative encodings, the idiomatic way to call Bool +// is something like: +// +// if w.Bool(x != 0) { +// // alternative #1 +// } else { +// // alternative #2 +// } +// +// For multi-alternative encodings, use Code instead. +func (w *Encoder) Bool(b bool) bool { + w.Sync(SyncBool) + var x byte + if b { + x = 1 + } + err := w.Data.WriteByte(x) + w.checkErr(err) + return b +} + +// Int64 encodes and writes an int64 value into the element bitstream. +func (w *Encoder) Int64(x int64) { + w.Sync(SyncInt64) + w.rawVarint(x) +} + +// Uint64 encodes and writes a uint64 value into the element bitstream. +func (w *Encoder) Uint64(x uint64) { + w.Sync(SyncUint64) + w.rawUvarint(x) +} + +// Len encodes and writes a non-negative int value into the element bitstream. +func (w *Encoder) Len(x int) { assert(x >= 0); w.Uint64(uint64(x)) } + +// Int encodes and writes an int value into the element bitstream. +func (w *Encoder) Int(x int) { w.Int64(int64(x)) } + +// Len encodes and writes a uint value into the element bitstream. +func (w *Encoder) Uint(x uint) { w.Uint64(uint64(x)) } + +// Reloc encodes and writes a relocation for the given (section, +// index) pair into the element bitstream. +// +// Note: Only the index is formally written into the element +// bitstream, so bitstream decoders must know from context which +// section an encoded relocation refers to. +func (w *Encoder) Reloc(r RelocKind, idx Index) { + w.Sync(SyncUseReloc) + w.Len(w.rawReloc(r, idx)) +} + +// Code encodes and writes a Code value into the element bitstream. +func (w *Encoder) Code(c Code) { + w.Sync(c.Marker()) + w.Len(c.Value()) +} + +// String encodes and writes a string value into the element +// bitstream. +// +// Internally, strings are deduplicated by adding them to the strings +// section (if not already present), and then writing a relocation +// into the element bitstream. +func (w *Encoder) String(s string) { + w.Sync(SyncString) + w.Reloc(RelocString, w.p.StringIdx(s)) +} + +// Strings encodes and writes a variable-length slice of strings into +// the element bitstream. +func (w *Encoder) Strings(ss []string) { + w.Len(len(ss)) + for _, s := range ss { + w.String(s) + } +} + +// Value encodes and writes a constant.Value into the element +// bitstream. +func (w *Encoder) Value(val constant.Value) { + w.Sync(SyncValue) + if w.Bool(val.Kind() == constant.Complex) { + w.scalar(constant.Real(val)) + w.scalar(constant.Imag(val)) + } else { + w.scalar(val) + } +} + +func (w *Encoder) scalar(val constant.Value) { + switch v := constant.Val(val).(type) { + default: + errorf("unhandled %v (%v)", val, val.Kind()) + case bool: + w.Code(ValBool) + w.Bool(v) + case string: + w.Code(ValString) + w.String(v) + case int64: + w.Code(ValInt64) + w.Int64(v) + case *big.Int: + w.Code(ValBigInt) + w.bigInt(v) + case *big.Rat: + w.Code(ValBigRat) + w.bigInt(v.Num()) + w.bigInt(v.Denom()) + case *big.Float: + w.Code(ValBigFloat) + w.bigFloat(v) + } +} + +func (w *Encoder) bigInt(v *big.Int) { + b := v.Bytes() + w.String(string(b)) // TODO: More efficient encoding. + w.Bool(v.Sign() < 0) +} + +func (w *Encoder) bigFloat(v *big.Float) { + b := v.Append(nil, 'p', -1) + w.String(string(b)) // TODO: More efficient encoding. +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/flags.go b/vendor/golang.org/x/tools/go/internal/pkgbits/flags.go new file mode 100644 index 0000000000..654222745f --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/flags.go @@ -0,0 +1,9 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +const ( + flagSyncMarkers = 1 << iota // file format contains sync markers +) diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go1.go b/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go1.go new file mode 100644 index 0000000000..5294f6a63e --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go1.go @@ -0,0 +1,21 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.7 +// +build !go1.7 + +// TODO(mdempsky): Remove after #44505 is resolved + +package pkgbits + +import "runtime" + +func walkFrames(pcs []uintptr, visit frameVisitor) { + for _, pc := range pcs { + fn := runtime.FuncForPC(pc) + file, line := fn.FileLine(pc) + + visit(file, line, fn.Name(), pc-fn.Entry()) + } +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go17.go b/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go17.go new file mode 100644 index 0000000000..2324ae7adf --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/frames_go17.go @@ -0,0 +1,28 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.7 +// +build go1.7 + +package pkgbits + +import "runtime" + +// walkFrames calls visit for each call frame represented by pcs. +// +// pcs should be a slice of PCs, as returned by runtime.Callers. +func walkFrames(pcs []uintptr, visit frameVisitor) { + if len(pcs) == 0 { + return + } + + frames := runtime.CallersFrames(pcs) + for { + frame, more := frames.Next() + visit(frame.File, frame.Line, frame.Function, frame.PC-frame.Entry) + if !more { + return + } + } +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go b/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go new file mode 100644 index 0000000000..7a8f04ab3f --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/reloc.go @@ -0,0 +1,42 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +// A RelocKind indicates a particular section within a unified IR export. +type RelocKind int + +// An Index represents a bitstream element index within a particular +// section. +type Index int + +// A relocEnt (relocation entry) is an entry in an element's local +// reference table. +// +// TODO(mdempsky): Rename this too. +type RelocEnt struct { + Kind RelocKind + Idx Index +} + +// Reserved indices within the meta relocation section. +const ( + PublicRootIdx Index = 0 + PrivateRootIdx Index = 1 +) + +const ( + RelocString RelocKind = iota + RelocMeta + RelocPosBase + RelocPkg + RelocName + RelocType + RelocObj + RelocObjExt + RelocObjDict + RelocBody + + numRelocs = iota +) diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/support.go b/vendor/golang.org/x/tools/go/internal/pkgbits/support.go new file mode 100644 index 0000000000..ad26d3b28c --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/support.go @@ -0,0 +1,17 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +import "fmt" + +func assert(b bool) { + if !b { + panic("assertion failed") + } +} + +func errorf(format string, args ...interface{}) { + panic(fmt.Errorf(format, args...)) +} diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/sync.go b/vendor/golang.org/x/tools/go/internal/pkgbits/sync.go new file mode 100644 index 0000000000..5bd51ef717 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/sync.go @@ -0,0 +1,113 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkgbits + +import ( + "fmt" + "strings" +) + +// fmtFrames formats a backtrace for reporting reader/writer desyncs. +func fmtFrames(pcs ...uintptr) []string { + res := make([]string, 0, len(pcs)) + walkFrames(pcs, func(file string, line int, name string, offset uintptr) { + // Trim package from function name. It's just redundant noise. + name = strings.TrimPrefix(name, "cmd/compile/internal/noder.") + + res = append(res, fmt.Sprintf("%s:%v: %s +0x%v", file, line, name, offset)) + }) + return res +} + +type frameVisitor func(file string, line int, name string, offset uintptr) + +// SyncMarker is an enum type that represents markers that may be +// written to export data to ensure the reader and writer stay +// synchronized. +type SyncMarker int + +//go:generate stringer -type=SyncMarker -trimprefix=Sync + +const ( + _ SyncMarker = iota + + // Public markers (known to go/types importers). + + // Low-level coding markers. + SyncEOF + SyncBool + SyncInt64 + SyncUint64 + SyncString + SyncValue + SyncVal + SyncRelocs + SyncReloc + SyncUseReloc + + // Higher-level object and type markers. + SyncPublic + SyncPos + SyncPosBase + SyncObject + SyncObject1 + SyncPkg + SyncPkgDef + SyncMethod + SyncType + SyncTypeIdx + SyncTypeParamNames + SyncSignature + SyncParams + SyncParam + SyncCodeObj + SyncSym + SyncLocalIdent + SyncSelector + + // Private markers (only known to cmd/compile). + SyncPrivate + + SyncFuncExt + SyncVarExt + SyncTypeExt + SyncPragma + + SyncExprList + SyncExprs + SyncExpr + SyncExprType + SyncAssign + SyncOp + SyncFuncLit + SyncCompLit + + SyncDecl + SyncFuncBody + SyncOpenScope + SyncCloseScope + SyncCloseAnotherScope + SyncDeclNames + SyncDeclName + + SyncStmts + SyncBlockStmt + SyncIfStmt + SyncForStmt + SyncSwitchStmt + SyncRangeStmt + SyncCaseClause + SyncCommClause + SyncSelectStmt + SyncDecls + SyncLabeledStmt + SyncUseObjLocal + SyncAddLocal + SyncLinkname + SyncStmt1 + SyncStmtsEnd + SyncLabel + SyncOptLabel +) diff --git a/vendor/golang.org/x/tools/go/internal/pkgbits/syncmarker_string.go b/vendor/golang.org/x/tools/go/internal/pkgbits/syncmarker_string.go new file mode 100644 index 0000000000..4a5b0ca5f2 --- /dev/null +++ b/vendor/golang.org/x/tools/go/internal/pkgbits/syncmarker_string.go @@ -0,0 +1,89 @@ +// Code generated by "stringer -type=SyncMarker -trimprefix=Sync"; DO NOT EDIT. + +package pkgbits + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[SyncEOF-1] + _ = x[SyncBool-2] + _ = x[SyncInt64-3] + _ = x[SyncUint64-4] + _ = x[SyncString-5] + _ = x[SyncValue-6] + _ = x[SyncVal-7] + _ = x[SyncRelocs-8] + _ = x[SyncReloc-9] + _ = x[SyncUseReloc-10] + _ = x[SyncPublic-11] + _ = x[SyncPos-12] + _ = x[SyncPosBase-13] + _ = x[SyncObject-14] + _ = x[SyncObject1-15] + _ = x[SyncPkg-16] + _ = x[SyncPkgDef-17] + _ = x[SyncMethod-18] + _ = x[SyncType-19] + _ = x[SyncTypeIdx-20] + _ = x[SyncTypeParamNames-21] + _ = x[SyncSignature-22] + _ = x[SyncParams-23] + _ = x[SyncParam-24] + _ = x[SyncCodeObj-25] + _ = x[SyncSym-26] + _ = x[SyncLocalIdent-27] + _ = x[SyncSelector-28] + _ = x[SyncPrivate-29] + _ = x[SyncFuncExt-30] + _ = x[SyncVarExt-31] + _ = x[SyncTypeExt-32] + _ = x[SyncPragma-33] + _ = x[SyncExprList-34] + _ = x[SyncExprs-35] + _ = x[SyncExpr-36] + _ = x[SyncExprType-37] + _ = x[SyncAssign-38] + _ = x[SyncOp-39] + _ = x[SyncFuncLit-40] + _ = x[SyncCompLit-41] + _ = x[SyncDecl-42] + _ = x[SyncFuncBody-43] + _ = x[SyncOpenScope-44] + _ = x[SyncCloseScope-45] + _ = x[SyncCloseAnotherScope-46] + _ = x[SyncDeclNames-47] + _ = x[SyncDeclName-48] + _ = x[SyncStmts-49] + _ = x[SyncBlockStmt-50] + _ = x[SyncIfStmt-51] + _ = x[SyncForStmt-52] + _ = x[SyncSwitchStmt-53] + _ = x[SyncRangeStmt-54] + _ = x[SyncCaseClause-55] + _ = x[SyncCommClause-56] + _ = x[SyncSelectStmt-57] + _ = x[SyncDecls-58] + _ = x[SyncLabeledStmt-59] + _ = x[SyncUseObjLocal-60] + _ = x[SyncAddLocal-61] + _ = x[SyncLinkname-62] + _ = x[SyncStmt1-63] + _ = x[SyncStmtsEnd-64] + _ = x[SyncLabel-65] + _ = x[SyncOptLabel-66] +} + +const _SyncMarker_name = "EOFBoolInt64Uint64StringValueValRelocsRelocUseRelocPublicPosPosBaseObjectObject1PkgPkgDefMethodTypeTypeIdxTypeParamNamesSignatureParamsParamCodeObjSymLocalIdentSelectorPrivateFuncExtVarExtTypeExtPragmaExprListExprsExprExprTypeAssignOpFuncLitCompLitDeclFuncBodyOpenScopeCloseScopeCloseAnotherScopeDeclNamesDeclNameStmtsBlockStmtIfStmtForStmtSwitchStmtRangeStmtCaseClauseCommClauseSelectStmtDeclsLabeledStmtUseObjLocalAddLocalLinknameStmt1StmtsEndLabelOptLabel" + +var _SyncMarker_index = [...]uint16{0, 3, 7, 12, 18, 24, 29, 32, 38, 43, 51, 57, 60, 67, 73, 80, 83, 89, 95, 99, 106, 120, 129, 135, 140, 147, 150, 160, 168, 175, 182, 188, 195, 201, 209, 214, 218, 226, 232, 234, 241, 248, 252, 260, 269, 279, 296, 305, 313, 318, 327, 333, 340, 350, 359, 369, 379, 389, 394, 405, 416, 424, 432, 437, 445, 450, 458} + +func (i SyncMarker) String() string { + i -= 1 + if i < 0 || i >= SyncMarker(len(_SyncMarker_index)-1) { + return "SyncMarker(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _SyncMarker_name[_SyncMarker_index[i]:_SyncMarker_index[i+1]] +} diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 50533995a6..de881562de 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -302,11 +302,12 @@ func (state *golistState) runContainsQueries(response *responseDeduper, queries } dirResponse, err := state.createDriverResponse(pattern) - // If there was an error loading the package, or the package is returned - // with errors, try to load the file as an ad-hoc package. + // If there was an error loading the package, or no packages are returned, + // or the package is returned with errors, try to load the file as an + // ad-hoc package. // Usually the error will appear in a returned package, but may not if we're // in module mode and the ad-hoc is located outside a module. - if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && + if err != nil || len(dirResponse.Packages) == 0 || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && len(dirResponse.Packages[0].Errors) == 1 { var queryErr error if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index cae4e9e38a..80cb8d5244 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -76,11 +76,12 @@ golang.org/x/sys/execabs golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/tools v0.1.11 +# golang.org/x/tools v0.1.12 ## explicit golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/packagesdriver +golang.org/x/tools/go/internal/pkgbits golang.org/x/tools/go/packages golang.org/x/tools/internal/event golang.org/x/tools/internal/event/core From 56eed898ec7fc3a068d15bcb5cb66e454bcef825 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 5 Dec 2022 18:38:00 +0100 Subject: [PATCH 15/72] proc/gdbserver: set child process pgrp as foreground group (#3205) Newer versions of debugserver (which contain [1]) will spawn the target process on a new process group, when we detect that this happen, and we are a headless instance and stdin is a tty, make the child process' process group the controlling group for the terminal. [1] https://reviews.llvm.org/rG33ac4fddc7906ba712c50cd3a9b02ae041d751ab --- pkg/proc/gdbserial/gdbserver.go | 7 +++++++ pkg/proc/gdbserial/gdbserver_unix.go | 6 ++++++ pkg/proc/gdbserial/gdbserver_windows.go | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 0151a631c3..8cea453cc9 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -573,6 +573,13 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ } else { tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched) } + if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) { + // Make the target process the controlling process of the tty if it is a foreground process. + err = tcsetpgrp(os.Stdin.Fd(), p.conn.pid) + if err != nil { + logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err) + } + } return tgt, err } diff --git a/pkg/proc/gdbserial/gdbserver_unix.go b/pkg/proc/gdbserial/gdbserver_unix.go index c5151bf442..d69bb38ddc 100644 --- a/pkg/proc/gdbserial/gdbserver_unix.go +++ b/pkg/proc/gdbserial/gdbserver_unix.go @@ -6,6 +6,8 @@ package gdbserial import ( "os/signal" "syscall" + + "golang.org/x/sys/unix" ) func sysProcAttr(foreground bool) *syscall.SysProcAttr { @@ -15,3 +17,7 @@ func sysProcAttr(foreground bool) *syscall.SysProcAttr { func foregroundSignalsIgnore() { signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) } + +func tcsetpgrp(fd uintptr, pid int) error { + return unix.IoctlSetPointerInt(int(fd), unix.TIOCSPGRP, pid) +} diff --git a/pkg/proc/gdbserial/gdbserver_windows.go b/pkg/proc/gdbserial/gdbserver_windows.go index c23d35e145..90d82517e3 100644 --- a/pkg/proc/gdbserial/gdbserver_windows.go +++ b/pkg/proc/gdbserial/gdbserver_windows.go @@ -8,3 +8,7 @@ func sysProcAttr(foreground bool) *syscall.SysProcAttr { func foregroundSignalsIgnore() { } + +func tcsetpgrp(fd uintptr, pid int) error { + return nil +} From 5ca60a81e868b2f0badcf265baa031417df3267a Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 5 Dec 2022 18:46:24 +0100 Subject: [PATCH 16/72] proc: extend macOS workaround to amd64 (#3204) Go change 064f34f (which exists in Go 1.19.2 and following) removed the pagezero_size option from linker calls (because it is deprecated). This expanded the problem that exists on darwin/arm64 as well as PIE builds on darwin/amd64 to all darwin/amd64 builds. This problem is described on: https://github.com/golang/go/issues/25841. This commit extends the darwin/arm64 workaround to darwin/amd64. Fixes #3194 --- _fixtures/issue3194.go | 17 +++++++++++++++++ pkg/proc/bininfo.go | 28 +++++++++++++++++++++++----- pkg/proc/proc_test.go | 16 ++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 _fixtures/issue3194.go diff --git a/_fixtures/issue3194.go b/_fixtures/issue3194.go new file mode 100644 index 0000000000..e1c6524881 --- /dev/null +++ b/_fixtures/issue3194.go @@ -0,0 +1,17 @@ +package main + +/* +#cgo LDFLAGS: -framework CoreFoundation +#cgo LDFLAGS: -framework CFNetwork +#include +*/ +import "C" +import "fmt" + +func main() { + f() // break here +} + +func f() { + fmt.Println("ok") +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index b40ad65690..e80221c947 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1809,8 +1809,7 @@ func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, debugI // // [golang/go#25841]: https://github.com/golang/go/issues/25841 func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() { - //TODO: log extensively because of bugs in the field - if bi.GOOS != "darwin" || bi.Arch.Name != "arm64" { + if bi.GOOS != "darwin" { return } if len(bi.Images) > 1 { @@ -1823,9 +1822,28 @@ func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() { if !ok { return } - if exe.Flags&macho.FlagPIE == 0 { - bi.logger.Infof("debug_frame workaround not needed: not a PIE (%#x)", exe.Flags) - return + if bi.Arch.Name == "arm64" { + if exe.Flags&macho.FlagPIE == 0 { + bi.logger.Infof("debug_frame workaround not needed: not a PIE (%#x)", exe.Flags) + return + } + } else { + prod := goversion.ParseProducer(bi.Producer()) + if !prod.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 19, Rev: 3}) && !prod.IsDevel() { + bi.logger.Infof("debug_frame workaround not needed (version %q on %s)", bi.Producer(), bi.Arch.Name) + return + } + found := false + for i := range bi.frameEntries { + if bi.frameEntries[i].CIE.CIE_id == ^uint32(0) && bi.frameEntries[i].Begin() < 0x4000000 { + found = true + break + } + } + if !found { + bi.logger.Infof("debug_frame workaround not needed (all FDEs above 0x4000000)") + return + } } // Find first Go function (first = lowest entry point) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 0e30923f50..e77c54dbbc 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -6012,3 +6012,19 @@ func TestGnuDebuglink(t *testing.T) { } } } + +func TestStacktraceExtlinkMac(t *testing.T) { + // Tests stacktrace for programs built using external linker. + // See issue #3194 + skipUnlessOn(t, "darwin only", "darwin") + withTestProcess("issue3194", t, func(p *proc.Target, fixture protest.Fixture) { + setFunctionBreakpoint(p, t, "main.main") + assertNoError(p.Continue(), t, "First Continue()") + frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10) + assertNoError(err, t, "ThreadStacktrace") + logStacktrace(t, p, frames) + if len(frames) < 2 || frames[0].Call.Fn.Name != "main.main" || frames[1].Call.Fn.Name != "runtime.main" { + t.Fatalf("bad stacktrace") + } + }) +} From e5006c105c44a863f4bbb4bf9a8ca8a530d996b9 Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Tue, 6 Dec 2022 00:21:52 +0100 Subject: [PATCH 17/72] Test windows/arm64 pipeline (#3200) * Test windows/arm64 pipeline * update build script to support windows/arm64 * skip TestLaunchRequestWithRelativeExecPath is symblink can't be created * partially fix and skip TestCgoStacktrace * update backend health docs * update * log test output * skip starbind test on windows arm64 * skip starbind test on windows arm64 * skip rtype test on windows arm64 * skip pie backend tests on windows/arm64 * fix tests * skip function calls test on windows/arm64 * fix tests * revert hardware breakpoint test relax * add pie test clarification * skip symlink test only on windows * skip TestStepConcurrentDirect * readd exp.winarm64 * fix param * add exp.winarm64 tags * skip TestGeneratedDoc on winarm64 --- .teamcity/settings.kts | 2 +- Documentation/backend_test_health.md | 3 + _fixtures/cgostacktest/hello.c | 4 ++ _scripts/make.go | 46 ++++++++------- _scripts/test_windows.ps1 | 84 ++++++++++++++++------------ cmd/dlv/dlv_test.go | 12 +++- pkg/proc/proc_test.go | 13 ++++- pkg/proc/target.go | 2 +- pkg/proc/test/support.go | 2 +- service/dap/server_test.go | 8 ++- 10 files changed, 110 insertions(+), 66 deletions(-) diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index d0f060dc4d..b8c62b5ab0 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -204,7 +204,7 @@ class TestBuild(val os: String, val arch: String, version: String, buildId: Abso scriptMode = file { path = "_scripts/test_windows.ps1" } - param("jetbrains_powershell_scriptArguments", "-version ${"go$version"} -arch $arch") + param("jetbrains_powershell_scriptArguments", "-version ${"go$version"} -arch $arch -binDir %teamcity.build.systemDir%") } } "mac" -> { diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md index eff970fe29..6ee41512ea 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -22,3 +22,6 @@ Tests skipped by each supported backend: * 1 broken * 3 see https://github.com/go-delve/delve/issues/2768 * 1 upstream issue +* windows/arm64 skipped = 2 + * 1 broken - cgo stacktraces + * 1 broken - step concurrent diff --git a/_fixtures/cgostacktest/hello.c b/_fixtures/cgostacktest/hello.c index 7226ffed8c..edcef6b7e4 100644 --- a/_fixtures/cgostacktest/hello.c +++ b/_fixtures/cgostacktest/hello.c @@ -7,8 +7,12 @@ #elif __i386__ #define BREAKPOINT asm("int3;") #elif __aarch64__ +#ifdef WIN32 +#define BREAKPOINT asm("brk 0xF000;") +#else #define BREAKPOINT asm("brk 0;") #endif +#endif void helloworld_pt2(int x) { BREAKPOINT; diff --git a/_scripts/make.go b/_scripts/make.go index 0f4a96981a..dd0e7a4295 100644 --- a/_scripts/make.go +++ b/_scripts/make.go @@ -42,15 +42,6 @@ func NewMakeCommands() *cobra.Command { Use: "build", Short: "Build delve", Run: func(cmd *cobra.Command, args []string) { - tagFlag := prepareMacnative() - if len(*Tags) > 0 { - if len(tagFlag) == 0 { - tagFlag = "-tags=" - } else { - tagFlag += "," - } - tagFlag += strings.Join(*Tags, ",") - } envflags := []string{} if len(Architecture) > 0 { envflags = append(envflags, "GOARCH="+Architecture) @@ -59,9 +50,9 @@ func NewMakeCommands() *cobra.Command { envflags = append(envflags, "GOOS="+OS) } if len(envflags) > 0 { - executeEnv(envflags, "go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath) + executeEnv(envflags, "go", "build", "-ldflags", "-extldflags -static", tagFlags(), buildFlags(), DelveMainPackagePath) } else { - execute("go", "build", "-ldflags", "-extldflags -static", tagFlag, buildFlags(), DelveMainPackagePath) + execute("go", "build", "-ldflags", "-extldflags -static", tagFlags(), buildFlags(), DelveMainPackagePath) } if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" && canMacnative() { codesign("./dlv") @@ -78,8 +69,7 @@ func NewMakeCommands() *cobra.Command { Use: "install", Short: "Installs delve", Run: func(cmd *cobra.Command, args []string) { - tagFlag := prepareMacnative() - execute("go", "install", tagFlag, buildFlags(), DelveMainPackagePath) + execute("go", "install", tagFlags(), buildFlags(), DelveMainPackagePath) if runtime.GOOS == "darwin" && os.Getenv("CERT") != "" && canMacnative() { codesign(installedExecutablePath()) } @@ -290,7 +280,24 @@ func prepareMacnative() string { if !checkCert() { return "" } - return "-tags=macnative" + return "macnative" +} + +func tagFlags() string { + var tags []string + if mactags := prepareMacnative(); mactags != "" { + tags = append(tags, mactags) + } + if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { + tags = append(tags, "exp.winarm64") + } + if Tags != nil && len(*Tags) > 0 { + tags = append(tags, *Tags...) + } + if len(tags) == 0 { + return "" + } + return "-tags=" + strings.Join(tags, ",") } func buildFlags() []string { @@ -390,8 +397,9 @@ func testStandard() { case "linux": dopie = true case "windows": + // windows/arm64 always uses pie buildmode, no need to test everything again. // only on Go 1.15 or later, with CGO_ENABLED and gcc found in path - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { + if runtime.GOARCH != "arm64" && goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { out, err := exec.Command("go", "env", "CGO_ENABLED").CombinedOutput() if err != nil { panic(err) @@ -451,11 +459,11 @@ func testCmdIntl(testSet, testRegex, testBackend, testBuildMode string) { } if len(testPackages) > 3 { - executeq(env, "go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag) + executeq(env, "go", "test", testFlags(), buildFlags(), tagFlags(), testPackages, backendFlag, buildModeFlag) } else if testRegex != "" { - executeq(env, "go", "test", testFlags(), buildFlags(), testPackages, "-run="+testRegex, backendFlag, buildModeFlag) + executeq(env, "go", "test", testFlags(), buildFlags(), tagFlags(), testPackages, "-run="+testRegex, backendFlag, buildModeFlag) } else { - executeq(env, "go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag) + executeq(env, "go", "test", testFlags(), buildFlags(), tagFlags(), testPackages, backendFlag, buildModeFlag) } } @@ -494,7 +502,7 @@ func inpath(exe string) bool { func allPackages() []string { r := []string{} - for _, dir := range strings.Split(getoutput("go", "list", "-mod=vendor", "./..."), "\n") { + for _, dir := range strings.Split(getoutput("go", "list", "-mod=vendor", tagFlags(), "./..."), "\n") { dir = strings.TrimSpace(dir) if dir == "" || strings.Contains(dir, "/vendor/") || strings.Contains(dir, "/_scripts") { continue diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index e5c5db09bc..ff5422daeb 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -1,55 +1,73 @@ param ( [Parameter(Mandatory = $true)][string]$version, - [Parameter(Mandatory = $true)][string]$arch + [Parameter(Mandatory = $true)][string]$arch, + [Parameter(Mandatory = $false)][string]$binDir ) -Set-MpPreference -DisableRealtimeMonitoring $true - -if ($arch -eq "arm64") { - # TODO: Remove when TeamCity subproject for windows/arm64 is set up. - Exit 0 +if ($binDir -eq "") { + # TODO: remove once the current version of settings.kts gets to master. + $binDir = Resolve-Path "../../system" # working directory } -# Install Chocolatey -#Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + +Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue # Install MinGW. -choco install -y mingw +if ($arch -eq "amd64") +{ + # Install Chocolatey + #Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1')) + choco install -y mingw +} elseif ($arch -eq "arm64") { + $llvmVersion = "20220906" + $name = "llvm-mingw-$llvmVersion-ucrt-aarch64" + if (-Not(Test-Path "$binDir\llvm-mingw\$name")) + { + New-Item "$binDir\llvm-mingw" -ItemType Directory -ErrorAction SilentlyContinue + $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$llvmVersion/$name.zip" + Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$name.zip" + Expand-Archive -Force -LiteralPath "$env:TEMP\$name.zip" -DestinationPath "$binDir\llvm-mingw\" + } + $env:PATH = "$binDir\llvm-mingw\$name\bin;$env:PATH" +} else { + Write-Error "Unsupported architecture: $arch" -ErrorAction Stop +} # Install Procdump -if (-Not(Test-Path "C:\procdump")) +if (-Not(Test-Path "$binDir\procdump")) { - mkdir C:\procdump - Invoke-WebRequest -UserAgent wget -Uri https://download.sysinternals.com/files/Procdump.zip -OutFile C:\procdump\procdump.zip - &7z x -oC:\procdump\ C:\procdump\procdump.zip > $null + New-Item "$binDir\procdump" -ItemType Directory + Invoke-WebRequest -UserAgent wget -Uri "https://download.sysinternals.com/files/Procdump.zip" -OutFile "$env:TEMP\procdump.zip" + Expand-Archive -Force -LiteralPath "$env:TEMP\procdump.zip" -DestinationPath "$binDir\procdump" } -$env:PATH += ";C:\procdump;C:\mingw64\bin" +$env:PATH = "$binDir\procdump;$env:PATH" function GetGo($version) { - $env:GOROOT = "C:\go\$version" + $env:GOROOT = "$binDir\go\$version" if (-Not(Test-Path $env:GOROOT)) { $file = "$version.windows-$arch.zip" $url = "https://dl.google.com/go/$file" - Invoke-WebRequest -UserAgent wget -Uri $url -OutFile $file - &7z x -oC:\go $file > $null - Move-Item -Path C:\go\go -Destination $env:GOROOT -force + Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$file" + Expand-Archive -Force -LiteralPath "$env:TEMP\$file" -DestinationPath "$env:TEMP\$version" + New-Item $env:GOROOT -ItemType Directory + Move-Item -Path "$env:TEMP\$version\go\*" -Destination $env:GOROOT -Force } } if ($version -eq "gotip") { #Exit 0 - $latest = Invoke-WebRequest -Uri https://golang.org/VERSION?m=text -UseBasicParsing | Select-Object -ExpandProperty Content + $latest = Invoke-WebRequest -Uri "https://golang.org/VERSION?m=text" -UseBasicParsing | Select-Object -ExpandProperty Content GetGo $latest $env:GOROOT_BOOTSTRAP = $env:GOROOT - $env:GOROOT = "C:\go\go-tip" + $env:GOROOT = "$binDir\go\go-tip" Write-Host "Building Go with GOROOT_BOOTSTRAP $env:GOROOT_BOOTSTRAP" if (-Not(Test-Path $env:GOROOT)) { - git clone https://go.googlesource.com/go C:\go\go-tip - Push-Location -Path C:\go\go-tip\src + git clone "https://go.googlesource.com/go" "$binDir\go\go-tip" + Push-Location -Path "$binDir\go\go-tip\src" } else { - Push-Location -Path C:\go\go-tip\src + Push-Location -Path "$binDir\go\go-tip\src" git pull } .\make.bat @@ -57,7 +75,7 @@ if ($version -eq "gotip") { } else { # Install Go Write-Host "Finding latest patch version for $version" - $versions = Invoke-WebRequest -Uri 'https://golang.org/dl/?mode=json&include=all' -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json + $versions = Invoke-WebRequest -Uri "https://golang.org/dl/?mode=json&include=all" -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version($|\.)" | Sort-Object -Descending | Select-Object -First 1 if ($v -eq $null) { $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version(rc)" | Sort-Object -Descending | Select-Object -First 1 @@ -69,25 +87,17 @@ if ($version -eq "gotip") { GetGo $v } -$env:GOPATH = "C:\gopath" -$env:PATH += ";$env:GOROOT\bin;$env:GOPATH\bin" +$env:GOPATH = "$binDir\gopath" +$env:PATH = "$env:GOROOT\bin;$env:GOPATH\bin;$env:PATH" Write-Host $env:PATH Write-Host $env:GOROOT Write-Host $env:GOPATH +Get-Command go go version go env -go run _scripts/make.go test -$x = $LastExitCode -if ($version -ne "gotip") { - Exit $x -} - -# TODO: Remove once we have a windows/arm64 builder. -# Test windows/arm64 compiles. -$env:GOARCH = "arm64" -go run _scripts/make.go build --tags exp.winarm64 +go run _scripts/make.go test -v $x = $LastExitCode if ($version -ne "gotip") { - Exit $x + Exit $x } diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index a7ed67dd14..cfec1439fd 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -210,7 +210,11 @@ func getDlvBin(t *testing.T) (string, string) { // we can ensure we don't get build errors // depending on the test ordering. os.Setenv("CGO_LDFLAGS", ldFlags) - return getDlvBinInternal(t) + var tags string + if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { + tags = "-tags=exp.winarm64" + } + return getDlvBinInternal(t, tags) } func getDlvBinEBPF(t *testing.T) (string, string) { @@ -424,6 +428,10 @@ func TestGeneratedDoc(t *testing.T) { if strings.ToLower(os.Getenv("TRAVIS")) == "true" && runtime.GOOS == "windows" { t.Skip("skipping test on Windows in CI") } + if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { + //TODO(qmuntal): investigate further when the Windows ARM64 backend is more stable. + t.Skip("skipping test on Windows in CI") + } // Checks gen-cli-docs.go var generatedBuf bytes.Buffer commands := terminal.DebugCommands(nil) @@ -457,9 +465,9 @@ func TestGeneratedDoc(t *testing.T) { return out } + checkAutogenDoc(t, "Documentation/backend_test_health.md", "go run _scripts/gen-backend_test_health.go", runScript("_scripts/gen-backend_test_health.go", "-")) checkAutogenDoc(t, "pkg/terminal/starbind/starlark_mapping.go", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "go", "-")) checkAutogenDoc(t, "Documentation/cli/starlark.md", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "doc/dummy", "Documentation/cli/starlark.md")) - checkAutogenDoc(t, "Documentation/backend_test_health.md", "go run _scripts/gen-backend_test_health.go", runScript("_scripts/gen-backend_test_health.go", "-")) if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) { checkAutogenDoc(t, "_scripts/rtype-out.txt", "go run _scripts/rtype.go report _scripts/rtype-out.txt", runScript("_scripts/rtype.go", "report")) runScript("_scripts/rtype.go", "check") diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index e77c54dbbc..02d8d83328 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -2482,6 +2482,7 @@ func TestStepOut(t *testing.T) { func TestStepConcurrentDirect(t *testing.T) { skipOn(t, "broken", "freebsd") + skipOn(t, "broken - step concurrent", "windows", "arm64") protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 37) @@ -3424,6 +3425,7 @@ func TestCgoStacktrace(t *testing.T) { } skipOn(t, "broken - cgo stacktraces", "386") + skipOn(t, "broken - cgo stacktraces", "windows", "arm64") protest.MustHaveCgo(t) // Tests that: @@ -3635,8 +3637,8 @@ func TestIssue1008(t *testing.T) { if !strings.HasSuffix(loc.File, "/main.go") { t.Errorf("unexpected location %s:%d\n", loc.File, loc.Line) } - if loc.Line > 31 { - t.Errorf("unexpected location %s:%d (file only has 30 lines)\n", loc.File, loc.Line) + if loc.Line > 35 { + t.Errorf("unexpected location %s:%d (file only has 34 lines)\n", loc.File, loc.Line) } }) } @@ -4042,10 +4044,15 @@ func TestInlineStepOut(t *testing.T) { func TestInlineFunctionList(t *testing.T) { // We should be able to list all functions, even inlined ones. - if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { + ver, _ := goversion.Parse(runtime.Version()) + if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } + if runtime.GOOS == "windows" && runtime.GOARCH == "arm64" { + // TODO(qmuntal): seems to be an upstream issue, investigate. + t.Skip("inlining not supported") + } withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { var found bool for _, fn := range p.BinInfo().Functions { diff --git a/pkg/proc/target.go b/pkg/proc/target.go index f556fee834..35d4c23f48 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -268,7 +268,7 @@ func (t *Target) Valid() (bool, error) { // Currently only non-recorded processes running on AMD64 support // function calls. func (t *Target) SupportsFunctionCalls() bool { - return t.Process.BinInfo().Arch.Name == "amd64" || t.Process.BinInfo().Arch.Name == "arm64" + return t.Process.BinInfo().Arch.Name == "amd64" || (t.Process.BinInfo().Arch.Name == "arm64" && t.Process.BinInfo().GOOS != "windows") } // ClearCaches clears internal caches that should not survive a restart. diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index bd5086738f..a9261e1ada 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -341,7 +341,7 @@ func MustSupportFunctionCalls(t *testing.T, testBackend string) { t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH)) } if runtime.GOARCH == "arm64" { - if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 19) { + if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 19) || runtime.GOOS == "windows" { t.Skip("this version of Go does not support function calls") } } diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 5615d42822..159392855d 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -5472,10 +5472,14 @@ func TestLaunchRequestWithRelativeExecPath(t *testing.T) { runTest(t, "increment", func(client *daptest.Client, fixture protest.Fixture) { symlink := "./__thisexe" err := os.Symlink(fixture.Path, symlink) - defer os.Remove(symlink) if err != nil { - t.Fatal("unable to create relative symlink:", err) + if runtime.GOOS == "windows" { + t.Skip("this test requires symlinks to be enabled and allowed") + } else { + t.Fatal("unable to create relative symlink:", err) + } } + defer os.Remove(symlink) runDebugSession(t, client, "launch", func() { client.LaunchRequestWithArgs(map[string]interface{}{ "mode": "exec", "program": symlink}) From f07be48220d1ffaff90b8836e8c5fc5f80d34ff8 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 8 Dec 2022 19:34:57 +0100 Subject: [PATCH 18/72] *: CHANGELOG and bump to version 1.20.0 (#3209) Thank you @Frederick888, @emad-elsaid, @cuiweixie, @qmuntal, @suzmue, @cuishuang, @aviramha, @dlipovetsky, @Foxboron, @gfszr. --- .teamcity/settings.kts | 14 +++++++------- CHANGELOG.md | 25 +++++++++++++++++++++++++ pkg/goversion/compat.go | 4 ++-- pkg/version/version.go | 3 +-- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index b8c62b5ab0..103a3fa7c4 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -35,26 +35,26 @@ To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View version = "2020.2" val targets = arrayOf( - "linux/amd64/1.17", "linux/amd64/1.18", "linux/amd64/1.19", + "linux/amd64/1.20", "linux/amd64/tip", - "linux/386/1.19", + "linux/386/1.20", - "linux/arm64/1.19", + "linux/arm64/1.20", "linux/arm64/tip", - "windows/amd64/1.19", + "windows/amd64/1.20", "windows/amd64/tip", - "windows/arm64/1.19", + "windows/arm64/1.20", "windows/arm64/tip", - "mac/amd64/1.19", + "mac/amd64/1.20", "mac/amd64/tip", - "mac/arm64/1.19", + "mac/arm64/1.20", "mac/arm64/tip" ) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97903950dc..faa95790ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning. +## [1.20.0] 2022-12-07 + +### Added + +- Support for Go 1.20 (#3129, #3196, #3180, @cuiweixie, @qmuntal, @aarzilli) +- Support for Windows/arm64 (gated by a build tag) (#3063, #3198, #3200, @qmuntal) +- Compatibility with coredumpctl (#3195, @Foxboron) + +### Fixed + +- Improve evaluation of type casts (#3146, #3149, #3186, @aarzilli) +- DAP: Added type to response of EvaluateRequest (#3172, @gfszr) +- Cgo stacktraces on linux/arm64 (#3192, @derekparker) +- Debugserver crashes on recent versions of macOS when $DYLD_INSERT_LIBRARIES is set (#3181, @aviramha) +- Stacktraces and stepping on Go 1.19.2 and later on macOS (#3204, @aarzilli) +- Attaching to processes used by a different user on Windows (#3162, @aarzilli) +- Nil pointer dereference when current address is not part of a function (#3157, @aarzilli) + +### Changed + +- Change behavior of exec command so that it looks for the executable in the current directory (#3167, @derekparker) +- DAP shows full value when evaluating log messages (#3141, @suzmue) +- Wait time is no longer reported for parked goroutines (its value was always incorrect) (#3139, @aarzilli) +- Miscellaneous improvements to documentation and error messages (#3119, #3117, #3154, #3161, #3169, #3188, @aarzilli, @derekparker, @cuishuang, @Frederick888, @dlipovetsky) + ## [1.9.1] 2022-08-23 ### Added diff --git a/pkg/goversion/compat.go b/pkg/goversion/compat.go index f8838aa259..5524ac9b5d 100644 --- a/pkg/goversion/compat.go +++ b/pkg/goversion/compat.go @@ -8,9 +8,9 @@ import ( var ( MinSupportedVersionOfGoMajor = 1 - MinSupportedVersionOfGoMinor = 17 + MinSupportedVersionOfGoMinor = 18 MaxSupportedVersionOfGoMajor = 1 - MaxSupportedVersionOfGoMinor = 19 + MaxSupportedVersionOfGoMinor = 20 goTooOldErr = fmt.Sprintf("Go version %%s is too old for this version of Delve (minimum supported version %d.%d, suppress this error with --check-go-version=false)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor) goTooOldWarn = fmt.Sprintf("WARNING: undefined behavior - Go version %%s is too old for this version of Delve (minimum supported version %d.%d)", MinSupportedVersionOfGoMajor, MinSupportedVersionOfGoMinor) dlvTooOldErr = fmt.Sprintf("Version of Delve is too old for Go version %%s (maximum supported version %d.%d, suppress this error with --check-go-version=false)", MaxSupportedVersionOfGoMajor, MaxSupportedVersionOfGoMinor) diff --git a/pkg/version/version.go b/pkg/version/version.go index d81b9fd12b..8ec46ee3d2 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -17,8 +17,7 @@ type Version struct { var ( // DelveVersion is the current version of Delve. DelveVersion = Version{ - Major: "1", Minor: "9", Patch: "1", Metadata: "", - //TODO(aarzilli): before updating this to 1.8.0 re-enable staticcheck test + Major: "1", Minor: "20", Patch: "0", Metadata: "", Build: "$Id$", } ) From a35b902ecf5241762e696fd220125bb84da41e04 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 12 Dec 2022 17:36:48 +0100 Subject: [PATCH 19/72] gdbserial: continue if tcsetpgrp fails (#3211) Do not stop if tcsetpgrp errors, also only do it if the target process got its own process group. Fixes #3210 --- _scripts/test_linux.sh | 5 ++++- pkg/proc/gdbserial/gdbserver.go | 2 +- pkg/proc/gdbserial/gdbserver_unix.go | 6 +++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/_scripts/test_linux.sh b/_scripts/test_linux.sh index 0ae64d6d79..4b18efc2d1 100755 --- a/_scripts/test_linux.sh +++ b/_scripts/test_linux.sh @@ -31,8 +31,11 @@ if [ "$version" = "gotip" ]; then cd - else echo Finding latest patch version for $version - version=$(curl 'https://go.dev/dl/?mode=json&include=all' | jq '.[].version' --raw-output | egrep ^$version'($|\.|^beta|^rc)' | sort -rV | head -1) echo "Go $version on $arch" + version=$(curl 'https://go.dev/dl/?mode=json&include=all' | jq '.[].version' --raw-output | egrep ^$version'($|\.|beta|rc)' | sort -rV | head -1) + if [ "x$version" = "x" ]; then + version=$(curl 'https://go.dev/dl/?mode=json&include=all' | jq '.[].version' --raw-output | egrep ^$version'($|\.)' | sort -rV | head -1) + fi getgo $version fi diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 8cea453cc9..6049a8e8b8 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -575,7 +575,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ } if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) { // Make the target process the controlling process of the tty if it is a foreground process. - err = tcsetpgrp(os.Stdin.Fd(), p.conn.pid) + err := tcsetpgrp(os.Stdin.Fd(), p.conn.pid) if err != nil { logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err) } diff --git a/pkg/proc/gdbserial/gdbserver_unix.go b/pkg/proc/gdbserial/gdbserver_unix.go index d69bb38ddc..4ba87ebf87 100644 --- a/pkg/proc/gdbserial/gdbserver_unix.go +++ b/pkg/proc/gdbserial/gdbserver_unix.go @@ -19,5 +19,9 @@ func foregroundSignalsIgnore() { } func tcsetpgrp(fd uintptr, pid int) error { - return unix.IoctlSetPointerInt(int(fd), unix.TIOCSPGRP, pid) + pgid, _ := syscall.Getpgid(pid) + if pid == pgid { + return unix.IoctlSetPointerInt(int(fd), unix.TIOCSPGRP, pid) + } + return nil } From 8e48ad75747fbc3d5faa07cb4f2ca8de85f25c29 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 12 Dec 2022 18:21:46 +0100 Subject: [PATCH 20/72] *: bump to version 1.20.1 (#3213) --- CHANGELOG.md | 6 ++++++ pkg/version/version.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faa95790ee..76f5daeeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. This project adheres to Semantic Versioning. +## [1.20.1] 2022-12-12 + +### Fixed + +- Fix executing programs on macOS with most versions of debugserver installed (#3211, @aarzilli) + ## [1.20.0] 2022-12-07 ### Added diff --git a/pkg/version/version.go b/pkg/version/version.go index 8ec46ee3d2..96e65b6c61 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -17,7 +17,7 @@ type Version struct { var ( // DelveVersion is the current version of Delve. DelveVersion = Version{ - Major: "1", Minor: "20", Patch: "0", Metadata: "", + Major: "1", Minor: "20", Patch: "1", Metadata: "", Build: "$Id$", } ) From 3a91d56823e6b8bafd4edd6fd7c12eaac85df379 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 12 Dec 2022 18:59:39 +0100 Subject: [PATCH 21/72] Documentation: add -test.run to test example (#3212) Add -test.run to the example of how 'dlv test' can be used. --- Documentation/usage/dlv_test.md | 2 +- cmd/dlv/cmds/commands.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index 9da841160c..cdeeac4179 100644 --- a/Documentation/usage/dlv_test.md +++ b/Documentation/usage/dlv_test.md @@ -11,7 +11,7 @@ unit tests. By default Delve will debug the tests in the current directory. Alternatively you can specify a package name, and Delve will debug the tests in that package instead. Double-dashes `--` can be used to pass arguments to the test program: -dlv test [package] -- -test.v -other-argument +dlv test [package] -- -test.run TestSometing -test.v -other-argument See also: 'go help testflag'. diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index d76ffe79b0..6b1d479c9d 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -278,7 +278,7 @@ unit tests. By default Delve will debug the tests in the current directory. Alternatively you can specify a package name, and Delve will debug the tests in that package instead. Double-dashes ` + "`--`" + ` can be used to pass arguments to the test program: -dlv test [package] -- -test.v -other-argument +dlv test [package] -- -test.run TestSometing -test.v -other-argument See also: 'go help testflag'.`, Run: testCmd, From 34b6ee869f50fbcea89b5400d362da3cacd11501 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Mon, 12 Dec 2022 20:16:49 +0200 Subject: [PATCH 22/72] proc: replace os.SEEK_CUR with io.SeekCurrent (#3214) Because os.SEEK_CUR is deprecated as stated in the documentation. --- pkg/proc/core/linux_core.go | 4 ++-- pkg/proc/variables.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/proc/core/linux_core.go b/pkg/proc/core/linux_core.go index 25ebbbfa89..ab53291dc8 100644 --- a/pkg/proc/core/linux_core.go +++ b/pkg/proc/core/linux_core.go @@ -340,14 +340,14 @@ func readNote(r io.ReadSeeker, machineType elf.Machine) (*note, error) { // skipPadding moves r to the next multiple of pad. func skipPadding(r io.ReadSeeker, pad int64) error { - pos, err := r.Seek(0, os.SEEK_CUR) + pos, err := r.Seek(0, io.SeekCurrent) if err != nil { return err } if pos%pad == 0 { return nil } - if _, err := r.Seek(pad-(pos%pad), os.SEEK_CUR); err != nil { + if _, err := r.Seek(pad-(pos%pad), io.SeekCurrent); err != nil { return err } return nil diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 425aecb086..7d063e9586 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -2293,7 +2293,7 @@ func (v *Variable) ConstDescr() string { } if typename := v.DwarfType.Common().Name; !strings.Contains(typename, ".") || strings.HasPrefix(typename, "C.") { // only attempt to use constants for user defined type, otherwise every - // int variable with value 1 will be described with os.SEEK_CUR and other + // int variable with value 1 will be described with io.SeekCurrent and other // similar problems. return "" } From 998e2f2aea7bc5a59d92d625d16e33481b1c5dfc Mon Sep 17 00:00:00 2001 From: Javier Honduvilla Coto Date: Tue, 13 Dec 2022 17:09:20 +0000 Subject: [PATCH 23/72] dwarf/frame: Remove expression constants (#3218) As they are unused and wrong. pkg/dwarf/op/opcodes.go has the right opcodes and that's what's used Signed-off-by: Francisco Javier Honduvilla Coto Signed-off-by: Francisco Javier Honduvilla Coto --- pkg/dwarf/frame/expression_constants.go | 164 ------------------------ 1 file changed, 164 deletions(-) delete mode 100644 pkg/dwarf/frame/expression_constants.go diff --git a/pkg/dwarf/frame/expression_constants.go b/pkg/dwarf/frame/expression_constants.go deleted file mode 100644 index 95240cfea9..0000000000 --- a/pkg/dwarf/frame/expression_constants.go +++ /dev/null @@ -1,164 +0,0 @@ -package frame - -// Operation opcodes -const ( - DW_OP_addr = 0x03 - DW_OP_const1s = 0x09 -) - -const ( - DW_OP_const2u = 0x0a - DW_OP_const2s = 0x0b - DW_OP_const4u = iota - DW_OP_const4s - DW_OP_const8u - DW_OP_const8s - DW_OP_constu - DW_OP_consts - DW_OP_dup - DW_OP_drop - DW_OP_over - DW_OP_pick - DW_OP_swap - DW_OP_rot - DW_OP_xderef - DW_OP_abs - DW_OP_and - DW_OP_div - DW_OP_minus - DW_OP_mod - DW_OP_mul - DW_OP_neg - DW_OP_not - DW_OP_or - DW_OP_plus - DW_OP_plus_uconst - DW_OP_shl - DW_OP_shr - DW_OP_shra - DW_OP_xor - DW_OP_skip - DW_OP_bra - DW_OP_eq - DW_OP_ge - DW_OP_gt - DW_OP_le - DW_OP_lt - DW_OP_ne -) - -const ( - DW_OP_lit0 = 0x30 - DW_OP_lit1 = 0x31 - DW_OP_lit2 = iota - DW_OP_lit3 - DW_OP_lit4 - DW_OP_lit5 - DW_OP_lit6 - DW_OP_lit7 - DW_OP_lit8 - DW_OP_lit9 - DW_OP_lit10 - DW_OP_lit11 - DW_OP_lit12 - DW_OP_lit13 - DW_OP_lit14 - DW_OP_lit15 - DW_OP_lit16 - DW_OP_lit17 - DW_OP_lit18 - DW_OP_lit19 - DW_OP_lit20 - DW_OP_lit21 - DW_OP_lit22 - DW_OP_lit23 - DW_OP_lit24 - DW_OP_lit25 - DW_OP_lit26 - DW_OP_lit27 - DW_OP_lit28 - DW_OP_lit29 - DW_OP_lit30 - DW_OP_lit31 - DW_OP_reg0 - DW_OP_reg1 - DW_OP_reg2 - DW_OP_reg3 - DW_OP_reg4 - DW_OP_reg5 - DW_OP_reg6 - DW_OP_reg7 - DW_OP_reg8 - DW_OP_reg9 - DW_OP_reg10 - DW_OP_reg11 - DW_OP_reg12 - DW_OP_reg13 - DW_OP_reg14 - DW_OP_reg15 - DW_OP_reg16 - DW_OP_reg17 - DW_OP_reg18 - DW_OP_reg19 - DW_OP_reg20 - DW_OP_reg21 - DW_OP_reg22 - DW_OP_reg23 - DW_OP_reg24 - DW_OP_reg25 - DW_OP_reg26 - DW_OP_reg27 - DW_OP_reg28 - DW_OP_reg29 - DW_OP_reg30 - DW_OP_reg31 - DW_OP_breg0 - DW_OP_breg1 - DW_OP_breg2 - DW_OP_breg3 - DW_OP_breg4 - DW_OP_breg5 - DW_OP_breg6 - DW_OP_breg7 - DW_OP_breg8 - DW_OP_breg9 - DW_OP_breg10 - DW_OP_breg11 - DW_OP_breg12 - DW_OP_breg13 - DW_OP_breg14 - DW_OP_breg15 - DW_OP_breg16 - DW_OP_breg17 - DW_OP_breg18 - DW_OP_breg19 - DW_OP_breg20 - DW_OP_breg21 - DW_OP_breg22 - DW_OP_breg23 - DW_OP_breg24 - DW_OP_breg25 - DW_OP_breg26 - DW_OP_breg27 - DW_OP_breg28 - DW_OP_breg29 - DW_OP_breg30 - DW_OP_breg31 - DW_OP_regx - DW_OP_fbreg - DW_OP_bregx - DW_OP_piece - DW_OP_deref_size - DW_OP_xderef_size - DW_OP_nop - DW_OP_push_object_address - DW_OP_call2 - DW_OP_call4 - DW_OP_call_ref - DW_OP_form_tls_address - DW_OP_call_frame_cfa - DW_OP_bit_piece - - DW_OP_lo_user = 0xe0 - DW_OP_hi_user = 0xff -) From 753cb4cc3bd5af3c27f92423c1383a17b2bd50f7 Mon Sep 17 00:00:00 2001 From: Russell Kennington Date: Tue, 13 Dec 2022 23:36:07 -0700 Subject: [PATCH 24/72] Update github.com/derekparker/trie dependency (#3219) --- go.mod | 2 +- go.sum | 2 ++ vendor/github.com/derekparker/trie/go.mod | 3 +++ vendor/github.com/derekparker/trie/trie.go | 2 +- vendor/modules.txt | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/derekparker/trie/go.mod diff --git a/go.mod b/go.mod index 000ace364f..2c5cd476b1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/cilium/ebpf v0.7.0 github.com/cosiner/argv v0.1.0 github.com/creack/pty v1.1.9 - github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 + github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4 github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d github.com/google/go-dap v0.6.0 github.com/hashicorp/golang-lru v0.5.4 diff --git a/go.sum b/go.sum index 400c4568bf..e3b8ac71aa 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 h1:G765iDCq7bP5opdrPkXk+4V3yfkgV9iGFuheWZ/X/zY= github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= +github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4 h1:atN94qKNhLpy+9BwbE5nxvFj4rScJi6W3x/NfFmMDg4= +github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/vendor/github.com/derekparker/trie/go.mod b/vendor/github.com/derekparker/trie/go.mod new file mode 100644 index 0000000000..d8e6e6a1a7 --- /dev/null +++ b/vendor/github.com/derekparker/trie/go.mod @@ -0,0 +1,3 @@ +module github.com/derekparker/trie + +go 1.19 diff --git a/vendor/github.com/derekparker/trie/trie.go b/vendor/github.com/derekparker/trie/trie.go index 2837c64864..772a3a38a6 100644 --- a/vendor/github.com/derekparker/trie/trie.go +++ b/vendor/github.com/derekparker/trie/trie.go @@ -246,7 +246,7 @@ func collect(node *Node) []string { i int ) keys := make([]string, 0, node.termCount) - nodes := make([]*Node, 1, len(node.children)) + nodes := make([]*Node, 1, len(node.children)+1) nodes[0] = node for l := len(nodes); l != 0; l = len(nodes) { i = l - 1 diff --git a/vendor/modules.txt b/vendor/modules.txt index 80cb8d5244..145df09bdb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,7 +15,7 @@ github.com/cpuguy83/go-md2man/v2/md2man # github.com/creack/pty v1.1.9 ## explicit github.com/creack/pty -# github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 +# github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4 ## explicit github.com/derekparker/trie # github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d From 95238498831d9713c7aee9af7a881727c4772d63 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Wed, 14 Dec 2022 18:56:07 +0200 Subject: [PATCH 25/72] *: Change comments to match common Go standarts (#3221) --- pkg/dwarf/util/buf.go | 8 ++++---- pkg/locspec/locations.go | 4 ++-- pkg/proc/arm64_arch.go | 2 +- pkg/proc/arm64_disasm.go | 2 +- pkg/proc/core/core.go | 2 +- pkg/proc/interface.go | 2 +- pkg/proc/linutil/doc.go | 2 +- pkg/proc/linutil/regs_arm64_arch.go | 6 +++--- pkg/proc/proc_test.go | 4 ++-- pkg/proc/variables.go | 2 +- 10 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/dwarf/util/buf.go b/pkg/dwarf/util/buf.go index 12899f8aa3..ddb97958e5 100644 --- a/pkg/dwarf/util/buf.go +++ b/pkg/dwarf/util/buf.go @@ -36,7 +36,7 @@ type dataFormat interface { addrsize() int } -// Some parts of DWARF have no data format, e.g., abbrevs. +// UnknownFormat is a struct for some parts of DWARF that have no data format, e.g., abbrevs. type UnknownFormat struct{} func (u UnknownFormat) version() int { @@ -66,7 +66,7 @@ func (b *buf) Uint8() uint8 { return val } -// Read a varint, which is 7 bits per byte, little endian. +// Varint reads a varint, which is 7 bits per byte, little endian. // the 0x80 bit means read another byte. func (b *buf) Varint() (c uint64, bits uint) { for i := 0; i < len(b.data); i++ { @@ -82,13 +82,13 @@ func (b *buf) Varint() (c uint64, bits uint) { return 0, 0 } -// Unsigned int is just a varint. +// Uint is just a varint. func (b *buf) Uint() uint64 { x, _ := b.Varint() return x } -// Signed int is a sign-extended varint. +// Int is a sign-extended varint. func (b *buf) Int() int64 { ux, bits := b.Varint() x := int64(ux) diff --git a/pkg/locspec/locations.go b/pkg/locspec/locations.go index b3c6710fe6..fd01f4a2ad 100644 --- a/pkg/locspec/locations.go +++ b/pkg/locspec/locations.go @@ -491,8 +491,8 @@ func SubstitutePath(path string, rules [][2]string) string { // On windows paths returned from headless server are as c:/dir/dir // though os.PathSeparator is '\\' - separator := "/" //make it default - if strings.Contains(path, "\\") { //dependent on the path + separator := "/" // make it default + if strings.Contains(path, "\\") { // dependent on the path separator = "\\" } for _, r := range rules { diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index 580539ff8d..01272e6954 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -223,7 +223,7 @@ func arm64SwitchStack(it *stackIterator, callFrameRegs *op.DwarfRegisters) bool it.atend = true return true case "crosscall2": - //The offsets get from runtime/cgo/asm_arm64.s:10 + // The offsets get from runtime/cgo/asm_arm64.s:10 bpoff := uint64(14) lroff := uint64(15) if producer := it.bi.Producer(); producer != "" && goversion.ProducerAfterOrEqual(producer, 1, 19) { diff --git a/pkg/proc/arm64_disasm.go b/pkg/proc/arm64_disasm.go index bd918f4a9a..517c9a2577 100644 --- a/pkg/proc/arm64_disasm.go +++ b/pkg/proc/arm64_disasm.go @@ -43,7 +43,7 @@ func arm64AsmDecode(asmInst *AsmInstruction, mem []byte, regs *op.DwarfRegisters func resolveCallArgARM64(inst *arm64asm.Inst, instAddr uint64, currentGoroutine bool, regs *op.DwarfRegisters, mem MemoryReadWriter, bininfo *BinaryInfo) *Location { switch inst.Op { case arm64asm.BL, arm64asm.BLR, arm64asm.B, arm64asm.BR: - //ok + // ok default: return nil } diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 561b0096f7..148d3405c9 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -396,7 +396,7 @@ func (t *thread) SetDX(uint64) error { return ErrChangeRegisterCore } -// ChangeRegs will always return an error, you cannot +// SetReg will always return an error, you cannot // change register values when debugging core files. func (t *thread) SetReg(regNum uint64, reg *op.DwarfRegister) error { return ErrChangeRegisterCore diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 2f845211bf..4027b40efc 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -64,7 +64,7 @@ type RecordingManipulation interface { // Recorded returns true if the current process is a recording and the path // to the trace directory. Recorded() (recorded bool, tracedir string) - // Direction changes execution direction. + // ChangeDirection changes execution direction. ChangeDirection(Direction) error // GetDirection returns the current direction of execution. GetDirection() Direction diff --git a/pkg/proc/linutil/doc.go b/pkg/proc/linutil/doc.go index c126c672e5..b4bea5897c 100644 --- a/pkg/proc/linutil/doc.go +++ b/pkg/proc/linutil/doc.go @@ -1,4 +1,4 @@ -// This package contains functions and data structures used by both the +// Package linutil contains functions and data structures used by both the // linux implementation of the native backend and the core backend to deal // with structures used by the linux kernel. package linutil diff --git a/pkg/proc/linutil/regs_arm64_arch.go b/pkg/proc/linutil/regs_arm64_arch.go index 5e9243a9cb..05fb48e9c6 100644 --- a/pkg/proc/linutil/regs_arm64_arch.go +++ b/pkg/proc/linutil/regs_arm64_arch.go @@ -10,11 +10,11 @@ import ( // ARM64Registers is a wrapper for sys.PtraceRegs. type ARM64Registers struct { - Regs *ARM64PtraceRegs //general-purpose registers + Regs *ARM64PtraceRegs // general-purpose registers iscgo bool tpidr_el0 uint64 - Fpregs []proc.Register //Formatted floating point registers - Fpregset []byte //holding all floating point register values + Fpregs []proc.Register // Formatted floating point registers + Fpregset []byte // holding all floating point register values loadFpRegs func(*ARM64Registers) error } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 02d8d83328..2af4e67207 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -1047,7 +1047,7 @@ func TestKill(t *testing.T) { } if runtime.GOOS == "linux" { if runtime.GOARCH == "arm64" { - //there is no any sync between signal sended(tracee handled) and open /proc/%d/. It may fail on arm64 + // there is no any sync between signal sended(tracee handled) and open /proc/%d/. It may fail on arm64 return } _, err := os.Open(fmt.Sprintf("/proc/%d/", p.Pid())) @@ -4693,7 +4693,7 @@ func TestPluginStepping(t *testing.T) { func TestIssue1601(t *testing.T) { protest.MustHaveCgo(t) - //Tests that recursive types involving C qualifiers and typedefs are parsed correctly + // Tests that recursive types involving C qualifiers and typedefs are parsed correctly withTestProcess("issue1601", t, func(p *proc.Target, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue") evalVariable(p, t, "C.globalq") diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 7d063e9586..45b946bc0a 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -81,7 +81,7 @@ const ( // the variable is the return value of a function call and allocated on a // frame that no longer exists) VariableFakeAddress - // VariableCPrt means the variable is a C pointer + // VariableCPtr means the variable is a C pointer VariableCPtr // VariableCPURegister means this variable is a CPU register. VariableCPURegister From 72c77b1c30fccead4c8d03e9bb9fbe071c156ace Mon Sep 17 00:00:00 2001 From: Philippe Blain Date: Thu, 15 Dec 2022 03:02:06 -0500 Subject: [PATCH 26/72] Documentation/faq: Correct commands in substitute-path section (#3222) The text of the substitute-path section in the FAQ ("Can not set breakpoints or see source listing in a complicated debugging environment") mentions the command 'config help substitute-path', but 'help' is not a valid argument to the 'config' command. Use 'help config' instead, which does mention 'substitute-path' and how to set it. Also, the command to list source files is 'sources', not 'source'. Fix that. --- Documentation/faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/faq.md b/Documentation/faq.md index 97a67a293d..d63543fc2c 100644 --- a/Documentation/faq.md +++ b/Documentation/faq.md @@ -103,8 +103,8 @@ Command failed: open /path/to/the/mainfile.go: no such file or directory This is not a bug. The Go compiler embeds the paths of source files into the executable so that debuggers, including Delve, can use them. Doing any of the things listed above will prevent this feature from working seamlessly. -The substitute-path feature can be used to solve this problem, see `config help substitute-path` or the `substitutePath` option in launch.json. +The substitute-path feature can be used to solve this problem, see `help config` or the `substitutePath` option in launch.json. -The `source` command could also be useful in troubleshooting this problem, it shows the list of file paths that has been embedded by the compiler into the executable. +The `sources` command could also be useful in troubleshooting this problem, it shows the list of file paths that has been embedded by the compiler into the executable. If you still think this is a bug in Delve and not a configuration problem, open an [issue](https://github.com/go-delve/delve/issues), filling the issue template and including the logs produced by delve with the options `--log --log-output=rpc,dap`. From 32df4071d2ec832b91cdefda3134f25f12f58985 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 19 Dec 2022 17:15:00 +0100 Subject: [PATCH 27/72] TeamCity: fix windows/amd64 builds (#3223) Windows/amd64 builds are not runnning because the current settings.kts has an implicity requirement to property teamcity.build.systemDir which does not exist on windows/amd64 agents. --- .teamcity/settings.kts | 2 +- _scripts/test_windows.ps1 | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index 103a3fa7c4..c57106a48c 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -204,7 +204,7 @@ class TestBuild(val os: String, val arch: String, version: String, buildId: Abso scriptMode = file { path = "_scripts/test_windows.ps1" } - param("jetbrains_powershell_scriptArguments", "-version ${"go$version"} -arch $arch -binDir %teamcity.build.systemDir%") + param("jetbrains_powershell_scriptArguments", "-version ${"go$version"} -arch $arch") } } "mac" -> { diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index ff5422daeb..751483efab 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -5,7 +5,6 @@ param ( ) if ($binDir -eq "") { - # TODO: remove once the current version of settings.kts gets to master. $binDir = Resolve-Path "../../system" # working directory } From 00df758d572f403d556e9da512043661715a0a28 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 20 Dec 2022 18:54:16 +0100 Subject: [PATCH 28/72] proc/native: fix FreeBSD backend (#3224) - use PT_SUSPEND/PT_RESUME to control running threads in resume/stop/singleStep - change manual stop signal from SIGTRAP to SIGSTOP to make manual stop handling simpler - change (*nativeProcess).trapWaitInternal to suspend newly created threads when we are stepping a thread - change (*nativeProcess).trapWaitInternal to handle some unhandled stop events - remove misleading (*nativeProcess).waitFast which does not do anything different from the normal wait variant - rewrite (*nativeProcess).stop to only set breakpoints for threads of which we have received SIGTRAP - rewrite (*nativeThread).singleStep to actually execute a single instruction and to properly route signals --- Documentation/backend_test_health.md | 3 +- cmd/dlv/dlv_test.go | 5 - pkg/proc/native/proc.go | 4 +- pkg/proc/native/proc_freebsd.go | 160 ++++++++++++++++++++++----- pkg/proc/native/ptrace_freebsd.go | 33 ++++++ pkg/proc/native/threads.go | 20 ---- pkg/proc/native/threads_darwin.go | 20 ++++ pkg/proc/native/threads_freebsd.go | 98 +++++++++------- pkg/proc/native/threads_linux.go | 20 ++++ pkg/proc/native/threads_windows.go | 16 --- pkg/proc/proc_test.go | 13 +-- pkg/terminal/command_test.go | 6 - service/dap/server_test.go | 27 +---- service/test/integration1_test.go | 3 - service/test/integration2_test.go | 3 - 15 files changed, 266 insertions(+), 165 deletions(-) diff --git a/Documentation/backend_test_health.md b/Documentation/backend_test_health.md index 6ee41512ea..4f16ef93bb 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -11,8 +11,7 @@ Tests skipped by each supported backend: * 1 broken - cgo stacktraces * darwin/lldb skipped = 1 * 1 upstream issue -* freebsd skipped = 16 - * 12 broken +* freebsd skipped = 4 * 4 not implemented * linux/386/pie skipped = 1 * 1 broken diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index cfec1439fd..753dd3835e 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1036,11 +1036,6 @@ func TestTrace(t *testing.T) { } func TestTraceMultipleGoroutines(t *testing.T) { - if runtime.GOOS == "freebsd" { - //TODO(aarzilli): investigate further when the FreeBSD backend is more stable. - t.Skip("temporarily disabled due to issues with FreeBSD in Delve and Go") - } - dlvbin, tmpdir := getDlvBin(t) defer os.RemoveAll(tmpdir) diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 2a0bd5c8a4..b1c906a48e 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -246,14 +246,12 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc // We disable asyncpreempt for the following reasons: // - on Windows asyncpreempt is incompatible with debuggers, see: // https://github.com/golang/go/issues/36494 - // - freebsd's backend is generally broken and asyncpreempt makes it even more so, see: - // https://github.com/go-delve/delve/issues/1754 // - on linux/arm64 asyncpreempt can sometimes restart a sequence of // instructions, if the sequence happens to contain a breakpoint it will // look like the breakpoint was hit twice when it was "logically" only // executed once. // See: https://go-review.googlesource.com/c/go/+/208126 - DisableAsyncPreempt: runtime.GOOS == "windows" || runtime.GOOS == "freebsd" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), + DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), StopReason: stopReason, CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"), diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index f4fb6334c8..be659065f4 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -42,6 +42,10 @@ const ( type osProcessDetails struct { comm string tid int + + delayedSignal syscall.Signal + trapThreads []int + selectedThread *nativeThread } func (os *osProcessDetails) Close() {} @@ -83,7 +87,6 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str process.Stdout = stdout process.Stderr = stderr process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground} - process.Env = proc.DisableAsyncPreemptEnv() if foreground { signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) } @@ -152,7 +155,12 @@ func (dbp *nativeProcess) kill() (err error) { if dbp.exited { return nil } - dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, int(sys.SIGKILL)) }) + dbp.execPtraceFunc(func() { + for _, th := range dbp.threads { + ptraceResume(th.ID) + } + err = ptraceCont(dbp.pid, int(sys.SIGKILL)) + }) if err != nil { return err } @@ -165,7 +173,7 @@ func (dbp *nativeProcess) kill() (err error) { // Used by RequestManualStop func (dbp *nativeProcess) requestManualStop() (err error) { - return sys.Kill(dbp.pid, sys.SIGTRAP) + return sys.Kill(dbp.pid, sys.SIGSTOP) } // Attach to a newly created thread, and store that thread in our list of @@ -178,7 +186,7 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) var err error dbp.execPtraceFunc(func() { err = sys.PtraceLwpEvents(dbp.pid, 1) }) if err == syscall.ESRCH { - if _, _, err = dbp.waitFast(dbp.pid); err != nil { + if _, _, err = dbp.wait(dbp.pid, 0); err != nil { return nil, fmt.Errorf("error while waiting after adding process: %d %s", dbp.pid, err) } } @@ -220,13 +228,29 @@ func findExecutable(path string, pid int) string { } func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, false) + return dbp.trapWaitInternal(pid, trapWaitNormal) } +type trapWaitMode uint8 + +const ( + trapWaitNormal trapWaitMode = iota + trapWaitStepping +) + // Used by stop and trapWait -func (dbp *nativeProcess) trapWaitInternal(pid int, halt bool) (*nativeThread, error) { +func (dbp *nativeProcess) trapWaitInternal(pid int, mode trapWaitMode) (*nativeThread, error) { + if dbp.os.selectedThread != nil { + th := dbp.os.selectedThread + dbp.os.selectedThread = nil + return th, nil + } for { wpid, status, err := dbp.wait(pid, 0) + if wpid != dbp.pid { + // possibly a delayed notification from a process we just detached and killed, freebsd bug? + continue + } if err != nil { return nil, fmt.Errorf("wait err %s %d", err, pid) } @@ -239,6 +263,11 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, halt bool) (*nativeThread, e dbp.postExit() return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} } + if status.Signaled() { + // Killed by a signal + dbp.postExit() + return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} + } var info sys.PtraceLwpInfoStruct dbp.execPtraceFunc(func() { info, err = ptraceGetLwpInfo(wpid) }) @@ -269,10 +298,10 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, halt bool) (*nativeThread, e } return nil, err } - if halt { - return nil, nil + if mode == trapWaitStepping { + dbp.execPtraceFunc(func() { ptraceSuspend(tid) }) } - if err = th.Continue(); err != nil { + if err = dbp.ptraceCont(0); err != nil { if err == sys.ESRCH { // thread died while we were adding it delete(dbp.threads, int(tid)) @@ -288,12 +317,16 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, halt bool) (*nativeThread, e continue } - if (halt && status.StopSignal() == sys.SIGSTOP) || (status.StopSignal() == sys.SIGTRAP) { + if mode == trapWaitStepping { + return th, nil + } + if status.StopSignal() == sys.SIGTRAP || status.Continued() { + // Continued in this case means we received the SIGSTOP signal return th, nil } // TODO(dp) alert user about unexpected signals here. - if err := th.resumeWithSig(int(status.StopSignal())); err != nil { + if err := dbp.ptraceCont(int(status.StopSignal())); err != nil { if err == sys.ESRCH { return nil, proc.ErrProcessExited{Pid: dbp.pid} } @@ -309,14 +342,6 @@ func status(pid int) rune { return status } -// Used by stop and singleStep -// waitFast is like wait but does not handle process-exit correctly -func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) { - var s sys.WaitStatus - wpid, err := sys.Wait4(pid, &s, 0, nil) - return wpid, &s, err -} - // Only used in this file func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { var s sys.WaitStatus @@ -330,7 +355,7 @@ func (dbp *nativeProcess) exitGuard(err error) error { return err } if status(dbp.pid) == statusZombie { - _, err := dbp.trapWaitInternal(-1, false) + _, err := dbp.trapWaitInternal(-1, trapWaitNormal) return err } @@ -348,9 +373,31 @@ func (dbp *nativeProcess) resume() error { thread.CurrentBreakpoint.Clear() } } + if len(dbp.os.trapThreads) > 0 { + // On these threads we have already received a SIGTRAP while stepping on a different thread, + // do not resume the process, instead record the breakpoint hits on them. + tt := dbp.os.trapThreads + dbp.os.trapThreads = dbp.os.trapThreads[:0] + var err error + dbp.os.selectedThread, err = dbp.postStop(tt...) + return err + } // all threads are resumed var err error - dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, 0) }) + dbp.execPtraceFunc(func() { + for _, th := range dbp.threads { + err = ptraceResume(th.ID) + if err != nil { + return + } + } + sig := int(dbp.os.delayedSignal) + dbp.os.delayedSignal = 0 + for _, thread := range dbp.threads { + thread.Status = nil + } + err = ptraceCont(dbp.pid, sig) + }) return err } @@ -360,19 +407,72 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } - // set breakpoints on all threads - for _, th := range dbp.threads { - if th.CurrentBreakpoint.Breakpoint == nil { - if err := th.SetCurrentBreakpoint(true); err != nil { - return nil, err + + var err error + dbp.execPtraceFunc(func() { + for _, th := range dbp.threads { + err = ptraceSuspend(th.ID) + if err != nil { + return } } + }) + if err != nil { + return nil, err } - return trapthread, nil + + if trapthread.Status == nil || (*sys.WaitStatus)(trapthread.Status).StopSignal() != sys.SIGTRAP { + return trapthread, nil + } + + return dbp.postStop(trapthread.ID) +} + +func (dbp *nativeProcess) postStop(tids ...int) (*nativeThread, error) { + var pickedTrapThread *nativeThread + for _, tid := range tids { + trapthread := dbp.threads[tid] + if trapthread == nil { + continue + } + + // Must either be a hardcoded breakpoint, a currently set breakpoint or a + // phantom breakpoint hit caused by a SIGTRAP that was delayed. + // If someone sent a SIGTRAP directly to the process this will fail, but we + // can't do better. + + err := trapthread.SetCurrentBreakpoint(true) + if err != nil { + return nil, err + } + + if trapthread.CurrentBreakpoint.Breakpoint == nil { + // hardcoded breakpoint or phantom breakpoint hit + if dbp.BinInfo().Arch.BreakInstrMovesPC() { + pc, _ := trapthread.PC() + if !trapthread.atHardcodedBreakpoint(pc) { + // phantom breakpoint hit + _ = trapthread.setPC(pc - uint64(len(dbp.BinInfo().Arch.BreakpointInstruction()))) + trapthread = nil + } + } + } + + if pickedTrapThread == nil && trapthread != nil { + pickedTrapThread = trapthread + } + } + + return pickedTrapThread, nil } // Used by Detach func (dbp *nativeProcess) detach(kill bool) error { + if !kill { + for _, th := range dbp.threads { + ptraceResume(th.ID) + } + } return ptraceDetach(dbp.pid) } @@ -395,6 +495,12 @@ func (dbp *nativeProcess) GetBufferedTracepoints() []ebpf.RawUProbeParams { panic("not implemented") } +func (dbp *nativeProcess) ptraceCont(sig int) error { + var err error + dbp.execPtraceFunc(func() { err = ptraceCont(dbp.pid, sig) }) + return err +} + // Usedy by Detach func killProcess(pid int) error { return sys.Kill(pid, sys.SIGINT) diff --git a/pkg/proc/native/ptrace_freebsd.go b/pkg/proc/native/ptrace_freebsd.go index fe7c5c371a..93cc90c22b 100644 --- a/pkg/proc/native/ptrace_freebsd.go +++ b/pkg/proc/native/ptrace_freebsd.go @@ -10,6 +10,7 @@ import "C" import ( "unsafe" + "syscall" sys "golang.org/x/sys/unix" ) @@ -66,3 +67,35 @@ func ptraceReadData(id int, addr uintptr, data []byte) (n int, err error) { func ptraceWriteData(id int, addr uintptr, data []byte) (n int, err error) { return sys.PtraceIO(sys.PIOD_WRITE_D, id, addr, data, len(data)) } + +func ptraceSuspend(id int) error { + _, _, e1 := sys.Syscall6(sys.SYS_PTRACE, uintptr(C.PT_SUSPEND), uintptr(id), uintptr(1), uintptr(0), 0, 0) + if e1 != 0 { + return syscall.Errno(e1) + } + return nil +} + +func ptraceResume(id int) error { + _, _, e1 := sys.Syscall6(sys.SYS_PTRACE, uintptr(C.PT_RESUME), uintptr(id), uintptr(1), uintptr(0), 0, 0) + if e1 != 0 { + return syscall.Errno(e1) + } + return nil +} + +func ptraceSetStep(id int) error { + _, _, e1 := sys.Syscall6(sys.SYS_PTRACE, uintptr(C.PT_SETSTEP), uintptr(id), uintptr(0), uintptr(0), 0, 0) + if e1 != 0 { + return syscall.Errno(e1) + } + return nil +} + +func ptraceClearStep(id int) error { + _, _, e1 := sys.Syscall6(sys.SYS_PTRACE, uintptr(C.PT_CLEARSTEP), uintptr(id), uintptr(0), uintptr(0), 0, 0) + if e1 != 0 { + return syscall.Errno(e1) + } + return nil +} diff --git a/pkg/proc/native/threads.go b/pkg/proc/native/threads.go index 3088545e22..5cf570d600 100644 --- a/pkg/proc/native/threads.go +++ b/pkg/proc/native/threads.go @@ -22,26 +22,6 @@ type nativeThread struct { common proc.CommonThread } -// Continue the execution of this thread. -// -// If we are currently at a breakpoint, we'll clear it -// first and then resume execution. Thread will continue until -// it hits a breakpoint or is signaled. -func (t *nativeThread) Continue() error { - pc, err := t.PC() - if err != nil { - return err - } - // Check whether we are stopped at a breakpoint, and - // if so, single step over it before continuing. - if _, ok := t.dbp.FindBreakpoint(pc, false); ok { - if err := t.StepInstruction(); err != nil { - return err - } - } - return t.resume() -} - // StepInstruction steps a single instruction. // // Executes exactly one instruction and then returns. diff --git a/pkg/proc/native/threads_darwin.go b/pkg/proc/native/threads_darwin.go index d4df60395c..b338ff3737 100644 --- a/pkg/proc/native/threads_darwin.go +++ b/pkg/proc/native/threads_darwin.go @@ -143,3 +143,23 @@ func (t *nativeThread) withDebugRegisters(f func(*amd64util.DebugRegisters) erro func (t *nativeThread) SoftExc() bool { return false } + +// Continue the execution of this thread. +// +// If we are currently at a breakpoint, we'll clear it +// first and then resume execution. Thread will continue until +// it hits a breakpoint or is signaled. +func (t *nativeThread) Continue() error { + pc, err := t.PC() + if err != nil { + return err + } + // Check whether we are stopped at a breakpoint, and + // if so, single step over it before continuing. + if _, ok := t.dbp.FindBreakpoint(pc, false); ok { + if err := t.StepInstruction(); err != nil { + return err + } + } + return t.resume() +} diff --git a/pkg/proc/native/threads_freebsd.go b/pkg/proc/native/threads_freebsd.go index f9163014b1..241455be57 100644 --- a/pkg/proc/native/threads_freebsd.go +++ b/pkg/proc/native/threads_freebsd.go @@ -1,16 +1,13 @@ package native -// #include -import "C" - import ( - "fmt" - "github.com/go-delve/delve/pkg/proc/fbsdutil" + "bytes" sys "golang.org/x/sys/unix" "github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc/amd64util" + "github.com/go-delve/delve/pkg/proc/fbsdutil" ) type waitStatus sys.WaitStatus @@ -20,59 +17,58 @@ type osSpecificDetails struct { registers sys.Reg } -func (t *nativeThread) stop() (err error) { - _, err = C.thr_kill2(C.pid_t(t.dbp.pid), C.long(t.ID), C.int(sys.SIGSTOP)) - if err != nil { - err = fmt.Errorf("stop err %s on thread %d", err, t.ID) - return - } - // If the process is stopped, we must continue it so it can receive the - // signal - t.dbp.execPtraceFunc(func() { err = ptraceCont(t.dbp.pid, 0) }) +func (t *nativeThread) singleStep() (err error) { + t.dbp.execPtraceFunc(func() { err = ptraceSetStep(t.ID) }) if err != nil { return err } - _, _, err = t.dbp.waitFast(t.dbp.pid) - if err != nil { - err = fmt.Errorf("wait err %s on thread %d", err, t.ID) - return - } - return -} - -func (t *nativeThread) Stopped() bool { - state := status(t.dbp.pid) - return state == statusStopped -} + defer func() { + t.dbp.execPtraceFunc(func() { ptraceClearStep(t.ID) }) + }() -func (t *nativeThread) resume() error { - return t.resumeWithSig(0) -} - -func (t *nativeThread) resumeWithSig(sig int) (err error) { - t.dbp.execPtraceFunc(func() { err = ptraceCont(t.ID, sig) }) - return -} - -func (t *nativeThread) singleStep() (err error) { - t.dbp.execPtraceFunc(func() { err = ptraceSingleStep(t.ID) }) + t.dbp.execPtraceFunc(func() { err = ptraceResume(t.ID) }) if err != nil { return err } + defer func() { + t.dbp.execPtraceFunc(func() { ptraceSuspend(t.ID) }) + }() + + sig := 0 for { - th, err := t.dbp.trapWait(t.dbp.pid) + err = t.dbp.ptraceCont(sig) + sig = 0 if err != nil { return err } - if th.ID == t.ID { - break - } - t.dbp.execPtraceFunc(func() { err = ptraceCont(th.ID, 0) }) + + trapthread, err := t.dbp.trapWaitInternal(-1, trapWaitStepping) if err != nil { return err } + + status := ((*sys.WaitStatus)(trapthread.Status)) + + if trapthread.ID == t.ID { + switch s := status.StopSignal(); s { + case sys.SIGTRAP: + return nil + case sys.SIGSTOP: + // delayed SIGSTOP, ignore it + case sys.SIGILL, sys.SIGBUS, sys.SIGFPE, sys.SIGSEGV: + // propagate signals that can be caused by current instruction + sig = int(s) + default: + t.dbp.os.delayedSignal = s + } + } else { + if status.StopSignal() == sys.SIGTRAP { + t.dbp.os.trapThreads = append(t.dbp.os.trapThreads, trapthread.ID) + } else { + t.dbp.os.delayedSignal = status.StopSignal() + } + } } - return nil } func (t *nativeThread) restoreRegisters(savedRegs proc.Registers) error { @@ -110,3 +106,19 @@ func (t *nativeThread) withDebugRegisters(f func(*amd64util.DebugRegisters) erro func (t *nativeThread) SoftExc() bool { return false } + +func (t *nativeThread) atHardcodedBreakpoint(pc uint64) bool { + for _, bpinstr := range [][]byte{ + t.dbp.BinInfo().Arch.BreakpointInstruction(), + t.dbp.BinInfo().Arch.AltBreakpointInstruction()} { + if bpinstr == nil { + continue + } + buf := make([]byte, len(bpinstr)) + _, _ = t.ReadMemory(buf, pc-uint64(len(buf))) + if bytes.Equal(buf, bpinstr) { + return true + } + } + return false +} diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go index 1b9f707e9e..4c25cc7b4f 100644 --- a/pkg/proc/native/threads_linux.go +++ b/pkg/proc/native/threads_linux.go @@ -120,3 +120,23 @@ func (t *nativeThread) ReadMemory(data []byte, addr uint64) (n int, err error) { func (t *nativeThread) SoftExc() bool { return t.os.setbp } + +// Continue the execution of this thread. +// +// If we are currently at a breakpoint, we'll clear it +// first and then resume execution. Thread will continue until +// it hits a breakpoint or is signaled. +func (t *nativeThread) Continue() error { + pc, err := t.PC() + if err != nil { + return err + } + // Check whether we are stopped at a breakpoint, and + // if so, single step over it before continuing. + if _, ok := t.dbp.FindBreakpoint(pc, false); ok { + if err := t.StepInstruction(); err != nil { + return err + } + } + return t.resume() +} diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index ecd6dacd74..8bcd6f6192 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -107,22 +107,6 @@ func (t *nativeThread) singleStep() error { return t.setContext(context) } -func (t *nativeThread) resume() error { - var err error - t.dbp.execPtraceFunc(func() { - //TODO: Note that we are ignoring the thread we were asked to continue and are continuing the - //thread that we last broke on. - err = _ContinueDebugEvent(uint32(t.dbp.pid), uint32(t.ID), _DBG_CONTINUE) - }) - return err -} - -// Stopped returns whether the thread is stopped at the operating system -// level. On windows this always returns true. -func (t *nativeThread) Stopped() bool { - return true -} - func (t *nativeThread) WriteMemory(addr uint64, data []byte) (int, error) { if t.dbp.exited { return 0, proc.ErrProcessExited{Pid: t.dbp.pid} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 2af4e67207..8ee2d8aed1 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -586,7 +586,6 @@ func TestNextGeneral(t *testing.T) { } func TestNextConcurrent(t *testing.T) { - skipOn(t, "broken", "freebsd") testcases := []nextTest{ {8, 9}, {9, 10}, @@ -622,7 +621,6 @@ func TestNextConcurrent(t *testing.T) { } func TestNextConcurrentVariant2(t *testing.T) { - skipOn(t, "broken", "freebsd") // Just like TestNextConcurrent but instead of removing the initial breakpoint we check that when it happens is for other goroutines testcases := []nextTest{ {8, 9}, @@ -1472,7 +1470,6 @@ func TestIssue325(t *testing.T) { } func TestBreakpointCounts(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 12) @@ -1504,7 +1501,6 @@ func TestBreakpointCounts(t *testing.T) { } func TestHardcodedBreakpointCounts(t *testing.T) { - skipOn(t, "broken", "freebsd") withTestProcess("hcbpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { counts := map[int64]int{} for { @@ -1716,7 +1712,6 @@ func BenchmarkLocalVariables(b *testing.B) { } func TestCondBreakpoint(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) @@ -1738,7 +1733,6 @@ func TestCondBreakpoint(t *testing.T) { } func TestCondBreakpointError(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) @@ -2101,7 +2095,6 @@ func TestIssue462(t *testing.T) { } func TestNextParked(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") @@ -2152,7 +2145,6 @@ func TestNextParked(t *testing.T) { } func TestStepParked(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") @@ -2481,7 +2473,6 @@ func TestStepOut(t *testing.T) { } func TestStepConcurrentDirect(t *testing.T) { - skipOn(t, "broken", "freebsd") skipOn(t, "broken - step concurrent", "windows", "arm64") protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { @@ -2546,7 +2537,6 @@ func TestStepConcurrentDirect(t *testing.T) { } func TestStepConcurrentPtr(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.AllowRecording(t) withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 24) @@ -4633,7 +4623,6 @@ func testCallConcurrentCheckReturns(p *proc.Target, t *testing.T, gid1, gid2 int } func TestCallConcurrent(t *testing.T) { - skipOn(t, "broken", "freebsd") protest.MustSupportFunctionCalls(t, testBackend) withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { grp := proc.NewGroup(p) @@ -5152,7 +5141,6 @@ func TestRequestManualStopWhileStopped(t *testing.T) { func TestStepOutPreservesGoroutine(t *testing.T) { // Checks that StepOut preserves the currently selected goroutine. - skipOn(t, "broken", "freebsd") rand.Seed(time.Now().Unix()) withTestProcess("issue2113", t, func(p *proc.Target, fixture protest.Fixture) { assertNoError(p.Continue(), t, "Continue()") @@ -5894,6 +5882,7 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { // this is also ok return } + t.Logf("third continue") assertNoError(err, t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp != nil { diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 706a83db08..e6b3c2cc23 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -456,9 +456,6 @@ func TestScopePrefix(t *testing.T) { } func TestOnPrefix(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("test is not valid on FreeBSD") - } const prefix = "\ti: " test.AllowRecording(t) lenient := false @@ -520,9 +517,6 @@ func TestNoVars(t *testing.T) { } func TestOnPrefixLocals(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("test is not valid on FreeBSD") - } const prefix = "\ti: " test.AllowRecording(t) withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 159392855d..e7469e06f3 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -485,9 +485,6 @@ func TestLaunchStopOnEntry(t *testing.T) { // TestAttachStopOnEntry is like TestLaunchStopOnEntry, but with attach request. func TestAttachStopOnEntry(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.SkipNow() - } runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) { // Start the program to attach to cmd := exec.Command(fixture.Path) @@ -3435,9 +3432,6 @@ func TestHaltPreventsAutoResume(t *testing.T) { // goroutine is hit the correct number of times and log points set in the // children goroutines produce the correct number of output events. func TestConcurrentBreakpointsLogPoints(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.SkipNow() - } tests := []struct { name string fixture string @@ -3685,9 +3679,6 @@ func TestLaunchSubstitutePath(t *testing.T) { // that does not exist and expects the substitutePath attribute // in the launch configuration to take care of the mapping. func TestAttachSubstitutePath(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.SkipNow() - } if runtime.GOOS == "windows" { t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details") } @@ -4616,9 +4607,6 @@ func getPC(t *testing.T, client *daptest.Client, threadId int) (uint64, error) { // TestNextParked tests that we can switched selected goroutine to a parked one // and perform next operation on it. func TestNextParked(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.SkipNow() - } runTest(t, "parallel_next", func(client *daptest.Client, fixture protest.Fixture) { runDebugSessionWithBPs(t, client, "launch", // Launch @@ -4681,7 +4669,8 @@ func testNextParkedHelper(t *testing.T, client *daptest.Client, fixture protest. // 2. hasn't called wg.Done yet // 3. is not the currently selected goroutine for _, g := range threads.Body.Threads { - if g.Id == se.Body.ThreadId { // Skip selected goroutine + if g.Id == se.Body.ThreadId || g.Id == 0 { + // Skip selected goroutine and goroutine 0 continue } client.StackTraceRequest(g.Id, 0, 5) @@ -4709,9 +4698,6 @@ func testNextParkedHelper(t *testing.T, client *daptest.Client, fixture protest. // and checks that StepOut preserves the currently selected goroutine. func TestStepOutPreservesGoroutine(t *testing.T) { // Checks that StepOut preserves the currently selected goroutine. - if runtime.GOOS == "freebsd" { - t.SkipNow() - } rand.Seed(time.Now().Unix()) runTest(t, "issue2113", func(client *daptest.Client, fixture protest.Fixture) { runDebugSessionWithBPs(t, client, "launch", @@ -4862,9 +4848,6 @@ func TestBadAccess(t *testing.T) { // again will produce an error with a helpful message, and 'continue' // will resume the program. func TestNextWhileNexting(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("test is not valid on FreeBSD") - } // a breakpoint triggering during a 'next' operation will interrupt 'next'' // Unlike the test for the terminal package, we cannot be certain // of the number of breakpoints we expect to hit, since multiple @@ -5710,9 +5693,6 @@ func TestLaunchRequestWithEnv(t *testing.T) { } func TestAttachRequest(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.SkipNow() - } if runtime.GOOS == "windows" { t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details") } @@ -6638,9 +6618,6 @@ func TestAttachRemoteToDlvLaunchHaltedStopOnEntry(t *testing.T) { } func TestAttachRemoteToDlvAttachHaltedStopOnEntry(t *testing.T) { - if runtime.GOOS == "freebsd" || runtime.GOOS == "windows" { - t.SkipNow() - } cmd, dbg := attachDebuggerWithTargetHalted(t, "http_server") runTestWithDebugger(t, dbg, func(client *daptest.Client) { client.AttachRequest(map[string]interface{}{"mode": "remote", "stopOnEntry": true}) diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index a362ce5282..464a99736d 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -985,9 +985,6 @@ func Test1NegativeStackDepthBug(t *testing.T) { } func Test1ClientServer_CondBreakpoint(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("test is not valid on FreeBSD") - } withTestClient1("parallel_next", t, func(c *rpc1.RPCClient) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) assertNoError(err, t, "CreateBreakpoint()") diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 69c81ef585..a66d8dd132 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -1496,9 +1496,6 @@ func TestNegativeStackDepthBug(t *testing.T) { } func TestClientServer_CondBreakpoint(t *testing.T) { - if runtime.GOOS == "freebsd" { - t.Skip("test is not valid on FreeBSD") - } protest.AllowRecording(t) withTestClient2("parallel_next", t, func(c service.Client) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: 1}) From 983624c484035519026e1d478129ec3ab0ecfa4a Mon Sep 17 00:00:00 2001 From: Andrei Matei Date: Tue, 20 Dec 2022 13:06:54 -0500 Subject: [PATCH 29/72] service/debugger: fix comment on EvalVariableInScope (#3225) --- service/debugger/debugger.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index f86de29e16..4eef46f441 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -1561,8 +1561,8 @@ func (d *Debugger) Function(goid int64, frame, deferredCall int, cfg proc.LoadCo return s.Fn, nil } -// EvalVariableInScope will attempt to evaluate the variable represented by 'symbol' -// in the scope provided. +// EvalVariableInScope will attempt to evaluate the 'expr' in the scope +// corresponding to the given 'frame' on the goroutine identified by 'goid'. func (d *Debugger) EvalVariableInScope(goid int64, frame, deferredCall int, expr string, cfg proc.LoadConfig) (*proc.Variable, error) { d.targetMutex.Lock() defer d.targetMutex.Unlock() From a6e3d144553682f83b8acd6b9b852378ebfed1a5 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Wed, 28 Dec 2022 13:41:13 +0200 Subject: [PATCH 30/72] proc: Fix typos in the comments (#3231) --- pkg/proc/bininfo.go | 6 +++--- pkg/proc/core/minidump/minidump.go | 4 ++-- pkg/proc/dump.go | 2 +- pkg/proc/fncall.go | 2 +- pkg/proc/gdbserial/gdbserver.go | 6 +++--- pkg/proc/gdbserial/gdbserver_conn.go | 2 +- pkg/proc/native/proc_linux.go | 2 +- pkg/proc/native/threads_windows.go | 2 +- pkg/proc/stack.go | 2 +- pkg/proc/target.go | 2 +- pkg/proc/target_group.go | 2 +- pkg/proc/types.go | 2 +- pkg/proc/variables.go | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index e80221c947..b2fb0c0eea 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1312,7 +1312,7 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn } if debugFilePath == "" && len(bi.BuildID) > 2 { - // Previous verrsions of delve looked for the build id in every debug info + // Previous versions of delve looked for the build id in every debug info // directory that contained the build-id substring. This behavior deviates // from the ones specified by GDB but we keep it for backwards compatibility. find(func(dir string) bool { return strings.Contains(dir, "build-id") }, fmt.Sprintf("%s/%s.debug", bi.BuildID[:2], bi.BuildID[2:])) @@ -1543,7 +1543,7 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync. // emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen // offset in libc's TLS block. // - On ARM64 (but really, any architecture other than i386 and 86x64) the - // offset is calculate using runtime.tls_g and the formula is different. + // offset is calculated using runtime.tls_g and the formula is different. var tls *elf.Prog for _, prog := range exe.Progs { @@ -2153,7 +2153,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB } } -// LookupGenericFunc returns a map that allows searching for instantiations of generic function by specificying a function name without type parameters. +// LookupGenericFunc returns a map that allows searching for instantiations of generic function by specifying a function name without type parameters. // For example the key "pkg.(*Receiver).Amethod" will find all instantiations of Amethod: // - pkg.(*Receiver[.shape.int]).Amethod // - pkg.(*Receiver[.shape.*uint8]).Amethod diff --git a/pkg/proc/core/minidump/minidump.go b/pkg/proc/core/minidump/minidump.go index 071470a47c..e0e3e5ed34 100644 --- a/pkg/proc/core/minidump/minidump.go +++ b/pkg/proc/core/minidump/minidump.go @@ -193,7 +193,7 @@ func (m *MemoryRange) ReadMemory(buf []byte, addr uint64) (int, error) { return len(buf), nil } -// MemoryInfo reprents an entry in the MemoryInfoList stream. +// MemoryInfo represents an entry in the MemoryInfoList stream. // See: https://docs.microsoft.com/en-us/windows/win32/api/minidumpapiset/ns-minidumpapiset-minidump_memory_info_list type MemoryInfo struct { Addr uint64 @@ -473,7 +473,7 @@ func readMinidumpHeader(mdmp *Minidump, buf *minidumpBuf) { mdmp.Flags = FileFlags(buf.u64()) } -// readDirectory reads the list of streams (i.e. the minidum "directory") +// readDirectory reads the list of streams (i.e. the minidump "directory") func readDirectory(mdmp *Minidump, buf *minidumpBuf) { buf.off = int(mdmp.streamOff) diff --git a/pkg/proc/dump.go b/pkg/proc/dump.go index ee693d6194..bfa29cc9b5 100644 --- a/pkg/proc/dump.go +++ b/pkg/proc/dump.go @@ -242,7 +242,7 @@ func (t *Target) dumpThreadNotes(notes []elfwriter.Note, state *DumpState, th Th // - register_name_len (2 bytes) // - register_name (register_name_len bytes) // - register_data_len (2 bytes) - // - register_data (regiter_data_len bytes) + // - register_data (register_data_len bytes) buf := new(bytes.Buffer) _ = binary.Write(buf, binary.LittleEndian, uint64(th.ThreadID())) diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 8f00ab6bf7..5f378a00c9 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -615,7 +615,7 @@ func funcCallCopyOneArg(scope *EvalScope, fncall *functionCallState, actualArg * } } - //TODO(aarzilli): autmoatic wrapping in interfaces for cases not handled + //TODO(aarzilli): automatic wrapping in interfaces for cases not handled // by convertToEface. var formalArgVar *Variable diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 6049a8e8b8..c24c0d33b4 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -1015,7 +1015,7 @@ func (p *gdbProcess) handleThreadSignals(cctx *proc.ContinueOnceContext, trapthr if p.getCtrlC(cctx) || cctx.GetManualStopRequested() { // If we request an interrupt and a target thread simultaneously receives - // an unrelated singal debugserver will discard our interrupt request and + // an unrelated signal debugserver will discard our interrupt request and // report the signal but we should stop anyway. shouldStop = true } @@ -1891,7 +1891,7 @@ func (t *gdbThread) clearBreakpointState() { // SetCurrentBreakpoint will find and set the threads current breakpoint. func (t *gdbThread) SetCurrentBreakpoint(adjustPC bool) error { - // adjustPC is ignored, it is the stub's responsibiility to set the PC + // adjustPC is ignored, it is the stub's responsibility to set the PC // address correctly after hitting a breakpoint. t.CurrentBreakpoint.Clear() if t.watchAddr > 0 { @@ -1967,7 +1967,7 @@ func (r *gdbRegisters) FloatLoadError() error { // SetPC will set the value of the PC register to the given value. func (t *gdbThread) setPC(pc uint64) error { - _, _ = t.Registers() // Registes must be loaded first + _, _ = t.Registers() // Registers must be loaded first t.regs.setPC(pc) if t.p.gcmdok { return t.p.conn.writeRegisters(t.strID, t.regs.buf) diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index da89664e5f..0449ed1475 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -81,7 +81,7 @@ func isProtocolErrorUnsupported(err error) bool { return gdberr.code == "" } -// GdbMalformedThreadIDError is returned when a the stub responds with a +// GdbMalformedThreadIDError is returned when the stub responds with a // thread ID that does not conform with the Gdb Remote Serial Protocol // specification. type GdbMalformedThreadIDError struct { diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index cfcc2878b1..29a08b506c 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -459,7 +459,7 @@ func status(pid int, comm string) rune { state rune ) - // The second field of /proc/pid/stat is the name of the task in parenthesis. + // The second field of /proc/pid/stat is the name of the task in parentheses. // The name of the task is the base name of the executable for this process limited to TASK_COMM_LEN characters // Since both parenthesis and spaces can appear inside the name of the task and no escaping happens we need to read the name of the executable first // See: include/linux/sched.c:315 and include/linux/sched.c:1510 diff --git a/pkg/proc/native/threads_windows.go b/pkg/proc/native/threads_windows.go index 8bcd6f6192..7fc45ea93d 100644 --- a/pkg/proc/native/threads_windows.go +++ b/pkg/proc/native/threads_windows.go @@ -46,7 +46,7 @@ func (t *nativeThread) singleStep() error { // runtime it will have a suspend count greater than 1 and to actually take // a single step we have to resume it multiple times here. // We keep a counter of how many times it was suspended so that after - // single-stepping we can re-suspend it the corrent number of times. + // single-stepping we can re-suspend it the correct number of times. for { n, err := _ResumeThread(t.os.hThread) if err != nil { diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index 1c52494d62..3ad7f2c4e9 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -623,7 +623,7 @@ func (d *Defer) load() { } // errSPDecreased is used when (*Defer).Next detects a corrupted linked -// list, specifically when after followin a link pointer the value of SP +// list, specifically when after following a link pointer the value of SP // decreases rather than increasing or staying the same (the defer list is a // FIFO list, nodes further down the list have been added by function calls // further down the call stack and therefore the SP should always increase). diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 35d4c23f48..5646f01338 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -339,7 +339,7 @@ func (p *Target) SwitchThread(tid int) error { return fmt.Errorf("thread %d does not exist", tid) } -// detach will detach the target from the underylying process. +// detach will detach the target from the underlying process. // This means the debugger will no longer receive events from the process // we were previously debugging. // If kill is true then the process will be killed when we detach. diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go index be608f3043..a1f6acb219 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -6,7 +6,7 @@ import ( "strings" ) -// TargetGroup reperesents a group of target processes being debugged that +// TargetGroup represents a group of target processes being debugged that // will be resumed and stopped simultaneously. // New targets are automatically added to the group if exec catching is // enabled and the backend supports it, otherwise the group will always diff --git a/pkg/proc/types.go b/pkg/proc/types.go index e9c72f0c46..88a4e2612d 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -97,7 +97,7 @@ func (ctxt *loadDebugInfoMapsContext) lookupAbstractOrigin(bi *BinaryInfo, off d // the name of the type, and then the type's name is used to look up // debug_info // - After go1.11 the runtimeTypeToDIE map is used to look up the address of -// the type and map it drectly to a DIE. +// the type and map it directly to a DIE. func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind int64, err error) { bi := _type.bi diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 45b946bc0a..bef2287d82 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -1392,7 +1392,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { // convertToEface converts srcv into an "interface {}" and writes it to // dstv. -// Dstv must be a variable of type "inteface {}" and srcv must either be an +// Dstv must be a variable of type "interface {}" and srcv must either be an // interface or a pointer shaped variable (map, channel, pointer or struct // containing a single pointer) func convertToEface(srcv, dstv *Variable) error { From bbc22707d087c071124f9149bd0f8c75080139f5 Mon Sep 17 00:00:00 2001 From: Hyang-Ah Hana Kim Date: Tue, 3 Jan 2023 11:13:28 -0500 Subject: [PATCH 31/72] pkg/proc: apply simplifycompositelit analysis fixes (#3236) https://pkg.go.dev/golang.org/x/tools/internal/lsp/analysis/simplifycompositelit --- pkg/proc/amd64_disasm.go | 136 ++++++++++++++++++------------------- pkg/proc/bininfo.go | 2 +- pkg/proc/i386_arch.go | 6 +- pkg/proc/i386_disasm.go | 56 +++++++-------- pkg/proc/proc_test.go | 18 ++--- pkg/proc/target_exec.go | 2 +- pkg/proc/variables_test.go | 4 +- 7 files changed, 112 insertions(+), 112 deletions(-) diff --git a/pkg/proc/amd64_disasm.go b/pkg/proc/amd64_disasm.go index be855e19c8..a27366bb91 100644 --- a/pkg/proc/amd64_disasm.go +++ b/pkg/proc/amd64_disasm.go @@ -42,78 +42,78 @@ func init() { var amd64AsmRegisters = map[int]asmRegister{ // 8-bit - int(x86asm.AL): asmRegister{regnum.AMD64_Rax, 0, mask8}, - int(x86asm.CL): asmRegister{regnum.AMD64_Rcx, 0, mask8}, - int(x86asm.DL): asmRegister{regnum.AMD64_Rdx, 0, mask8}, - int(x86asm.BL): asmRegister{regnum.AMD64_Rbx, 0, mask8}, - int(x86asm.AH): asmRegister{regnum.AMD64_Rax, 8, mask8}, - int(x86asm.CH): asmRegister{regnum.AMD64_Rcx, 8, mask8}, - int(x86asm.DH): asmRegister{regnum.AMD64_Rdx, 8, mask8}, - int(x86asm.BH): asmRegister{regnum.AMD64_Rbx, 8, mask8}, - int(x86asm.SPB): asmRegister{regnum.AMD64_Rsp, 0, mask8}, - int(x86asm.BPB): asmRegister{regnum.AMD64_Rbp, 0, mask8}, - int(x86asm.SIB): asmRegister{regnum.AMD64_Rsi, 0, mask8}, - int(x86asm.DIB): asmRegister{regnum.AMD64_Rdi, 0, mask8}, - int(x86asm.R8B): asmRegister{regnum.AMD64_R8, 0, mask8}, - int(x86asm.R9B): asmRegister{regnum.AMD64_R9, 0, mask8}, - int(x86asm.R10B): asmRegister{regnum.AMD64_R10, 0, mask8}, - int(x86asm.R11B): asmRegister{regnum.AMD64_R11, 0, mask8}, - int(x86asm.R12B): asmRegister{regnum.AMD64_R12, 0, mask8}, - int(x86asm.R13B): asmRegister{regnum.AMD64_R13, 0, mask8}, - int(x86asm.R14B): asmRegister{regnum.AMD64_R14, 0, mask8}, - int(x86asm.R15B): asmRegister{regnum.AMD64_R15, 0, mask8}, + int(x86asm.AL): {regnum.AMD64_Rax, 0, mask8}, + int(x86asm.CL): {regnum.AMD64_Rcx, 0, mask8}, + int(x86asm.DL): {regnum.AMD64_Rdx, 0, mask8}, + int(x86asm.BL): {regnum.AMD64_Rbx, 0, mask8}, + int(x86asm.AH): {regnum.AMD64_Rax, 8, mask8}, + int(x86asm.CH): {regnum.AMD64_Rcx, 8, mask8}, + int(x86asm.DH): {regnum.AMD64_Rdx, 8, mask8}, + int(x86asm.BH): {regnum.AMD64_Rbx, 8, mask8}, + int(x86asm.SPB): {regnum.AMD64_Rsp, 0, mask8}, + int(x86asm.BPB): {regnum.AMD64_Rbp, 0, mask8}, + int(x86asm.SIB): {regnum.AMD64_Rsi, 0, mask8}, + int(x86asm.DIB): {regnum.AMD64_Rdi, 0, mask8}, + int(x86asm.R8B): {regnum.AMD64_R8, 0, mask8}, + int(x86asm.R9B): {regnum.AMD64_R9, 0, mask8}, + int(x86asm.R10B): {regnum.AMD64_R10, 0, mask8}, + int(x86asm.R11B): {regnum.AMD64_R11, 0, mask8}, + int(x86asm.R12B): {regnum.AMD64_R12, 0, mask8}, + int(x86asm.R13B): {regnum.AMD64_R13, 0, mask8}, + int(x86asm.R14B): {regnum.AMD64_R14, 0, mask8}, + int(x86asm.R15B): {regnum.AMD64_R15, 0, mask8}, // 16-bit - int(x86asm.AX): asmRegister{regnum.AMD64_Rax, 0, mask16}, - int(x86asm.CX): asmRegister{regnum.AMD64_Rcx, 0, mask16}, - int(x86asm.DX): asmRegister{regnum.AMD64_Rdx, 0, mask16}, - int(x86asm.BX): asmRegister{regnum.AMD64_Rbx, 0, mask16}, - int(x86asm.SP): asmRegister{regnum.AMD64_Rsp, 0, mask16}, - int(x86asm.BP): asmRegister{regnum.AMD64_Rbp, 0, mask16}, - int(x86asm.SI): asmRegister{regnum.AMD64_Rsi, 0, mask16}, - int(x86asm.DI): asmRegister{regnum.AMD64_Rdi, 0, mask16}, - int(x86asm.R8W): asmRegister{regnum.AMD64_R8, 0, mask16}, - int(x86asm.R9W): asmRegister{regnum.AMD64_R9, 0, mask16}, - int(x86asm.R10W): asmRegister{regnum.AMD64_R10, 0, mask16}, - int(x86asm.R11W): asmRegister{regnum.AMD64_R11, 0, mask16}, - int(x86asm.R12W): asmRegister{regnum.AMD64_R12, 0, mask16}, - int(x86asm.R13W): asmRegister{regnum.AMD64_R13, 0, mask16}, - int(x86asm.R14W): asmRegister{regnum.AMD64_R14, 0, mask16}, - int(x86asm.R15W): asmRegister{regnum.AMD64_R15, 0, mask16}, + int(x86asm.AX): {regnum.AMD64_Rax, 0, mask16}, + int(x86asm.CX): {regnum.AMD64_Rcx, 0, mask16}, + int(x86asm.DX): {regnum.AMD64_Rdx, 0, mask16}, + int(x86asm.BX): {regnum.AMD64_Rbx, 0, mask16}, + int(x86asm.SP): {regnum.AMD64_Rsp, 0, mask16}, + int(x86asm.BP): {regnum.AMD64_Rbp, 0, mask16}, + int(x86asm.SI): {regnum.AMD64_Rsi, 0, mask16}, + int(x86asm.DI): {regnum.AMD64_Rdi, 0, mask16}, + int(x86asm.R8W): {regnum.AMD64_R8, 0, mask16}, + int(x86asm.R9W): {regnum.AMD64_R9, 0, mask16}, + int(x86asm.R10W): {regnum.AMD64_R10, 0, mask16}, + int(x86asm.R11W): {regnum.AMD64_R11, 0, mask16}, + int(x86asm.R12W): {regnum.AMD64_R12, 0, mask16}, + int(x86asm.R13W): {regnum.AMD64_R13, 0, mask16}, + int(x86asm.R14W): {regnum.AMD64_R14, 0, mask16}, + int(x86asm.R15W): {regnum.AMD64_R15, 0, mask16}, // 32-bit - int(x86asm.EAX): asmRegister{regnum.AMD64_Rax, 0, mask32}, - int(x86asm.ECX): asmRegister{regnum.AMD64_Rcx, 0, mask32}, - int(x86asm.EDX): asmRegister{regnum.AMD64_Rdx, 0, mask32}, - int(x86asm.EBX): asmRegister{regnum.AMD64_Rbx, 0, mask32}, - int(x86asm.ESP): asmRegister{regnum.AMD64_Rsp, 0, mask32}, - int(x86asm.EBP): asmRegister{regnum.AMD64_Rbp, 0, mask32}, - int(x86asm.ESI): asmRegister{regnum.AMD64_Rsi, 0, mask32}, - int(x86asm.EDI): asmRegister{regnum.AMD64_Rdi, 0, mask32}, - int(x86asm.R8L): asmRegister{regnum.AMD64_R8, 0, mask32}, - int(x86asm.R9L): asmRegister{regnum.AMD64_R9, 0, mask32}, - int(x86asm.R10L): asmRegister{regnum.AMD64_R10, 0, mask32}, - int(x86asm.R11L): asmRegister{regnum.AMD64_R11, 0, mask32}, - int(x86asm.R12L): asmRegister{regnum.AMD64_R12, 0, mask32}, - int(x86asm.R13L): asmRegister{regnum.AMD64_R13, 0, mask32}, - int(x86asm.R14L): asmRegister{regnum.AMD64_R14, 0, mask32}, - int(x86asm.R15L): asmRegister{regnum.AMD64_R15, 0, mask32}, + int(x86asm.EAX): {regnum.AMD64_Rax, 0, mask32}, + int(x86asm.ECX): {regnum.AMD64_Rcx, 0, mask32}, + int(x86asm.EDX): {regnum.AMD64_Rdx, 0, mask32}, + int(x86asm.EBX): {regnum.AMD64_Rbx, 0, mask32}, + int(x86asm.ESP): {regnum.AMD64_Rsp, 0, mask32}, + int(x86asm.EBP): {regnum.AMD64_Rbp, 0, mask32}, + int(x86asm.ESI): {regnum.AMD64_Rsi, 0, mask32}, + int(x86asm.EDI): {regnum.AMD64_Rdi, 0, mask32}, + int(x86asm.R8L): {regnum.AMD64_R8, 0, mask32}, + int(x86asm.R9L): {regnum.AMD64_R9, 0, mask32}, + int(x86asm.R10L): {regnum.AMD64_R10, 0, mask32}, + int(x86asm.R11L): {regnum.AMD64_R11, 0, mask32}, + int(x86asm.R12L): {regnum.AMD64_R12, 0, mask32}, + int(x86asm.R13L): {regnum.AMD64_R13, 0, mask32}, + int(x86asm.R14L): {regnum.AMD64_R14, 0, mask32}, + int(x86asm.R15L): {regnum.AMD64_R15, 0, mask32}, // 64-bit - int(x86asm.RAX): asmRegister{regnum.AMD64_Rax, 0, 0}, - int(x86asm.RCX): asmRegister{regnum.AMD64_Rcx, 0, 0}, - int(x86asm.RDX): asmRegister{regnum.AMD64_Rdx, 0, 0}, - int(x86asm.RBX): asmRegister{regnum.AMD64_Rbx, 0, 0}, - int(x86asm.RSP): asmRegister{regnum.AMD64_Rsp, 0, 0}, - int(x86asm.RBP): asmRegister{regnum.AMD64_Rbp, 0, 0}, - int(x86asm.RSI): asmRegister{regnum.AMD64_Rsi, 0, 0}, - int(x86asm.RDI): asmRegister{regnum.AMD64_Rdi, 0, 0}, - int(x86asm.R8): asmRegister{regnum.AMD64_R8, 0, 0}, - int(x86asm.R9): asmRegister{regnum.AMD64_R9, 0, 0}, - int(x86asm.R10): asmRegister{regnum.AMD64_R10, 0, 0}, - int(x86asm.R11): asmRegister{regnum.AMD64_R11, 0, 0}, - int(x86asm.R12): asmRegister{regnum.AMD64_R12, 0, 0}, - int(x86asm.R13): asmRegister{regnum.AMD64_R13, 0, 0}, - int(x86asm.R14): asmRegister{regnum.AMD64_R14, 0, 0}, - int(x86asm.R15): asmRegister{regnum.AMD64_R15, 0, 0}, + int(x86asm.RAX): {regnum.AMD64_Rax, 0, 0}, + int(x86asm.RCX): {regnum.AMD64_Rcx, 0, 0}, + int(x86asm.RDX): {regnum.AMD64_Rdx, 0, 0}, + int(x86asm.RBX): {regnum.AMD64_Rbx, 0, 0}, + int(x86asm.RSP): {regnum.AMD64_Rsp, 0, 0}, + int(x86asm.RBP): {regnum.AMD64_Rbp, 0, 0}, + int(x86asm.RSI): {regnum.AMD64_Rsi, 0, 0}, + int(x86asm.RDI): {regnum.AMD64_Rdi, 0, 0}, + int(x86asm.R8): {regnum.AMD64_R8, 0, 0}, + int(x86asm.R9): {regnum.AMD64_R9, 0, 0}, + int(x86asm.R10): {regnum.AMD64_R10, 0, 0}, + int(x86asm.R11): {regnum.AMD64_R11, 0, 0}, + int(x86asm.R12): {regnum.AMD64_R12, 0, 0}, + int(x86asm.R13): {regnum.AMD64_R13, 0, 0}, + int(x86asm.R14): {regnum.AMD64_R14, 0, 0}, + int(x86asm.R15): {regnum.AMD64_R15, 0, 0}, } diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index b2fb0c0eea..c96cd9643d 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1056,7 +1056,7 @@ func (bi *BinaryInfo) LocationCovers(entry *dwarf.Entry, attr dwarf.Attr) ([][2] return nil, fmt.Errorf("attribute %s not found", attr) } if _, isblock := a.([]byte); isblock { - return [][2]uint64{[2]uint64{0, ^uint64(0)}}, nil + return [][2]uint64{{0, ^uint64(0)}}, nil } off, ok := a.(int64) diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go index 21059890db..50b9d41c77 100644 --- a/pkg/proc/i386_arch.go +++ b/pkg/proc/i386_arch.go @@ -68,15 +68,15 @@ func i386FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryI return &frame.FrameContext{ RetAddrReg: regnum.I386_Eip, Regs: map[uint64]frame.DWRule{ - regnum.I386_Eip: frame.DWRule{ + regnum.I386_Eip: { Rule: frame.RuleOffset, Offset: int64(-i.PtrSize()), }, - regnum.I386_Ebp: frame.DWRule{ + regnum.I386_Ebp: { Rule: frame.RuleOffset, Offset: int64(-2 * i.PtrSize()), }, - regnum.I386_Esp: frame.DWRule{ + regnum.I386_Esp: { Rule: frame.RuleValOffset, Offset: 0, }, diff --git a/pkg/proc/i386_disasm.go b/pkg/proc/i386_disasm.go index b176227841..bebcb9f777 100644 --- a/pkg/proc/i386_disasm.go +++ b/pkg/proc/i386_disasm.go @@ -43,36 +43,36 @@ func init() { var i386AsmRegisters = map[int]asmRegister{ // 8-bit - int(x86asm.AL): asmRegister{regnum.I386_Eax, 0, mask8}, - int(x86asm.CL): asmRegister{regnum.I386_Ecx, 0, mask8}, - int(x86asm.DL): asmRegister{regnum.I386_Edx, 0, mask8}, - int(x86asm.BL): asmRegister{regnum.I386_Ebx, 0, mask8}, - int(x86asm.AH): asmRegister{regnum.I386_Eax, 8, mask8}, - int(x86asm.CH): asmRegister{regnum.I386_Ecx, 8, mask8}, - int(x86asm.DH): asmRegister{regnum.I386_Edx, 8, mask8}, - int(x86asm.BH): asmRegister{regnum.I386_Ebx, 8, mask8}, - int(x86asm.SPB): asmRegister{regnum.I386_Esp, 0, mask8}, - int(x86asm.BPB): asmRegister{regnum.I386_Ebp, 0, mask8}, - int(x86asm.SIB): asmRegister{regnum.I386_Esi, 0, mask8}, - int(x86asm.DIB): asmRegister{regnum.I386_Edi, 0, mask8}, + int(x86asm.AL): {regnum.I386_Eax, 0, mask8}, + int(x86asm.CL): {regnum.I386_Ecx, 0, mask8}, + int(x86asm.DL): {regnum.I386_Edx, 0, mask8}, + int(x86asm.BL): {regnum.I386_Ebx, 0, mask8}, + int(x86asm.AH): {regnum.I386_Eax, 8, mask8}, + int(x86asm.CH): {regnum.I386_Ecx, 8, mask8}, + int(x86asm.DH): {regnum.I386_Edx, 8, mask8}, + int(x86asm.BH): {regnum.I386_Ebx, 8, mask8}, + int(x86asm.SPB): {regnum.I386_Esp, 0, mask8}, + int(x86asm.BPB): {regnum.I386_Ebp, 0, mask8}, + int(x86asm.SIB): {regnum.I386_Esi, 0, mask8}, + int(x86asm.DIB): {regnum.I386_Edi, 0, mask8}, // 16-bit - int(x86asm.AX): asmRegister{regnum.I386_Eax, 0, mask16}, - int(x86asm.CX): asmRegister{regnum.I386_Ecx, 0, mask16}, - int(x86asm.DX): asmRegister{regnum.I386_Edx, 0, mask16}, - int(x86asm.BX): asmRegister{regnum.I386_Ebx, 0, mask16}, - int(x86asm.SP): asmRegister{regnum.I386_Esp, 0, mask16}, - int(x86asm.BP): asmRegister{regnum.I386_Ebp, 0, mask16}, - int(x86asm.SI): asmRegister{regnum.I386_Esi, 0, mask16}, - int(x86asm.DI): asmRegister{regnum.I386_Edi, 0, mask16}, + int(x86asm.AX): {regnum.I386_Eax, 0, mask16}, + int(x86asm.CX): {regnum.I386_Ecx, 0, mask16}, + int(x86asm.DX): {regnum.I386_Edx, 0, mask16}, + int(x86asm.BX): {regnum.I386_Ebx, 0, mask16}, + int(x86asm.SP): {regnum.I386_Esp, 0, mask16}, + int(x86asm.BP): {regnum.I386_Ebp, 0, mask16}, + int(x86asm.SI): {regnum.I386_Esi, 0, mask16}, + int(x86asm.DI): {regnum.I386_Edi, 0, mask16}, // 32-bit - int(x86asm.EAX): asmRegister{regnum.I386_Eax, 0, mask32}, - int(x86asm.ECX): asmRegister{regnum.I386_Ecx, 0, mask32}, - int(x86asm.EDX): asmRegister{regnum.I386_Edx, 0, mask32}, - int(x86asm.EBX): asmRegister{regnum.I386_Ebx, 0, mask32}, - int(x86asm.ESP): asmRegister{regnum.I386_Esp, 0, mask32}, - int(x86asm.EBP): asmRegister{regnum.I386_Ebp, 0, mask32}, - int(x86asm.ESI): asmRegister{regnum.I386_Esi, 0, mask32}, - int(x86asm.EDI): asmRegister{regnum.I386_Edi, 0, mask32}, + int(x86asm.EAX): {regnum.I386_Eax, 0, mask32}, + int(x86asm.ECX): {regnum.I386_Ecx, 0, mask32}, + int(x86asm.EDX): {regnum.I386_Edx, 0, mask32}, + int(x86asm.EBX): {regnum.I386_Ebx, 0, mask32}, + int(x86asm.ESP): {regnum.I386_Esp, 0, mask32}, + int(x86asm.EBP): {regnum.I386_Ebp, 0, mask32}, + int(x86asm.ESI): {regnum.I386_Esi, 0, mask32}, + int(x86asm.EDI): {regnum.I386_Edi, 0, mask32}, } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 8ee2d8aed1..5ee67eee30 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -3429,11 +3429,11 @@ func TestCgoStacktrace(t *testing.T) { // frame than those listed here but all the frames listed must appear in // the specified order. testCases := [][]string{ - []string{"main.main"}, - []string{"C.helloworld_pt2", "C.helloworld", "main.main"}, - []string{"main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}, - []string{"C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}, - []string{"main.helloWorld2", "C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}} + {"main.main"}, + {"C.helloworld_pt2", "C.helloworld", "main.main"}, + {"main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}, + {"C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}, + {"main.helloWorld2", "C.helloworld_pt4", "C.helloworld_pt3", "main.helloWorldS", "main.helloWorld", "C.helloworld_pt2", "C.helloworld", "main.main"}} var gid int64 @@ -3868,14 +3868,14 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { line: 7, ok: false, varChecks: []varCheck{ - varCheck{ + { name: "a", typ: "int", kind: reflect.Int, hasVal: true, intVal: 3, }, - varCheck{ + { name: "z", typ: "int", kind: reflect.Int, @@ -3889,14 +3889,14 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { line: 7, ok: false, varChecks: []varCheck{ - varCheck{ + { name: "a", typ: "int", kind: reflect.Int, hasVal: true, intVal: 4, }, - varCheck{ + { name: "z", typ: "int", kind: reflect.Int, diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 1c894e8c57..0e5b68fbb3 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -1137,7 +1137,7 @@ func (tgt *Target) handleHardcodedBreakpoints(trapthread Thread, threads []Threa hcbp.Addr = loc.PC hcbp.Logical = &LogicalBreakpoint{} hcbp.Logical.Name = HardcodedBreakpoint - hcbp.Breaklets = []*Breaklet{&Breaklet{Kind: UserBreakpoint, LogicalID: hardcodedBreakpointID}} + hcbp.Breaklets = []*Breaklet{{Kind: UserBreakpoint, LogicalID: hardcodedBreakpointID}} tgt.StopReason = StopHardcodedBreakpoint } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 22c350375e..a46826ea4e 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -1576,14 +1576,14 @@ func TestEvalExpressionGenerics(t *testing.T) { testcases := [][]varTest{ // testfn[int, float32] - []varTest{ + { {"arg1", true, "3", "", "int", nil}, {"arg2", true, "2.1", "", "float32", nil}, {"m", true, "map[float32]int [2.1: 3, ]", "", "map[float32]int", nil}, }, // testfn[*astruct, astruct] - []varTest{ + { {"arg1", true, "*main.astruct {x: 0, y: 1}", "", "*main.astruct", nil}, {"arg2", true, "main.astruct {x: 2, y: 3}", "", "main.astruct", nil}, {"m", true, "map[main.astruct]*main.astruct [{x: 2, y: 3}: *{x: 0, y: 1}, ]", "", "map[main.astruct]*main.astruct", nil}, From 9f731debb1b22cc51790780ef0ccb4c1a895762b Mon Sep 17 00:00:00 2001 From: Hyang-Ah Hana Kim Date: Tue, 3 Jan 2023 11:40:51 -0500 Subject: [PATCH 32/72] go.mod: update github.com/google/go-dap to v0.7.0 (#3237) * go.mod: update github.com/google/go-dap to v0.7.0 Updates https://github.com/google/go-dap/issues/66 * Run go mod tidy --- go.mod | 3 +-- go.sum | 24 ++----------------- service/dap/server.go | 2 +- .../github.com/google/go-dap/schematypes.go | 20 ++++++++-------- vendor/modules.txt | 2 +- 5 files changed, 15 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index 2c5cd476b1..2be8c4d5b5 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/creack/pty v1.1.9 github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4 github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d - github.com/google/go-dap v0.6.0 + github.com/google/go-dap v0.7.0 github.com/hashicorp/golang-lru v0.5.4 github.com/mattn/go-colorable v0.0.9 github.com/mattn/go-isatty v0.0.3 @@ -20,6 +20,5 @@ require ( golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4 golang.org/x/sys v0.0.0-20220908164124-27713097b956 golang.org/x/tools v0.1.12 - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index e3b8ac71aa..af05495ef7 100644 --- a/go.sum +++ b/go.sum @@ -26,7 +26,6 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k= @@ -46,8 +45,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9 h1:G765iDCq7bP5opdrPkXk+4V3yfkgV9iGFuheWZ/X/zY= -github.com/derekparker/trie v0.0.0-20200317170641-1fdf38b7b0e9/go.mod h1:D6ICZm05D9VN1n/8iOtBxLpXtoGp6HDFUJ1RNVieOSE= github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4 h1:atN94qKNhLpy+9BwbE5nxvFj4rScJi6W3x/NfFmMDg4= github.com/derekparker/trie v0.0.0-20221213183930-4c74548207f4/go.mod h1:C7Es+DLenIpPc9J6IYw4jrK0h7S9bKj4DNl8+KxGEXU= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -92,8 +89,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-dap v0.6.0 h1:Y1RHGUtv3R8y6sXq2dtGRMYrFB2hSqyFVws7jucrzX4= -github.com/google/go-dap v0.6.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= +github.com/google/go-dap v0.7.0 h1:088PdKBUkxAxrXrnY8FREUJXpS6Y6jhAyZIuJv3OGOM= +github.com/google/go-dap v0.7.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -220,14 +217,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.starlark.net v0.0.0-20200821142938-949cc6f4b097 h1:YiRMXXgG+Pg26t1fjq+iAjaauKWMC9cmGFrtOEuwDDg= -go.starlark.net v0.0.0-20200821142938-949cc6f4b097/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU= go.starlark.net v0.0.0-20220816155156-cfacd8902214 h1:MqijAN3S61c7KWasOk+zIqIjHQPN6WUra/X3+YAkQxQ= go.starlark.net v0.0.0-20220816155156-cfacd8902214/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -276,7 +269,6 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -286,7 +278,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -302,29 +293,21 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho= -golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -347,8 +330,6 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -386,7 +367,6 @@ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miE google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/service/dap/server.go b/service/dap/server.go index 07300295a0..3c953ddf72 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -1411,7 +1411,7 @@ func (s *Session) updateBreakpointsResponse(breakpoints []dap.Breakpoint, i int, path := s.toClientPath(got.File) breakpoints[i].Id = got.ID breakpoints[i].Line = got.Line - breakpoints[i].Source = dap.Source{Name: filepath.Base(path), Path: path} + breakpoints[i].Source = &dap.Source{Name: filepath.Base(path), Path: path} } } diff --git a/vendor/github.com/google/go-dap/schematypes.go b/vendor/github.com/google/go-dap/schematypes.go index 1d93058f24..3fd2770ce1 100644 --- a/vendor/github.com/google/go-dap/schematypes.go +++ b/vendor/github.com/google/go-dap/schematypes.go @@ -1827,16 +1827,16 @@ type InstructionBreakpoint struct { // Breakpoint: Information about a Breakpoint created in setBreakpoints, setFunctionBreakpoints, setInstructionBreakpoints, or setDataBreakpoints. type Breakpoint struct { - Id int `json:"id,omitempty"` - Verified bool `json:"verified"` - Message string `json:"message,omitempty"` - Source Source `json:"source,omitempty"` - Line int `json:"line,omitempty"` - Column int `json:"column,omitempty"` - EndLine int `json:"endLine,omitempty"` - EndColumn int `json:"endColumn,omitempty"` - InstructionReference string `json:"instructionReference,omitempty"` - Offset int `json:"offset,omitempty"` + Id int `json:"id,omitempty"` + Verified bool `json:"verified"` + Message string `json:"message,omitempty"` + Source *Source `json:"source,omitempty"` + Line int `json:"line,omitempty"` + Column int `json:"column,omitempty"` + EndLine int `json:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty"` + InstructionReference string `json:"instructionReference,omitempty"` + Offset int `json:"offset,omitempty"` } // SteppingGranularity: The granularity of one 'step' in the stepping requests 'next', 'stepIn', 'stepOut', and 'stepBack'. diff --git a/vendor/modules.txt b/vendor/modules.txt index 145df09bdb..ac1d2a3ab4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -21,7 +21,7 @@ github.com/derekparker/trie # github.com/go-delve/liner v1.2.3-0.20220127212407-d32d89dd2a5d ## explicit github.com/go-delve/liner -# github.com/google/go-dap v0.6.0 +# github.com/google/go-dap v0.7.0 ## explicit github.com/google/go-dap # github.com/hashicorp/golang-lru v0.5.4 From 709da9a217bdd77942ef95004d8bc4ede205b55a Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 3 Jan 2023 18:23:26 +0100 Subject: [PATCH 33/72] Documentation: document how to debug runtime with Delve (#3234) Add FAQ entry to document how to debug the Go runtime using Delve. Also add table of contents to FAQ. --- Documentation/faq.md | 31 +++++++++-- _scripts/gen-faq-toc.go | 115 ++++++++++++++++++++++++++++++++++++++++ cmd/dlv/dlv_test.go | 1 + 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 _scripts/gen-faq-toc.go diff --git a/Documentation/faq.md b/Documentation/faq.md index d63543fc2c..8efc5463ac 100644 --- a/Documentation/faq.md +++ b/Documentation/faq.md @@ -1,6 +1,15 @@ ## Frequently Asked Questions -#### I'm getting an error while compiling Delve / unsupported architectures and OSs + +* [I'm getting an error while compiling Delve / unsupported architectures and OSs](#unsupportedplatforms) +* [How do I use Delve with Docker?](#docker) +* [How can I use Delve to debug a CLI application?](#ttydebug) +* [How can I use Delve for remote debugging?](#remote) +* [Can not set breakpoints or see source listing in a complicated debugging environment](#substpath) +* [Using Delve to debug the Go runtime](#runtime) + + +### I'm getting an error while compiling Delve / unsupported architectures and OSs The most likely cause of this is that you are running an unsupported Operating System or architecture. Currently Delve supports (GOOS / GOARCH): @@ -18,7 +27,7 @@ There is no planned ETA for support of other architectures or operating systems. See also: [backend test health](backend_test_health.md). -#### How do I use Delve with Docker? +### How do I use Delve with Docker? When running the container you should pass the `--security-opt=seccomp:unconfined` option to Docker. You can start a headless instance of Delve inside the container like this: @@ -40,7 +49,7 @@ dlv exec --headless --continue --listen :4040 --accept-multiclient /path/to/exec Note that the connection to Delve is unauthenticated and will allow arbitrary remote code execution: *do not do this in production*. -#### How can I use Delve to debug a CLI application? +### How can I use Delve to debug a CLI application? There are three good ways to go about this @@ -54,7 +63,7 @@ the terminal TTY. `dlv debug` and `dlv exec` commands. For the best experience, you should create your own PTY and assign it as the TTY. This can be done via [ptyme](https://github.com/derekparker/ptyme). -#### How can I use Delve for remote debugging? +### How can I use Delve for remote debugging? It is best not to use remote debugging on a public network. If you have to do this, we recommend using ssh tunnels or a vpn connection. @@ -77,7 +86,7 @@ ssh -NL 4040:localhost:4040 user@remote.ip dlv connect :4040 ``` -#### Can not set breakpoints or see source listing in a complicated debugging environment +### Can not set breakpoints or see source listing in a complicated debugging environment This problem manifests when one or more of these things happen: @@ -108,3 +117,15 @@ The substitute-path feature can be used to solve this problem, see `help config` The `sources` command could also be useful in troubleshooting this problem, it shows the list of file paths that has been embedded by the compiler into the executable. If you still think this is a bug in Delve and not a configuration problem, open an [issue](https://github.com/go-delve/delve/issues), filling the issue template and including the logs produced by delve with the options `--log --log-output=rpc,dap`. + +### Using Delve to debug the Go runtime + +It's possible to use Delve to debug the Go runtime, however there are some caveats to keep in mind + +* The `runtime` package is always compiled with optimizations and inlining, all of the caveats that apply to debugging optimized binaries apply to the runtime package. In particular some variables could be unavailable or have stale values and it could expose some bugs with the compiler assigning line numbers to instructions. + +* Next, step and stepout try to follow the current goroutine, if you debug one of the functions in the runtime that modify the curg pointer they will get confused. The 'step-instruction' command should be used instead. + +* When executing a stacktrace from g0 Delve will return the top frame and then immediately switch to the goroutine stack. If you want to see the g0 stacktrace use `stack -mode simple`. + +* The step command only steps into private runtime functions if it is already inside a runtime function. To step inside a private runtime function inserted into user code by the compiler set a breakpoint and then use `runtime.curg.goid == ` as condition. diff --git a/_scripts/gen-faq-toc.go b/_scripts/gen-faq-toc.go new file mode 100644 index 0000000000..6c008d32c1 --- /dev/null +++ b/_scripts/gen-faq-toc.go @@ -0,0 +1,115 @@ +package main + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" +) + +func must(err error, fmtstr string, args ...interface{}) { + if err != nil { + log.Fatalf(fmtstr, args...) + } +} + +func usage() { + os.Stderr.WriteString("gen-faq-to input-path output-path") + os.Exit(1) +} + +var anchor = regexp.MustCompile(`### (.*)`) + +type tocentry struct { + anchor, title string +} + +const ( + startOfToc = "" + endOfToc = "" +) + +func spliceDocs(docpath string, docs []byte, outpath string) { + docbuf, err := ioutil.ReadFile(docpath) + if err != nil { + log.Fatalf("could not read doc file: %v", err) + } + + v := strings.Split(string(docbuf), startOfToc) + if len(v) != 2 { + log.Fatal("could not find start of mapping table") + } + header := v[0] + v = strings.Split(v[1], endOfToc) + if len(v) != 2 { + log.Fatal("could not find end of mapping table") + } + footer := v[1] + + outbuf := bytes.NewBuffer(make([]byte, 0, len(header)+len(docs)+len(footer)+len(startOfToc)+len(endOfToc)+1)) + outbuf.Write([]byte(header)) + outbuf.Write([]byte(startOfToc)) + outbuf.WriteByte('\n') + outbuf.Write(docs) + outbuf.Write([]byte(endOfToc)) + outbuf.Write([]byte(footer)) + + if outpath != "-" { + err = ioutil.WriteFile(outpath, outbuf.Bytes(), 0664) + must(err, "could not write documentation file: %v", err) + } else { + os.Stdout.Write(outbuf.Bytes()) + } +} + +func readtoc(docpath string) []tocentry { + infh, err := os.Open(docpath) + must(err, "could not open %s: %v", docpath, err) + defer infh.Close() + scan := bufio.NewScanner(infh) + tocentries := []tocentry{} + seenAnchors := map[string]bool{} + for scan.Scan() { + line := scan.Text() + if !strings.HasPrefix(line, "### ") { + continue + } + m := anchor.FindStringSubmatch(line) + if len(m) != 3 { + log.Fatalf("entry %q does not have anchor", line) + } + if seenAnchors[m[1]] { + log.Fatalf("duplicate anchor %q", m[1]) + } + anchor, title := m[1], m[2] + seenAnchors[anchor] = true + tocentries = append(tocentries, tocentry{anchor, title}) + + } + must(scan.Err(), "could not read %s: %v", scan.Err()) + return tocentries +} + +func writetoc(tocentries []tocentry) []byte { + b := new(bytes.Buffer) + for _, tocentry := range tocentries { + fmt.Fprintf(b, "* [%s](#%s)\n", tocentry.title, tocentry.anchor) + } + return b.Bytes() +} + +func main() { + if len(os.Args) != 3 { + usage() + } + + docpath, outpath := os.Args[1], os.Args[2] + + tocentries := readtoc(docpath) + + spliceDocs(docpath, []byte(writetoc(tocentries)), outpath) +} diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 753dd3835e..57465acaec 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -468,6 +468,7 @@ func TestGeneratedDoc(t *testing.T) { checkAutogenDoc(t, "Documentation/backend_test_health.md", "go run _scripts/gen-backend_test_health.go", runScript("_scripts/gen-backend_test_health.go", "-")) checkAutogenDoc(t, "pkg/terminal/starbind/starlark_mapping.go", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "go", "-")) checkAutogenDoc(t, "Documentation/cli/starlark.md", "'go generate' inside pkg/terminal/starbind", runScript("_scripts/gen-starlark-bindings.go", "doc/dummy", "Documentation/cli/starlark.md")) + checkAutogenDoc(t, "Documentation/faq.md", "'go run _scripts/gen-faq-toc.go Documentation/faq.md Documentation/faq.md'", runScript("_scripts/gen-faq-toc.go", "Documentation/faq.md", "-")) if goversion.VersionAfterOrEqual(runtime.Version(), 1, 18) { checkAutogenDoc(t, "_scripts/rtype-out.txt", "go run _scripts/rtype.go report _scripts/rtype-out.txt", runScript("_scripts/rtype.go", "report")) runScript("_scripts/rtype.go", "check") From 9230a97210e60fbdcc421f9817e834885ae330c8 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Wed, 4 Jan 2023 01:22:19 -0800 Subject: [PATCH 34/72] pkg/terminal: fix handling list colors via config (#3240) Parsing the source list color when set during a Delve debug session resulted in unexpected errors. Additionally the changes were not reflected in the current session, forcing the user to save the config and start a new session. This patch fixes those issues. --- pkg/config/split.go | 21 ++++++++++++++++ pkg/terminal/config.go | 1 + pkg/terminal/terminal.go | 53 +++++++++++++++++++++++----------------- 3 files changed, 53 insertions(+), 22 deletions(-) diff --git a/pkg/config/split.go b/pkg/config/split.go index a60b07d3f1..94a216be15 100644 --- a/pkg/config/split.go +++ b/pkg/config/split.go @@ -84,7 +84,28 @@ func ConfigureSetSimple(rest string, cfgname string, field reflect.Value) error v := rest == "true" return reflect.ValueOf(&v), nil case reflect.String: + unquoted, err := strconv.Unquote(rest) + if err == nil { + rest = unquoted + } return reflect.ValueOf(&rest), nil + case reflect.Interface: + // We special case this particular configuration key because historically we accept both a numerical value and a string value for it. + if cfgname == "source-list-line-color" { + n, err := strconv.Atoi(rest) + if err == nil { + if n < 0 { + return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be a number greater than zero", cfgname) + } + return reflect.ValueOf(&n), nil + } + unquoted, err := strconv.Unquote(rest) + if err == nil { + rest = unquoted + } + return reflect.ValueOf(&rest), nil + } + fallthrough default: return reflect.ValueOf(nil), fmt.Errorf("unsupported type for configuration key %q", cfgname) } diff --git a/pkg/terminal/config.go b/pkg/terminal/config.go index a005c51aa6..9d7f43d287 100644 --- a/pkg/terminal/config.go +++ b/pkg/terminal/config.go @@ -25,6 +25,7 @@ func configureCmd(t *Term, ctx callContext, args string) error { if t.client != nil { // only happens in tests lcfg := t.loadConfig() t.client.SetReturnValuesLoadConfig(&lcfg) + t.updateColorScheme() } return nil } diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index e04b77add5..b4cde64c58 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -108,28 +108,7 @@ func New(client service.Client, conf *config.Config) *Term { t.stdout.pw = &pagingWriter{w: getColorableWriter()} t.stdout.colorEscapes = make(map[colorize.Style]string) t.stdout.colorEscapes[colorize.NormalStyle] = terminalResetEscapeCode - wd := func(s string, defaultCode int) string { - if s == "" { - return fmt.Sprintf(terminalHighlightEscapeCode, defaultCode) - } - return s - } - t.stdout.colorEscapes[colorize.KeywordStyle] = conf.SourceListKeywordColor - t.stdout.colorEscapes[colorize.StringStyle] = wd(conf.SourceListStringColor, ansiGreen) - t.stdout.colorEscapes[colorize.NumberStyle] = conf.SourceListNumberColor - t.stdout.colorEscapes[colorize.CommentStyle] = wd(conf.SourceListCommentColor, ansiBrMagenta) - t.stdout.colorEscapes[colorize.ArrowStyle] = wd(conf.SourceListArrowColor, ansiYellow) - switch x := conf.SourceListLineColor.(type) { - case string: - t.stdout.colorEscapes[colorize.LineNoStyle] = x - case int: - if (x > ansiWhite && x < ansiBrBlack) || x < ansiBlack || x > ansiBrWhite { - x = ansiBlue - } - t.stdout.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, x) - case nil: - t.stdout.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, ansiBlue) - } + t.updateColorScheme() } if client != nil { @@ -141,6 +120,36 @@ func New(client service.Client, conf *config.Config) *Term { return t } +func (t *Term) updateColorScheme() { + if t.stdout.colorEscapes == nil { + return + } + + conf := t.conf + wd := func(s string, defaultCode int) string { + if s == "" { + return fmt.Sprintf(terminalHighlightEscapeCode, defaultCode) + } + return s + } + t.stdout.colorEscapes[colorize.KeywordStyle] = conf.SourceListKeywordColor + t.stdout.colorEscapes[colorize.StringStyle] = wd(conf.SourceListStringColor, ansiGreen) + t.stdout.colorEscapes[colorize.NumberStyle] = conf.SourceListNumberColor + t.stdout.colorEscapes[colorize.CommentStyle] = wd(conf.SourceListCommentColor, ansiBrMagenta) + t.stdout.colorEscapes[colorize.ArrowStyle] = wd(conf.SourceListArrowColor, ansiYellow) + switch x := conf.SourceListLineColor.(type) { + case string: + t.stdout.colorEscapes[colorize.LineNoStyle] = x + case int: + if (x > ansiWhite && x < ansiBrBlack) || x < ansiBlack || x > ansiBrWhite { + x = ansiBlue + } + t.stdout.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, x) + case nil: + t.stdout.colorEscapes[colorize.LineNoStyle] = fmt.Sprintf(terminalHighlightEscapeCode, ansiBlue) + } +} + func (t *Term) SetTraceNonInteractive() { t.traceNonInteractive = true } From aee401b69a798034aa6d0b30aeba99ef7aaa5b35 Mon Sep 17 00:00:00 2001 From: Andrei Matei Date: Wed, 4 Jan 2023 12:07:23 -0500 Subject: [PATCH 35/72] pkg/proc: populate pointer values (#3229) * proc: add a test for dangling unsafe pointers This new tests checks the behavior when dereferencing dangling pointers. The behavior does not fully make sense; the test checks the current behavior for now, which will be improved in subsequent commits. * proc: populate pointer values This patch changes how Value and Unreadable are populated for pointer Variables. Before this patch, variables of kind reflect.Ptr did not have their Value field populated. This patch populates it in Variable.loadValue(), which seems natural and consistent with other types of variables. The Value is the address that the pointer points to. The Unreadable field was populated inconsistently for pointer variables: it was never populated for an outer pointer, but it could be populated for an inner pointer in pointer-to-pointer types. Before this patch, in pointer whose value could not be read was not easily distinguishable from a pointer with a value that could be read, but that cannot be dereferenced (i.e. a dangling pointer): neither of these would be marked as Unreadable, and both would have a child marked as Unreadable. This patch makes it so that a pointer variable whose pointer value cannot be read is marked as Unreadable. Using this new distinction, this patch fixes a bug around dereferencing dangling pointers: before, attempting such a dereference produced a "nil pointer dereference" error. This was bogus, since the pointer was not nil. Now, we are more discerning and generate a different error. --- _fixtures/testfnpos.go | 16 +++++++++ _fixtures/testunsafepointers.go | 17 +++++++++ pkg/proc/eval.go | 9 ++++- pkg/proc/variables.go | 40 +++++++++++++++++++-- pkg/proc/variables_test.go | 63 +++++++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 _fixtures/testfnpos.go create mode 100644 _fixtures/testunsafepointers.go diff --git a/_fixtures/testfnpos.go b/_fixtures/testfnpos.go new file mode 100644 index 0000000000..9572603fc8 --- /dev/null +++ b/_fixtures/testfnpos.go @@ -0,0 +1,16 @@ +package main + +import "fmt" + +func f2() { + fmt.Printf("f2\n") +} + +func f1() { + fmt.Printf("f1\n") +} + +func main() { + f1() + f2() +} diff --git a/_fixtures/testunsafepointers.go b/_fixtures/testunsafepointers.go new file mode 100644 index 0000000000..13bbecabe6 --- /dev/null +++ b/_fixtures/testunsafepointers.go @@ -0,0 +1,17 @@ +package main + +import ( + "runtime" + "unsafe" +) + +func main() { + // We're going to produce a pointer with a bad address. + badAddr := uintptr(0x42) + unsafeDanglingPtrPtr := unsafe.Pointer(badAddr) + // We produce a **int, instead of more simply a *int, in order for the test + // program to test more complex Delve behavior. + danglingPtrPtr := (**int)(unsafeDanglingPtrPtr) + _ = danglingPtrPtr + runtime.Breakpoint() +} diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 94ef278433..1afa229488 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -1571,7 +1571,14 @@ func (scope *EvalScope) evalPointerDeref(node *ast.StarExpr) (*Variable, error) xev.Children[0].OnlyAddr = false return &(xev.Children[0]), nil } - rv := xev.maybeDereference() + xev.loadPtr() + if xev.Unreadable != nil { + val, ok := constant.Uint64Val(xev.Value) + if ok && val == 0 { + return nil, fmt.Errorf("couldn't read pointer: %w", xev.Unreadable) + } + } + rv := &xev.Children[0] if rv.Addr == 0 { return nil, fmt.Errorf("nil pointer dereference") } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index bef2287d82..5830894235 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -124,6 +124,9 @@ type Variable struct { // number of elements to skip when loading a map mapSkip int + // Children lists the variables sub-variables. What constitutes a child + // depends on the variable's type. For pointers, there's one child + // representing the pointed-to variable. Children []Variable loaded bool @@ -1244,6 +1247,40 @@ func (v *Variable) maybeDereference() *Variable { } } +// loadPtr assumes that v is a pointer and loads its value. v also gets a child +// variable, representing the pointed-to value. If v is already loaded, +// loadPtr() is a no-op. +func (v *Variable) loadPtr() { + if len(v.Children) > 0 { + // We've already loaded this variable. + return + } + + t := v.RealType.(*godwarf.PtrType) + v.Len = 1 + + var child *Variable + if v.Unreadable == nil { + ptrval, err := readUintRaw(v.mem, v.Addr, t.ByteSize) + if err == nil { + child = v.newVariable("", ptrval, t.Type, DereferenceMemory(v.mem)) + } else { + // We failed to read the pointer value; mark v as unreadable. + v.Unreadable = err + } + } + + if v.Unreadable != nil { + // Pointers get a child even if their value can't be read, to + // maintain backwards compatibility. + child = v.newVariable("", 0 /* addr */, t.Type, DereferenceMemory(v.mem)) + child.Unreadable = fmt.Errorf("parent pointer unreadable: %w", v.Unreadable) + } + + v.Children = []Variable{*child} + v.Value = constant.MakeUint64(v.Children[0].Addr) +} + func loadValues(vars []*Variable, cfg LoadConfig) { for i := range vars { vars[i].loadValueInternal(0, cfg) @@ -1263,8 +1300,7 @@ func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { v.loaded = true switch v.Kind { case reflect.Ptr, reflect.UnsafePointer: - v.Len = 1 - v.Children = []Variable{*v.maybeDereference()} + v.loadPtr() if cfg.FollowPointers { // Don't increase the recursion level when dereferencing pointers // unless this is a pointer to interface (which could cause an infinite loop) diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index a46826ea4e..9af5956b04 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -1610,3 +1610,66 @@ func TestEvalExpressionGenerics(t *testing.T) { } }) } + +// Test the behavior when reading dangling pointers produced by unsafe code. +func TestBadUnsafePtr(t *testing.T) { + withTestProcess("testunsafepointers", t, func(p *proc.Target, fixture protest.Fixture) { + assertNoError(p.Continue(), t, "Continue()") + + // danglingPtrPtr is a pointer with value 0x42, which is an unreadable + // address. + danglingPtrPtr, err := evalVariableWithCfg(p, "danglingPtrPtr", pnormalLoadConfig) + assertNoError(err, t, "eval returned an error") + t.Logf("danglingPtrPtr (%s): unreadable: %v. addr: 0x%x, value: %v", + danglingPtrPtr.TypeString(), danglingPtrPtr.Unreadable, danglingPtrPtr.Addr, danglingPtrPtr.Value) + assertNoError(danglingPtrPtr.Unreadable, t, "danglingPtrPtr is unreadable") + if val := danglingPtrPtr.Value; val == nil { + t.Fatal("Value not set danglingPtrPtr") + } + val, ok := constant.Uint64Val(danglingPtrPtr.Value) + if !ok { + t.Fatalf("Value not uint64: %v", danglingPtrPtr.Value) + } + if val != 0x42 { + t.Fatalf("expected value to be 0x42, got 0x%x", val) + } + if len(danglingPtrPtr.Children) != 1 { + t.Fatalf("expected 1 child, got: %d", len(danglingPtrPtr.Children)) + } + + badPtr, err := evalVariableWithCfg(p, "*danglingPtrPtr", pnormalLoadConfig) + assertNoError(err, t, "error evaluating *danglingPtrPtr") + t.Logf("badPtr: (%s): unreadable: %v. addr: 0x%x, value: %v", + badPtr.TypeString(), badPtr.Unreadable, badPtr.Addr, badPtr.Value) + if badPtr.Unreadable == nil { + t.Fatalf("badPtr should be unreadable") + } + if badPtr.Addr != 0x42 { + t.Fatalf("expected danglingPtr to point to 0x42, got 0x%x", badPtr.Addr) + } + if len(badPtr.Children) != 1 { + t.Fatalf("expected 1 child, got: %d", len(badPtr.Children)) + } + badPtrChild := badPtr.Children[0] + t.Logf("badPtr.Child (%s): unreadable: %v. addr: 0x%x, value: %v", + badPtrChild.TypeString(), badPtrChild.Unreadable, badPtrChild.Addr, badPtrChild.Value) + // We expect the dummy child variable to be marked as unreadable. + if badPtrChild.Unreadable == nil { + t.Fatalf("expected x to be unreadable, but got value: %v", badPtrChild.Value) + } + + // Evaluating **danglingPtrPtr fails. + _, err = evalVariableWithCfg(p, "**danglingPtrPtr", pnormalLoadConfig) + if err == nil { + t.Fatalf("expected error doing **danglingPtrPtr") + } + expErr := "couldn't read pointer" + if !strings.Contains(err.Error(), expErr) { + t.Fatalf("expected \"%s\", got: \"%s\"", expErr, err) + } + nexpErr := "nil pointer dereference" + if strings.Contains(err.Error(), nexpErr) { + t.Fatalf("shouldn't have gotten \"%s\", but got: \"%s\"", nexpErr, err) + } + }) +} From 4aaa184b312b5c1c5077a27166f25941d15ca898 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Wed, 4 Jan 2023 19:47:27 +0100 Subject: [PATCH 36/72] cmd/dlv: match go test behavior when dlv test gets a list of .go files (#3232) When 'dlv test' is called with a list of files it should match the behavior of 'go test' and chdir to the directory where the go files are (if they are all in the same directory). Fixes #3230 --- cmd/dlv/cmds/commands.go | 12 +++++------- cmd/dlv/dlv_test.go | 35 ++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 6b1d479c9d..6813de0b82 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -754,11 +754,7 @@ func testCmd(cmd *cobra.Command, args []string) { processArgs := append([]string{debugname}, targetArgs...) if workingDir == "" { - if len(dlvArgs) == 1 { - workingDir = getPackageDir(dlvArgs[0]) - } else { - workingDir = "." - } + workingDir = getPackageDir(dlvArgs) } return execute(0, processArgs, conf, "", debugger.ExecutingGeneratedTest, dlvArgs, buildFlags) @@ -766,8 +762,10 @@ func testCmd(cmd *cobra.Command, args []string) { os.Exit(status) } -func getPackageDir(pkg string) string { - out, err := exec.Command("go", "list", "--json", pkg).CombinedOutput() +func getPackageDir(pkg []string) string { + args := []string{"list", "--json"} + args = append(args, pkg...) + out, err := exec.Command("go", args...).CombinedOutput() if err != nil { return "." } diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 57465acaec..b2f35d85a5 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1266,19 +1266,32 @@ func TestDlvTestChdir(t *testing.T) { defer os.RemoveAll(tmpdir) fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "--allow-non-terminal-interactive=true", "test", filepath.Join(fixtures, "buildtest"), "--", "-test.v") - cmd.Stdin = strings.NewReader("continue\nexit\n") - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("error executing Delve: %v", err) - } - t.Logf("output: %q", out) - p, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) - tgt := "current directory: " + p - if !strings.Contains(string(out), tgt) { - t.Errorf("output did not contain expected string %q", tgt) + dotest := func(testargs []string) { + t.Helper() + + args := []string{"--allow-non-terminal-interactive=true", "test"} + args = append(args, testargs...) + args = append(args, "--", "-test.v") + t.Logf("dlv test %s", args) + cmd := exec.Command(dlvbin, args...) + cmd.Stdin = strings.NewReader("continue\nexit\n") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("error executing Delve: %v", err) + } + t.Logf("output: %q", out) + + p, _ := filepath.Abs(filepath.Join(fixtures, "buildtest")) + tgt := "current directory: " + p + if !strings.Contains(string(out), tgt) { + t.Errorf("output did not contain expected string %q", tgt) + } } + + dotest([]string{filepath.Join(fixtures, "buildtest")}) + files, _ := filepath.Glob(filepath.Join(fixtures, "buildtest", "*.go")) + dotest(files) } func TestVersion(t *testing.T) { From 4050a62c0a4eba5770d46b7886e6b84a5d2e57f4 Mon Sep 17 00:00:00 2001 From: Aviram Hassan Date: Thu, 5 Jan 2023 00:14:23 +0200 Subject: [PATCH 37/72] pkg/proc: fix stripping DYLD_INSERT_LIBRARIES on macOS (#3245) --- pkg/proc/gdbserial/gdbserver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index c24c0d33b4..8f99c999d0 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -553,7 +553,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ // This is unlike other protected processes, where they just strip it out. env := make([]string, 0, len(process.Env)) for _, v := range process.Env { - if !(v == "DYLD_INSERT_LIBRARIES") { + if !strings.HasPrefix(v, "DYLD_INSERT_LIBRARIES") { env = append(env, v) } } From 62335703d216fc15cb8180a493e7f0526184f720 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 5 Jan 2023 05:09:03 -0800 Subject: [PATCH 38/72] terminal: Add config options for tab printing (#3243) New config "tab" allows user to set a string instead of literal tab (e.g. "... "). New config "source-list-tab-color" allows that string to be colored. This builds on top of the `updateColorScheme` change --- pkg/config/config.go | 12 ++++++++++++ pkg/terminal/colorize/colorize.go | 24 +++++++++++++++++++++--- pkg/terminal/colorize/colorize_test.go | 4 ++-- pkg/terminal/config.go | 2 +- pkg/terminal/out.go | 5 +++-- pkg/terminal/terminal.go | 14 +++++++++++++- 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 0c0de69872..035b137dfd 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -77,6 +77,9 @@ type Config struct { // Source list comment color, as a terminal escape sequence. SourceListCommentColor string `yaml:"source-list-comment-color"` + // Source list tab color, as a terminal escape sequence. + SourceListTabColor string `yaml:"source-list-tab-color"` + // number of lines to list above and below cursor when printfile() is // called (i.e. when execution stops, listCommand is used, etc) SourceListLineCount *int `yaml:"source-list-line-count,omitempty"` @@ -94,6 +97,11 @@ type Config struct { // - default (or the empty string): use disassembly for step-instruction, // source for everything else. Position string `yaml:"position"` + + // Tab changes what is printed when a '\t' is encountered in source code. + // This can be used to shorten the tabstop (e.g. " ") or to print a more + // visual indent (e.g. ">__ "). + Tab string `yaml:"tab"` } func (c *Config) GetSourceListLineCount() int { @@ -252,6 +260,10 @@ func writeDefaultConfig(f *os.File) error { # source-list-number-color: "\x1b[0m" # source-list-comment-color: "\x1b[95m" # source-list-arrow-color: "\x1b[93m" +# source-list-tab-color: "\x1b[90m" + +# Uncomment to change what is printed instead of '\t'. +# tab: "... " # Uncomment to change the number of lines printed above and below cursor when # listing source code. diff --git a/pkg/terminal/colorize/colorize.go b/pkg/terminal/colorize/colorize.go index 56931b5cf2..da60f416d5 100644 --- a/pkg/terminal/colorize/colorize.go +++ b/pkg/terminal/colorize/colorize.go @@ -23,17 +23,28 @@ const ( CommentStyle LineNoStyle ArrowStyle + TabStyle ) // Print prints to out a syntax highlighted version of the text read from // reader, between lines startLine and endLine. -func Print(out io.Writer, path string, reader io.Reader, startLine, endLine, arrowLine int, colorEscapes map[Style]string) error { +func Print(out io.Writer, path string, reader io.Reader, startLine, endLine, arrowLine int, colorEscapes map[Style]string, altTabStr string) error { buf, err := ioutil.ReadAll(reader) if err != nil { return err } - w := &lineWriter{w: out, lineRange: [2]int{startLine, endLine}, arrowLine: arrowLine, colorEscapes: colorEscapes} + w := &lineWriter{ + w: out, + lineRange: [2]int{startLine, endLine}, + arrowLine: arrowLine, + colorEscapes: colorEscapes, + } + if len(altTabStr) > 0 { + w.tabBytes = []byte(altTabStr) + } else { + w.tabBytes = []byte("\t") + } if filepath.Ext(path) != ".go" { w.Write(NormalStyle, buf, true) @@ -211,6 +222,8 @@ type lineWriter struct { lineno int colorEscapes map[Style]string + + tabBytes []byte } func (w *lineWriter) style(style Style) { @@ -268,7 +281,8 @@ func (w *lineWriter) writeInternal(style Style, data []byte) { func (w *lineWriter) Write(style Style, data []byte, last bool) { cur := 0 for i := range data { - if data[i] == '\n' { + switch data[i] { + case '\n': if last && i == len(data)-1 { w.writeInternal(style, data[cur:i]) if w.curStyle != NormalStyle { @@ -283,6 +297,10 @@ func (w *lineWriter) Write(style Style, data []byte, last bool) { w.nl() } cur = i + 1 + case '\t': + w.writeInternal(style, data[cur:i]) + w.writeInternal(TabStyle, w.tabBytes) + cur = i + 1 } } if cur < len(data) { diff --git a/pkg/terminal/colorize/colorize_test.go b/pkg/terminal/colorize/colorize_test.go index bbccbcd6e1..9ff9e89cca 100644 --- a/pkg/terminal/colorize/colorize_test.go +++ b/pkg/terminal/colorize/colorize_test.go @@ -58,11 +58,11 @@ func TestPrint(t *testing.T) { // ensures the AST analysis behaves as expected, // please update `printed` if `terminal/colorize.Print` changes. buf := &bytes.Buffer{} - colorize.Print(buf, "main.go", bytes.NewBuffer(dat), 1, 30, 10, colors) + colorize.Print(buf, "main.go", bytes.NewBuffer(dat), 1, 30, 10, colors, "") const printToStdout = false if printToStdout { - colorize.Print(os.Stdout, "main.go", bytes.NewBuffer(dat), 1, 30, 10, colors) + colorize.Print(os.Stdout, "main.go", bytes.NewBuffer(dat), 1, 30, 10, colors, "") } b := bytes.ReplaceAll(buf.Bytes(), []byte("\r\n"), []byte("\n")) diff --git a/pkg/terminal/config.go b/pkg/terminal/config.go index 9d7f43d287..3584f9b0c4 100644 --- a/pkg/terminal/config.go +++ b/pkg/terminal/config.go @@ -25,7 +25,7 @@ func configureCmd(t *Term, ctx callContext, args string) error { if t.client != nil { // only happens in tests lcfg := t.loadConfig() t.client.SetReturnValuesLoadConfig(&lcfg) - t.updateColorScheme() + t.updateConfig() } return nil } diff --git a/pkg/terminal/out.go b/pkg/terminal/out.go index b8bb95bb1b..7b0cab8300 100644 --- a/pkg/terminal/out.go +++ b/pkg/terminal/out.go @@ -19,6 +19,7 @@ type transcriptWriter struct { file *bufio.Writer fh io.Closer colorEscapes map[colorize.Style]string + altTabString string } func (w *transcriptWriter) Write(p []byte) (nn int, err error) { @@ -38,12 +39,12 @@ func (w *transcriptWriter) Write(p []byte) (nn int, err error) { func (w *transcriptWriter) ColorizePrint(path string, reader io.ReadSeeker, startLine, endLine, arrowLine int) error { var err error if !w.fileOnly { - err = colorize.Print(w.pw.w, path, reader, startLine, endLine, arrowLine, w.colorEscapes) + err = colorize.Print(w.pw.w, path, reader, startLine, endLine, arrowLine, w.colorEscapes, w.altTabString) } if err == nil { if w.file != nil { reader.Seek(0, io.SeekStart) - return colorize.Print(w.file, path, reader, startLine, endLine, arrowLine, nil) + return colorize.Print(w.file, path, reader, startLine, endLine, arrowLine, nil, w.altTabString) } } return err diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index b4cde64c58..581087ef57 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -108,9 +108,10 @@ func New(client service.Client, conf *config.Config) *Term { t.stdout.pw = &pagingWriter{w: getColorableWriter()} t.stdout.colorEscapes = make(map[colorize.Style]string) t.stdout.colorEscapes[colorize.NormalStyle] = terminalResetEscapeCode - t.updateColorScheme() } + t.updateConfig() + if client != nil { lcfg := t.loadConfig() client.SetReturnValuesLoadConfig(&lcfg) @@ -120,6 +121,12 @@ func New(client service.Client, conf *config.Config) *Term { return t } +func (t *Term) updateConfig() { + // These are always called together. + t.updateColorScheme() + t.updateTab() +} + func (t *Term) updateColorScheme() { if t.stdout.colorEscapes == nil { return @@ -137,6 +144,7 @@ func (t *Term) updateColorScheme() { t.stdout.colorEscapes[colorize.NumberStyle] = conf.SourceListNumberColor t.stdout.colorEscapes[colorize.CommentStyle] = wd(conf.SourceListCommentColor, ansiBrMagenta) t.stdout.colorEscapes[colorize.ArrowStyle] = wd(conf.SourceListArrowColor, ansiYellow) + t.stdout.colorEscapes[colorize.TabStyle] = wd(conf.SourceListTabColor, ansiBrBlack) switch x := conf.SourceListLineColor.(type) { case string: t.stdout.colorEscapes[colorize.LineNoStyle] = x @@ -150,6 +158,10 @@ func (t *Term) updateColorScheme() { } } +func (t *Term) updateTab() { + t.stdout.altTabString = t.conf.Tab +} + func (t *Term) SetTraceNonInteractive() { t.traceNonInteractive = true } From 9c449548604bee779e11baae4593e55d6290288f Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Thu, 5 Jan 2023 18:45:55 +0100 Subject: [PATCH 39/72] godwarf: Attempt to load `DW_AT_specification` if present (#3247) --- pkg/dwarf/godwarf/tree.go | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/pkg/dwarf/godwarf/tree.go b/pkg/dwarf/godwarf/tree.go index 12008d9e60..379da1a9a5 100644 --- a/pkg/dwarf/godwarf/tree.go +++ b/pkg/dwarf/godwarf/tree.go @@ -24,11 +24,13 @@ func (ce compositeEntry) Val(attr dwarf.Attr) interface{} { return nil } -// LoadAbstractOrigin loads the entry corresponding to the -// DW_AT_abstract_origin of entry and returns a combination of entry and its -// abstract origin. -func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) { - ao, ok := entry.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset) +// LoadAbstractOriginAndSpecification loads the entry corresponding to the +// DW_AT_abstract_origin and/or DW_AT_specification of entry and returns a +// combination of entry and its abstract origin. If a DIE has both a +// specification and an abstract origin the specification will be ignored, the +// DWARF standard is unclear on how this should be handled +func LoadAbstractOriginAndSpecification(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.Offset) { + ao, ok := getAbstractOriginOrSpecification(entry) if !ok { return entry, entry.Offset } @@ -43,7 +45,7 @@ func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.O } r = append(r, e) - ao, ok = e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset) + ao, ok = getAbstractOriginOrSpecification(e) if !ok { break } @@ -52,6 +54,18 @@ func LoadAbstractOrigin(entry *dwarf.Entry, aordr *dwarf.Reader) (Entry, dwarf.O return compositeEntry(r), entry.Offset } +func getAbstractOriginOrSpecification(e *dwarf.Entry) (dwarf.Offset, bool) { + ao, ok := e.Val(dwarf.AttrAbstractOrigin).(dwarf.Offset) + if ok { + return ao, true + } + sp, ok := e.Val(dwarf.AttrSpecification).(dwarf.Offset) + if ok { + return sp, true + } + return dwarf.Offset(0), false +} + // Tree represents a tree of dwarf objects. type Tree struct { Entry @@ -85,7 +99,7 @@ func LoadTree(off dwarf.Offset, dw *dwarf.Data, staticBase uint64) (*Tree, error if err != nil { return nil, err } - r.resolveAbstractEntries(rdr) + r.resolveAbstractAndSpecificationEntries(rdr) return r, nil } @@ -228,10 +242,10 @@ func rangeContains(a, b [2]uint64) bool { return a[0] <= b[0] && a[1] >= b[1] } -func (n *Tree) resolveAbstractEntries(rdr *dwarf.Reader) { - n.Entry, n.Offset = LoadAbstractOrigin(n.Entry.(*dwarf.Entry), rdr) +func (n *Tree) resolveAbstractAndSpecificationEntries(rdr *dwarf.Reader) { + n.Entry, n.Offset = LoadAbstractOriginAndSpecification(n.Entry.(*dwarf.Entry), rdr) for _, child := range n.Children { - child.resolveAbstractEntries(rdr) + child.resolveAbstractAndSpecificationEntries(rdr) } } From 8db9be977c4eb73aca56ee4808a85ffe5919bf19 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Thu, 5 Jan 2023 23:49:15 -0800 Subject: [PATCH 40/72] pkg/terminal: use default for Y/n empty response (#3248) The typical convention with `[Y/n]` type command line questions is that the capitalized letter represents the default if the user simply hits "enter" instead of typing an actual response. This patch fixes our implementation to use the implied default response. --- pkg/terminal/command.go | 2 +- pkg/terminal/terminal.go | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 86b8779f55..d661daeffd 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -1754,7 +1754,7 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([] if findLocErr != nil && shouldAskToSuspendBreakpoint(t) { fmt.Fprintf(os.Stderr, "Command failed: %s\n", findLocErr.Error()) findLocErr = nil - answer, err := yesno(t.line, "Set a suspended breakpoint (Delve will try to set this breakpoint when a plugin is loaded) [Y/n]?") + answer, err := yesno(t.line, "Set a suspended breakpoint (Delve will try to set this breakpoint when a plugin is loaded) [Y/n]?", "yes") if err != nil { return nil, err } diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 581087ef57..4b62625ed3 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -432,13 +432,16 @@ func (t *Term) promptForInput() (string, error) { return l, nil } -func yesno(line *liner.State, question string) (bool, error) { +func yesno(line *liner.State, question, defaultAnswer string) (bool, error) { for { answer, err := line.Prompt(question) if err != nil { return false, err } answer = strings.ToLower(strings.TrimSpace(answer)) + if answer == "" { + answer = defaultAnswer + } switch answer { case "n", "no": return false, nil @@ -469,7 +472,7 @@ func (t *Term) handleExit() (int, error) { if err != nil { if isErrProcessExited(err) { if t.client.IsMulticlient() { - answer, err := yesno(t.line, "Remote process has exited. Would you like to kill the headless instance? [Y/n] ") + answer, err := yesno(t.line, "Remote process has exited. Would you like to kill the headless instance? [Y/n] ", "yes") if err != nil { return 2, io.EOF } @@ -495,7 +498,7 @@ func (t *Term) handleExit() (int, error) { doDetach := true if t.client.IsMulticlient() { - answer, err := yesno(t.line, "Would you like to kill the headless instance? [Y/n] ") + answer, err := yesno(t.line, "Would you like to kill the headless instance? [Y/n] ", "yes") if err != nil { return 2, io.EOF } @@ -505,7 +508,7 @@ func (t *Term) handleExit() (int, error) { if doDetach { kill := true if t.client.AttachedToExistingProcess() { - answer, err := yesno(t.line, "Would you like to kill the process? [Y/n] ") + answer, err := yesno(t.line, "Would you like to kill the process? [Y/n] ", "yes") if err != nil { return 2, io.EOF } From 3847b7a199793a7ff5bbdca0152544d5d34a88db Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Fri, 6 Jan 2023 09:37:35 -0800 Subject: [PATCH 41/72] debugger,terminal: Allow setting and clearing breakpoints after process exit (#3246) This patch allows users to set a breakpoint even when the process has exited. It will be left in a pending state until the process is restarted. Fixes #3242 --- pkg/terminal/command.go | 11 +++++-- service/debugger/debugger.go | 16 ++++++--- service/test/integration1_test.go | 6 +--- service/test/integration2_test.go | 54 +++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 22 deletions(-) diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index d661daeffd..c3f802ac24 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -1753,14 +1753,18 @@ func setBreakpoint(t *Term, ctx callContext, tracepoint bool, argstr string) ([] } if findLocErr != nil && shouldAskToSuspendBreakpoint(t) { fmt.Fprintf(os.Stderr, "Command failed: %s\n", findLocErr.Error()) - findLocErr = nil - answer, err := yesno(t.line, "Set a suspended breakpoint (Delve will try to set this breakpoint when a plugin is loaded) [Y/n]?", "yes") + question := "Set a suspended breakpoint (Delve will try to set this breakpoint when a plugin is loaded) [Y/n]?" + if isErrProcessExited(findLocErr) { + question = "Set a suspended breakpoint (Delve will try to set this breakpoint when the process is restarted) [Y/n]?" + } + answer, err := yesno(t.line, question, "yes") if err != nil { return nil, err } if !answer { return nil, nil } + findLocErr = nil bp, err := t.client.CreateBreakpointWithExpr(requestedBp, spec, t.substitutePathRules(), true) if err != nil { return nil, err @@ -3220,5 +3224,6 @@ func (t *Term) formatBreakpointLocation(bp *api.Breakpoint) string { func shouldAskToSuspendBreakpoint(t *Term) bool { fns, _ := t.client.ListFunctions(`^plugin\.Open$`) - return len(fns) > 0 + _, err := t.client.GetState() + return len(fns) > 0 || isErrProcessExited(err) } diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 4eef46f441..746d167472 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -789,6 +789,18 @@ func createLogicalBreakpoint(d *Debugger, requestedBp *api.Breakpoint, setbp *pr lbp.Set = *setbp + if lbp.Set.Expr != nil { + addrs := lbp.Set.Expr(d.Target()) + if len(addrs) > 0 { + f, l, fn := d.Target().BinInfo().PCToLine(addrs[0]) + lbp.File = f + lbp.Line = l + if fn != nil { + lbp.FunctionName = fn.Name + } + } + } + err = d.target.EnableBreakpoint(lbp) if err != nil { if suspended { @@ -965,10 +977,6 @@ func (d *Debugger) ClearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint // clearBreakpoint clears a breakpoint, we can consume this function to avoid locking a goroutine func (d *Debugger) clearBreakpoint(requestedBp *api.Breakpoint) (*api.Breakpoint, error) { - if _, err := d.target.Valid(); err != nil { - return nil, err - } - if requestedBp.ID <= 0 { if len(d.target.Targets()) != 1 { return nil, ErrNotImplementedWithMultitarget diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 464a99736d..dfd006b6d2 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -806,7 +806,7 @@ func Test1ClientServer_FullStacktrace(t *testing.T) { func Test1Issue355(t *testing.T) { // After the target process has terminated should return an error but not crash withTestClient1("continuetestprog", t, func(c *rpc1.RPCClient) { - bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() state := <-ch @@ -835,10 +835,6 @@ func Test1Issue355(t *testing.T) { assertErrorOrExited(s, err, t, "SwitchGoroutine()") s, err = c.Halt() assertErrorOrExited(s, err, t, "Halt()") - _, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1}) - assertError(err, t, "CreateBreakpoint()") - _, err = c.ClearBreakpoint(bp.ID) - assertError(err, t, "ClearBreakpoint()") _, err = c.ListThreads() assertError(err, t, "ListThreads()") _, err = c.GetThread(tid) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index a66d8dd132..5550f7a3c0 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -996,7 +996,7 @@ func TestClientServer_FindLocations(t *testing.T) { if strings.Contains(locsNoSubst[0].File, "\\") { sep = "\\" } - substRules := [][2]string{[2]string{strings.Replace(locsNoSubst[0].File, "locationsprog.go", "", 1), strings.Replace(locsNoSubst[0].File, "_fixtures"+sep+"locationsprog.go", "nonexistent", 1)}} + substRules := [][2]string{{strings.Replace(locsNoSubst[0].File, "locationsprog.go", "", 1), strings.Replace(locsNoSubst[0].File, "_fixtures"+sep+"locationsprog.go", "nonexistent", 1)}} t.Logf("substitute rules: %q -> %q", substRules[0][0], substRules[0][1]) locsSubst, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "nonexistent/locationsprog.go:35", false, substRules) if err != nil { @@ -1305,7 +1305,7 @@ func TestIssue355(t *testing.T) { // After the target process has terminated should return an error but not crash protest.AllowRecording(t) withTestClient2("continuetestprog", t, func(c service.Client) { - bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) + _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.sayhi", Line: -1}) assertNoError(err, t, "CreateBreakpoint()") ch := c.Continue() state := <-ch @@ -1334,14 +1334,6 @@ func TestIssue355(t *testing.T) { assertErrorOrExited(s, err, t, "SwitchGoroutine()") s, err = c.Halt() assertErrorOrExited(s, err, t, "Halt()") - _, err = c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: -1}) - if testBackend != "rr" { - assertError(err, t, "CreateBreakpoint()") - } - _, err = c.ClearBreakpoint(bp.ID) - if testBackend != "rr" { - assertError(err, t, "ClearBreakpoint()") - } _, err = c.ListThreads() assertError(err, t, "ListThreads()") _, err = c.GetThread(tid) @@ -2959,6 +2951,48 @@ func TestPluginSuspendedBreakpoint(t *testing.T) { }) } +// Tests that breakpoint set after the process has exited will be hit when the process is restarted. +func TestBreakpointAfterProcessExit(t *testing.T) { + withTestClient2("continuetestprog", t, func(c service.Client) { + state := <-c.Continue() + if !state.Exited { + t.Fatal("process should have exited") + } + bp, err := c.CreateBreakpointWithExpr(&api.Breakpoint{ID: 2, FunctionName: "main.main", Line: 1}, "main.main", nil, true) + if err != nil { + t.Fatal(err) + } + _, err = c.Restart(false) + if err != nil { + t.Fatal(err) + } + state = <-c.Continue() + if state.CurrentThread == nil { + t.Fatal("no current thread") + } + if state.CurrentThread.Breakpoint == nil { + t.Fatal("no breakpoint") + } + if state.CurrentThread.Breakpoint.ID != bp.ID { + t.Fatal("did not hit correct breakpoint") + } + if state.CurrentThread.Function == nil { + t.Fatal("no function") + } + if state.CurrentThread.Function.Name() != "main.main" { + t.Fatal("stopped at incorrect function") + } + state = <-c.Continue() + if !state.Exited { + t.Fatal("process should have exited") + } + _, err = c.ClearBreakpoint(bp.ID) + if err != nil { + t.Fatal(err) + } + }) +} + func TestClientServer_createBreakpointWithID(t *testing.T) { protest.AllowRecording(t) withTestClient2("continuetestprog", t, func(c service.Client) { From 13143680f729e54619d549f8b44054a97b83541e Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Tue, 10 Jan 2023 22:29:58 +0200 Subject: [PATCH 42/72] proc/dwarf: LEB128 encode/decode in separate pkg (#3235) --- pkg/dwarf/dwarfbuilder/info.go | 14 ++-- pkg/dwarf/dwarfbuilder/loc.go | 6 +- pkg/dwarf/frame/parser.go | 19 ++--- pkg/dwarf/frame/table.go | 54 ++++++------- pkg/dwarf/leb128/decode.go | 82 ++++++++++++++++++++ pkg/dwarf/leb128/decode_test.go | 28 +++++++ pkg/dwarf/leb128/doc.go | 4 + pkg/dwarf/leb128/encode.go | 42 +++++++++++ pkg/dwarf/leb128/encode_test.go | 42 +++++++++++ pkg/dwarf/line/line_parser.go | 11 +-- pkg/dwarf/line/parse_util.go | 11 +-- pkg/dwarf/line/state_machine.go | 17 +++-- pkg/dwarf/line/state_machine_test.go | 9 ++- pkg/dwarf/loclist/dwarf5_loclist.go | 19 ++--- pkg/dwarf/loclist/loclist5_test.go | 4 +- pkg/dwarf/op/op.go | 27 +++---- pkg/dwarf/util/util.go | 109 +++++---------------------- pkg/dwarf/util/util_test.go | 58 -------------- 18 files changed, 314 insertions(+), 242 deletions(-) create mode 100644 pkg/dwarf/leb128/decode.go create mode 100644 pkg/dwarf/leb128/decode_test.go create mode 100644 pkg/dwarf/leb128/doc.go create mode 100644 pkg/dwarf/leb128/encode.go create mode 100644 pkg/dwarf/leb128/encode_test.go diff --git a/pkg/dwarf/dwarfbuilder/info.go b/pkg/dwarf/dwarfbuilder/info.go index 95b0f05d31..7cc5b94fb2 100644 --- a/pkg/dwarf/dwarfbuilder/info.go +++ b/pkg/dwarf/dwarfbuilder/info.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "github.com/go-delve/delve/pkg/dwarf/godwarf" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) // Form represents a DWARF form kind (see Figure 20, page 160 and following, @@ -229,19 +229,19 @@ func (b *Builder) makeAbbrevTable() []byte { var abbrev bytes.Buffer for i := range b.abbrevs { - util.EncodeULEB128(&abbrev, uint64(i+1)) - util.EncodeULEB128(&abbrev, uint64(b.abbrevs[i].tag)) + leb128.EncodeUnsigned(&abbrev, uint64(i+1)) + leb128.EncodeUnsigned(&abbrev, uint64(b.abbrevs[i].tag)) if b.abbrevs[i].children { abbrev.WriteByte(0x01) } else { abbrev.WriteByte(0x00) } for j := range b.abbrevs[i].attr { - util.EncodeULEB128(&abbrev, uint64(b.abbrevs[i].attr[j])) - util.EncodeULEB128(&abbrev, uint64(b.abbrevs[i].form[j])) + leb128.EncodeUnsigned(&abbrev, uint64(b.abbrevs[i].attr[j])) + leb128.EncodeUnsigned(&abbrev, uint64(b.abbrevs[i].form[j])) } - util.EncodeULEB128(&abbrev, 0) - util.EncodeULEB128(&abbrev, 0) + leb128.EncodeUnsigned(&abbrev, 0) + leb128.EncodeUnsigned(&abbrev, 0) } return abbrev.Bytes() diff --git a/pkg/dwarf/dwarfbuilder/loc.go b/pkg/dwarf/dwarfbuilder/loc.go index 357be789df..f89e6e7cac 100644 --- a/pkg/dwarf/dwarfbuilder/loc.go +++ b/pkg/dwarf/dwarfbuilder/loc.go @@ -3,8 +3,8 @@ package dwarfbuilder import ( "bytes" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/op" - "github.com/go-delve/delve/pkg/dwarf/util" ) // LocEntry represents one entry of debug_loc. @@ -23,9 +23,9 @@ func LocationBlock(args ...interface{}) []byte { case op.Opcode: buf.WriteByte(byte(x)) case int: - util.EncodeSLEB128(&buf, int64(x)) + leb128.EncodeSigned(&buf, int64(x)) case uint: - util.EncodeULEB128(&buf, uint64(x)) + leb128.EncodeUnsigned(&buf, uint64(x)) default: panic("unsupported value type") } diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go index 147ca34f15..e9c638ecdf 100644 --- a/pkg/dwarf/frame/parser.go +++ b/pkg/dwarf/frame/parser.go @@ -9,6 +9,7 @@ import ( "fmt" "io" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -123,10 +124,10 @@ func parseFDE(ctx *parseContext) parsefunc { ctx.entries = append(ctx.entries, ctx.frame) if ctx.parsingEHFrame() && len(ctx.frame.CIE.Augmentation) > 0 { - // If we are parsing a .eh_frame and we saw an agumentation string then we + // If we are parsing a .eh_frame and we saw an augmentation string then we // need to read the augmentation data, which are encoded as a ULEB128 // size followed by 'size' bytes. - n, _ := util.DecodeULEB128(reader) + n, _ := leb128.DecodeUnsigned(reader) reader.Seek(int64(n), io.SeekCurrent) } @@ -165,23 +166,23 @@ func parseCIE(ctx *parseContext) parsefunc { } // parse code alignment factor - ctx.common.CodeAlignmentFactor, _ = util.DecodeULEB128(buf) + ctx.common.CodeAlignmentFactor, _ = leb128.DecodeUnsigned(buf) // parse data alignment factor - ctx.common.DataAlignmentFactor, _ = util.DecodeSLEB128(buf) + ctx.common.DataAlignmentFactor, _ = leb128.DecodeSigned(buf) // parse return address register if ctx.parsingEHFrame() && ctx.common.Version == 1 { b, _ := buf.ReadByte() ctx.common.ReturnAddressRegister = uint64(b) } else { - ctx.common.ReturnAddressRegister, _ = util.DecodeULEB128(buf) + ctx.common.ReturnAddressRegister, _ = leb128.DecodeUnsigned(buf) } ctx.common.ptrEncAddr = ptrEncAbs if ctx.parsingEHFrame() && len(ctx.common.Augmentation) > 0 { - _, _ = util.DecodeULEB128(buf) // augmentation data length + _, _ = leb128.DecodeUnsigned(buf) // augmentation data length for i := 1; i < len(ctx.common.Augmentation); i++ { switch ctx.common.Augmentation[i] { case 'L': @@ -231,7 +232,7 @@ func parseCIE(ctx *parseContext) parsefunc { // The parameter addr is the address that the current byte of 'buf' will be // mapped to when the executable file containing the eh_frame section being // parse is loaded in memory. -func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, ptrEnc ptrEnc) uint64 { +func (ctx *parseContext) readEncodedPtr(addr uint64, buf leb128.Reader, ptrEnc ptrEnc) uint64 { if ptrEnc == ptrEncOmit { return 0 } @@ -242,7 +243,7 @@ func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, case ptrEncAbs, ptrEncSigned: ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, ctx.ptrSize) case ptrEncUleb: - ptr, _ = util.DecodeULEB128(buf) + ptr, _ = leb128.DecodeUnsigned(buf) case ptrEncUdata2: ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2) case ptrEncSdata2: @@ -256,7 +257,7 @@ func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, case ptrEncUdata8, ptrEncSdata8: ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 8) case ptrEncSleb: - n, _ := util.DecodeSLEB128(buf) + n, _ := leb128.DecodeSigned(buf) ptr = uint64(n) } diff --git a/pkg/dwarf/frame/table.go b/pkg/dwarf/frame/table.go index a0f3f1e306..c49d2115b8 100644 --- a/pkg/dwarf/frame/table.go +++ b/pkg/dwarf/frame/table.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "fmt" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) // DWRule wrapper of rule defined for register values. @@ -254,7 +254,7 @@ func offset(frame *FrameContext) { var ( reg = b & low_6_offset - offset, _ = util.DecodeULEB128(frame.buf) + offset, _ = leb128.DecodeUnsigned(frame.buf) ) frame.Regs[uint64(reg)] = DWRule{Offset: int64(offset) * frame.dataAlignment, Rule: RuleOffset} @@ -284,26 +284,26 @@ func setloc(frame *FrameContext) { func offsetextended(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - offset, _ = util.DecodeULEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + offset, _ = leb128.DecodeUnsigned(frame.buf) ) frame.Regs[reg] = DWRule{Offset: int64(offset) * frame.dataAlignment, Rule: RuleOffset} } func undefined(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) frame.Regs[reg] = DWRule{Rule: RuleUndefined} } func samevalue(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) frame.Regs[reg] = DWRule{Rule: RuleSameVal} } func register(frame *FrameContext) { - reg1, _ := util.DecodeULEB128(frame.buf) - reg2, _ := util.DecodeULEB128(frame.buf) + reg1, _ := leb128.DecodeUnsigned(frame.buf) + reg2, _ := leb128.DecodeUnsigned(frame.buf) frame.Regs[reg1] = DWRule{Reg: reg2, Rule: RuleRegister} } @@ -316,7 +316,7 @@ func restorestate(frame *FrameContext) { } func restoreextended(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) oldrule, ok := frame.initialRegs[reg] if ok { @@ -327,8 +327,8 @@ func restoreextended(frame *FrameContext) { } func defcfa(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) - offset, _ := util.DecodeULEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) + offset, _ := leb128.DecodeUnsigned(frame.buf) frame.CFA.Rule = RuleCFA frame.CFA.Reg = reg @@ -336,18 +336,18 @@ func defcfa(frame *FrameContext) { } func defcfaregister(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) frame.CFA.Reg = reg } func defcfaoffset(frame *FrameContext) { - offset, _ := util.DecodeULEB128(frame.buf) + offset, _ := leb128.DecodeUnsigned(frame.buf) frame.CFA.Offset = int64(offset) } func defcfasf(frame *FrameContext) { - reg, _ := util.DecodeULEB128(frame.buf) - offset, _ := util.DecodeSLEB128(frame.buf) + reg, _ := leb128.DecodeUnsigned(frame.buf) + offset, _ := leb128.DecodeSigned(frame.buf) frame.CFA.Rule = RuleCFA frame.CFA.Reg = reg @@ -355,14 +355,14 @@ func defcfasf(frame *FrameContext) { } func defcfaoffsetsf(frame *FrameContext) { - offset, _ := util.DecodeSLEB128(frame.buf) + offset, _ := leb128.DecodeSigned(frame.buf) offset *= frame.dataAlignment frame.CFA.Offset = offset } func defcfaexpression(frame *FrameContext) { var ( - l, _ = util.DecodeULEB128(frame.buf) + l, _ = leb128.DecodeUnsigned(frame.buf) expr = frame.buf.Next(int(l)) ) @@ -372,8 +372,8 @@ func defcfaexpression(frame *FrameContext) { func expression(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - l, _ = util.DecodeULEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + l, _ = leb128.DecodeUnsigned(frame.buf) expr = frame.buf.Next(int(l)) ) @@ -382,8 +382,8 @@ func expression(frame *FrameContext) { func offsetextendedsf(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - offset, _ = util.DecodeSLEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + offset, _ = leb128.DecodeSigned(frame.buf) ) frame.Regs[reg] = DWRule{Offset: offset * frame.dataAlignment, Rule: RuleOffset} @@ -391,8 +391,8 @@ func offsetextendedsf(frame *FrameContext) { func valoffset(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - offset, _ = util.DecodeULEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + offset, _ = leb128.DecodeUnsigned(frame.buf) ) frame.Regs[reg] = DWRule{Offset: int64(offset), Rule: RuleValOffset} @@ -400,8 +400,8 @@ func valoffset(frame *FrameContext) { func valoffsetsf(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - offset, _ = util.DecodeSLEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + offset, _ = leb128.DecodeSigned(frame.buf) ) frame.Regs[reg] = DWRule{Offset: offset * frame.dataAlignment, Rule: RuleValOffset} @@ -409,8 +409,8 @@ func valoffsetsf(frame *FrameContext) { func valexpression(frame *FrameContext) { var ( - reg, _ = util.DecodeULEB128(frame.buf) - l, _ = util.DecodeULEB128(frame.buf) + reg, _ = leb128.DecodeUnsigned(frame.buf) + l, _ = leb128.DecodeUnsigned(frame.buf) expr = frame.buf.Next(int(l)) ) diff --git a/pkg/dwarf/leb128/decode.go b/pkg/dwarf/leb128/decode.go new file mode 100644 index 0000000000..61f0aa3cf3 --- /dev/null +++ b/pkg/dwarf/leb128/decode.go @@ -0,0 +1,82 @@ +package leb128 + +import ( + "io" +) + +// Reader is a io.ByteReader with a Len method. This interface is +// satisfied by both bytes.Buffer and bytes.Reader. +type Reader interface { + io.ByteReader + io.Reader + Len() int +} + +// DecodeUnsigned decodes an unsigned Little Endian Base 128 +// represented number. +func DecodeUnsigned(buf Reader) (uint64, uint32) { + var ( + result uint64 + shift uint64 + length uint32 + ) + + if buf.Len() == 0 { + return 0, 0 + } + + for { + b, err := buf.ReadByte() + if err != nil { + panic("Could not parse ULEB128 value") + } + length++ + + result |= uint64((uint(b) & 0x7f) << shift) + + // If high order bit is 1. + if b&0x80 == 0 { + break + } + + shift += 7 + } + + return result, length +} + +// DecodeSigned decodes a signed Little Endian Base 128 +// represented number. +func DecodeSigned(buf Reader) (int64, uint32) { + var ( + b byte + err error + result int64 + shift uint64 + length uint32 + ) + + if buf.Len() == 0 { + return 0, 0 + } + + for { + b, err = buf.ReadByte() + if err != nil { + panic("Could not parse SLEB128 value") + } + length++ + + result |= int64((int64(b) & 0x7f) << shift) + shift += 7 + if b&0x80 == 0 { + break + } + } + + if (shift < 8*uint64(length)) && (b&0x40 > 0) { + result |= -(1 << shift) + } + + return result, length +} diff --git a/pkg/dwarf/leb128/decode_test.go b/pkg/dwarf/leb128/decode_test.go new file mode 100644 index 0000000000..f9049bc79b --- /dev/null +++ b/pkg/dwarf/leb128/decode_test.go @@ -0,0 +1,28 @@ +package leb128 + +import ( + "bytes" + "testing" +) + +func TestDecodeUnsigned(t *testing.T) { + leb128 := bytes.NewBuffer([]byte{0xE5, 0x8E, 0x26}) + + n, c := DecodeUnsigned(leb128) + if n != 624485 { + t.Fatal("Number was not decoded properly, got: ", n, c) + } + + if c != 3 { + t.Fatal("Count not returned correctly") + } +} + +func TestDecodeSigned(t *testing.T) { + sleb128 := bytes.NewBuffer([]byte{0x9b, 0xf1, 0x59}) + + n, c := DecodeSigned(sleb128) + if n != -624485 { + t.Fatal("Number was not decoded properly, got: ", n, c) + } +} diff --git a/pkg/dwarf/leb128/doc.go b/pkg/dwarf/leb128/doc.go new file mode 100644 index 0000000000..73f5a0f625 --- /dev/null +++ b/pkg/dwarf/leb128/doc.go @@ -0,0 +1,4 @@ +// Package leb128 provides encoders and decoders for The Little Endian Base 128 format. +// The Little Endian Base 128 format is defined in the DWARF v4 standard, +// section 7.6, page 161 and following. +package leb128 diff --git a/pkg/dwarf/leb128/encode.go b/pkg/dwarf/leb128/encode.go new file mode 100644 index 0000000000..c4f1a232cc --- /dev/null +++ b/pkg/dwarf/leb128/encode.go @@ -0,0 +1,42 @@ +package leb128 + +import ( + "io" +) + +// EncodeUnsigned encodes x to the unsigned Little Endian Base 128 format. +func EncodeUnsigned(out io.ByteWriter, x uint64) { + for { + b := byte(x & 0x7f) + x = x >> 7 + if x != 0 { + b = b | 0x80 + } + out.WriteByte(b) + if x == 0 { + break + } + } +} + +// EncodeSigned encodes x to the signed Little Endian Base 128 format. +func EncodeSigned(out io.ByteWriter, x int64) { + for { + b := byte(x & 0x7f) + x >>= 7 + + signb := b & 0x40 + + last := false + if (x == 0 && signb == 0) || (x == -1 && signb != 0) { + last = true + } else { + b = b | 0x80 + } + out.WriteByte(b) + + if last { + break + } + } +} diff --git a/pkg/dwarf/leb128/encode_test.go b/pkg/dwarf/leb128/encode_test.go new file mode 100644 index 0000000000..f344b7c889 --- /dev/null +++ b/pkg/dwarf/leb128/encode_test.go @@ -0,0 +1,42 @@ +package leb128 + +import ( + "bytes" + "testing" +) + +func TestEncodeUnsigned(t *testing.T) { + tc := []uint64{0x00, 0x7f, 0x80, 0x8f, 0xffff, 0xfffffff7} + for i := range tc { + var buf bytes.Buffer + EncodeUnsigned(&buf, tc[i]) + enc := append([]byte{}, buf.Bytes()...) + buf.Write([]byte{0x1, 0x2, 0x3}) + out, c := DecodeUnsigned(&buf) + t.Logf("input %x output %x encoded %x", tc[i], out, enc) + if c != uint32(len(enc)) { + t.Errorf("wrong encode") + } + if out != tc[i] { + t.Errorf("wrong encode") + } + } +} + +func TestEncodeSigned(t *testing.T) { + tc := []int64{2, -2, 127, -127, 128, -128, 129, -129} + for i := range tc { + var buf bytes.Buffer + EncodeSigned(&buf, tc[i]) + enc := append([]byte{}, buf.Bytes()...) + buf.Write([]byte{0x1, 0x2, 0x3}) + out, c := DecodeSigned(&buf) + t.Logf("input %x output %x encoded %x", tc[i], out, enc) + if c != uint32(len(enc)) { + t.Errorf("wrong encode") + } + if out != tc[i] { + t.Errorf("wrong encode") + } + } +} diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index 19d2ae20b4..a6bb499544 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -6,6 +6,7 @@ import ( "path" "strings" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -173,7 +174,7 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool { if dirEntryFormReader == nil { return false } - dirCount, _ := util.DecodeULEB128(buf) + dirCount, _ := leb128.DecodeUnsigned(buf) info.IncludeDirs = make([]string, 0, dirCount) for i := uint64(0); i < dirCount; i++ { dirEntryFormReader.reset() @@ -242,9 +243,9 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) entry.Path = strings.ReplaceAll(entry.Path, "\\", "/") } - entry.DirIdx, _ = util.DecodeULEB128(buf) - entry.LastModTime, _ = util.DecodeULEB128(buf) - entry.Length, _ = util.DecodeULEB128(buf) + entry.DirIdx, _ = leb128.DecodeUnsigned(buf) + entry.LastModTime, _ = leb128.DecodeUnsigned(buf) + entry.Length, _ = leb128.DecodeUnsigned(buf) if !pathIsAbs(entry.Path) { if entry.DirIdx < uint64(len(info.IncludeDirs)) { entry.Path = path.Join(info.IncludeDirs[entry.DirIdx], entry.Path) @@ -276,7 +277,7 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool { if fileEntryFormReader == nil { return false } - fileCount, _ := util.DecodeULEB128(buf) + fileCount, _ := leb128.DecodeUnsigned(buf) info.FileNames = make([]*FileEntry, 0, fileCount) for i := 0; i < int(fileCount); i++ { var ( diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go index 867d9b7b3b..4f2e5bbf07 100644 --- a/pkg/dwarf/line/parse_util.go +++ b/pkg/dwarf/line/parse_util.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "errors" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -70,8 +71,8 @@ func readEntryFormat(buf *bytes.Buffer, logf func(string, ...interface{})) *form formCodes: make([]uint64, count), } for i := range r.contentTypes { - r.contentTypes[i], _ = util.DecodeULEB128(buf) - r.formCodes[i], _ = util.DecodeULEB128(buf) + r.contentTypes[i], _ = leb128.DecodeUnsigned(buf) + r.formCodes[i], _ = leb128.DecodeUnsigned(buf) } return r } @@ -94,7 +95,7 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { switch rdr.formCode { case _DW_FORM_block: - n, _ := util.DecodeULEB128(buf) + n, _ := leb128.DecodeUnsigned(buf) rdr.readBlock(buf, n) case _DW_FORM_block1: @@ -150,10 +151,10 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { rdr.readBlock(buf, 16) case _DW_FORM_sdata: - rdr.i64, _ = util.DecodeSLEB128(buf) + rdr.i64, _ = leb128.DecodeSigned(buf) case _DW_FORM_udata, _DW_FORM_strx: - rdr.u64, _ = util.DecodeULEB128(buf) + rdr.u64, _ = leb128.DecodeUnsigned(buf) case _DW_FORM_string: rdr.str, _ = util.ParseString(buf) diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index 691b57a96d..7922795345 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -7,6 +7,7 @@ import ( "fmt" "io" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -409,7 +410,7 @@ func (sm *StateMachine) next() error { // in the prologue and do nothing with them opnum := sm.dbl.Prologue.StdOpLengths[b-1] for i := 0; i < int(opnum); i++ { - util.DecodeSLEB128(sm.buf) + leb128.DecodeSigned(sm.buf) } fmt.Printf("unknown opcode %d(0x%x), %d arguments, file %s, line %d, address 0x%x\n", b, b, opnum, sm.file, sm.line, sm.address) } @@ -432,7 +433,7 @@ func execSpecialOpcode(sm *StateMachine, instr byte) { } func execExtendedOpcode(sm *StateMachine, buf *bytes.Buffer) { - _, _ = util.DecodeULEB128(buf) + _, _ = leb128.DecodeUnsigned(buf) b, _ := buf.ReadByte() if fn, ok := extendedopcodes[b]; ok { fn(sm, buf) @@ -444,18 +445,18 @@ func copyfn(sm *StateMachine, buf *bytes.Buffer) { } func advancepc(sm *StateMachine, buf *bytes.Buffer) { - addr, _ := util.DecodeULEB128(buf) + addr, _ := leb128.DecodeUnsigned(buf) sm.address += addr * uint64(sm.dbl.Prologue.MinInstrLength) } func advanceline(sm *StateMachine, buf *bytes.Buffer) { - line, _ := util.DecodeSLEB128(buf) + line, _ := leb128.DecodeSigned(buf) sm.line += int(line) sm.lastDelta = int(line) } func setfile(sm *StateMachine, buf *bytes.Buffer) { - i, _ := util.DecodeULEB128(buf) + i, _ := leb128.DecodeUnsigned(buf) if sm.dbl.Prologue.Version < 5 { // in DWARF v5 files are indexed starting from 0, in v4 and prior the index starts at 1 i-- @@ -473,7 +474,7 @@ func setfile(sm *StateMachine, buf *bytes.Buffer) { } func setcolumn(sm *StateMachine, buf *bytes.Buffer) { - c, _ := util.DecodeULEB128(buf) + c, _ := leb128.DecodeUnsigned(buf) sm.column = uint(c) } @@ -510,7 +511,7 @@ func setaddress(sm *StateMachine, buf *bytes.Buffer) { } func setdiscriminator(sm *StateMachine, buf *bytes.Buffer) { - _, _ = util.DecodeULEB128(buf) + _, _ = leb128.DecodeUnsigned(buf) } func definefile(sm *StateMachine, buf *bytes.Buffer) { @@ -528,6 +529,6 @@ func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) { } func setisa(sm *StateMachine, buf *bytes.Buffer) { - c, _ := util.DecodeULEB128(buf) + c, _ := leb128.DecodeUnsigned(buf) sm.isa = c } diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index b728115126..3eb47ef35e 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -13,6 +13,7 @@ import ( "runtime" "testing" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -136,7 +137,7 @@ func TestMultipleSequences(t *testing.T) { write_DW_LNE_set_address := func(addr uint64) { instr.WriteByte(0) - util.EncodeULEB128(instr, 9) // 1 + ptr_size + leb128.EncodeUnsigned(instr, 9) // 1 + ptr_size instr.WriteByte(DW_LINE_set_address) util.WriteUint(instr, binary.LittleEndian, ptrSize, addr) } @@ -147,17 +148,17 @@ func TestMultipleSequences(t *testing.T) { write_DW_LNS_advance_pc := func(off uint64) { instr.WriteByte(DW_LNS_advance_pc) - util.EncodeULEB128(instr, off) + leb128.EncodeUnsigned(instr, off) } write_DW_LNS_advance_line := func(off int64) { instr.WriteByte(DW_LNS_advance_line) - util.EncodeSLEB128(instr, off) + leb128.EncodeSigned(instr, off) } write_DW_LNE_end_sequence := func() { instr.WriteByte(0) - util.EncodeULEB128(instr, 1) + leb128.EncodeUnsigned(instr, 1) instr.WriteByte(DW_LINE_end_sequence) } diff --git a/pkg/dwarf/loclist/dwarf5_loclist.go b/pkg/dwarf/loclist/dwarf5_loclist.go index b0e371d1df..110bd8c8e5 100644 --- a/pkg/dwarf/loclist/dwarf5_loclist.go +++ b/pkg/dwarf/loclist/dwarf5_loclist.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/go-delve/delve/pkg/dwarf/godwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -116,7 +117,7 @@ func (it *loclistsIterator) next() bool { return false case _DW_LLE_base_addressx: - baseIdx, _ := util.DecodeULEB128(it.buf) + baseIdx, _ := leb128.DecodeUnsigned(it.buf) if err != nil { it.err = err return false @@ -126,8 +127,8 @@ func (it *loclistsIterator) next() bool { it.onRange = false case _DW_LLE_startx_endx: - startIdx, _ := util.DecodeULEB128(it.buf) - endIdx, _ := util.DecodeULEB128(it.buf) + startIdx, _ := leb128.DecodeUnsigned(it.buf) + endIdx, _ := leb128.DecodeUnsigned(it.buf) it.readInstr() it.start, it.err = it.debugAddr.Get(startIdx) @@ -137,8 +138,8 @@ func (it *loclistsIterator) next() bool { it.onRange = true case _DW_LLE_startx_length: - startIdx, _ := util.DecodeULEB128(it.buf) - length, _ := util.DecodeULEB128(it.buf) + startIdx, _ := leb128.DecodeUnsigned(it.buf) + length, _ := leb128.DecodeUnsigned(it.buf) it.readInstr() it.start, it.err = it.debugAddr.Get(startIdx) @@ -146,8 +147,8 @@ func (it *loclistsIterator) next() bool { it.onRange = true case _DW_LLE_offset_pair: - off1, _ := util.DecodeULEB128(it.buf) - off2, _ := util.DecodeULEB128(it.buf) + off1, _ := leb128.DecodeUnsigned(it.buf) + off2, _ := leb128.DecodeUnsigned(it.buf) it.readInstr() it.start = it.base + off1 @@ -172,7 +173,7 @@ func (it *loclistsIterator) next() bool { case _DW_LLE_start_length: it.start, it.err = util.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) - length, _ := util.DecodeULEB128(it.buf) + length, _ := leb128.DecodeUnsigned(it.buf) it.readInstr() it.end = it.start + length it.onRange = true @@ -188,6 +189,6 @@ func (it *loclistsIterator) next() bool { } func (it *loclistsIterator) readInstr() { - length, _ := util.DecodeULEB128(it.buf) + length, _ := leb128.DecodeUnsigned(it.buf) it.instr = it.buf.Next(int(length)) } diff --git a/pkg/dwarf/loclist/loclist5_test.go b/pkg/dwarf/loclist/loclist5_test.go index f041d29519..5bf692cbc9 100644 --- a/pkg/dwarf/loclist/loclist5_test.go +++ b/pkg/dwarf/loclist/loclist5_test.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "testing" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) func TestLoclist5(t *testing.T) { @@ -14,7 +14,7 @@ func TestLoclist5(t *testing.T) { p32 := func(n uint32) { binary.Write(buf, binary.LittleEndian, n) } p16 := func(n uint16) { binary.Write(buf, binary.LittleEndian, n) } p8 := func(n uint8) { binary.Write(buf, binary.LittleEndian, n) } - uleb := func(n uint64) { util.EncodeULEB128(buf, n) } + uleb := func(n uint64) { leb128.EncodeUnsigned(buf, n) } p32(0x0) // length (use 0 because it is ignored) p16(0x5) // version diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go index 043942eb4b..b0889ca937 100644 --- a/pkg/dwarf/op/op.go +++ b/pkg/dwarf/op/op.go @@ -7,6 +7,7 @@ import ( "fmt" "io" + "github.com/go-delve/delve/pkg/dwarf/leb128" "github.com/go-delve/delve/pkg/dwarf/util" ) @@ -130,11 +131,11 @@ func PrettyPrint(out io.Writer, instructions []byte, regnumToName func(uint64) s var regnum uint64 switch arg { case 's': - n, _ := util.DecodeSLEB128(in) + n, _ := leb128.DecodeSigned(in) regnum = uint64(n) fmt.Fprintf(out, "%#x ", n) case 'u': - n, _ := util.DecodeULEB128(in) + n, _ := leb128.DecodeUnsigned(in) regnum = n fmt.Fprintf(out, "%#x ", n) case '1': @@ -154,7 +155,7 @@ func PrettyPrint(out io.Writer, instructions []byte, regnumToName func(uint64) s binary.Read(in, binary.LittleEndian, &x) fmt.Fprintf(out, "%#x ", x) case 'B': - sz, _ := util.DecodeULEB128(in) + sz, _ := leb128.DecodeUnsigned(in) data := make([]byte, sz) sz2, _ := in.Read(data) data = data[:sz2] @@ -189,7 +190,7 @@ func (ctxt *context) closeLoc(opcode0 Opcode, piece Piece) error { switch opcode { case DW_OP_piece: - sz, _ := util.DecodeULEB128(ctxt.buf) + sz, _ := leb128.DecodeUnsigned(ctxt.buf) piece.Size = int(sz) ctxt.pieces = append(ctxt.pieces, piece) return nil @@ -222,19 +223,19 @@ func addr(opcode Opcode, ctxt *context) error { func plusuconsts(opcode Opcode, ctxt *context) error { slen := len(ctxt.stack) - num, _ := util.DecodeULEB128(ctxt.buf) + num, _ := leb128.DecodeUnsigned(ctxt.buf) ctxt.stack[slen-1] = ctxt.stack[slen-1] + int64(num) return nil } func consts(opcode Opcode, ctxt *context) error { - num, _ := util.DecodeSLEB128(ctxt.buf) + num, _ := leb128.DecodeSigned(ctxt.buf) ctxt.stack = append(ctxt.stack, num) return nil } func framebase(opcode Opcode, ctxt *context) error { - num, _ := util.DecodeSLEB128(ctxt.buf) + num, _ := leb128.DecodeSigned(ctxt.buf) ctxt.stack = append(ctxt.stack, ctxt.FrameBase+num) return nil } @@ -242,7 +243,7 @@ func framebase(opcode Opcode, ctxt *context) error { func register(opcode Opcode, ctxt *context) error { var regnum uint64 if opcode == DW_OP_regx { - regnum, _ = util.DecodeULEB128(ctxt.buf) + regnum, _ = leb128.DecodeUnsigned(ctxt.buf) } else { regnum = uint64(opcode - DW_OP_reg0) } @@ -252,11 +253,11 @@ func register(opcode Opcode, ctxt *context) error { func bregister(opcode Opcode, ctxt *context) error { var regnum uint64 if opcode == DW_OP_bregx { - regnum, _ = util.DecodeULEB128(ctxt.buf) + regnum, _ = leb128.DecodeUnsigned(ctxt.buf) } else { regnum = uint64(opcode - DW_OP_breg0) } - offset, _ := util.DecodeSLEB128(ctxt.buf) + offset, _ := leb128.DecodeSigned(ctxt.buf) if ctxt.Reg(regnum) == nil { return fmt.Errorf("register %d not available", regnum) } @@ -265,7 +266,7 @@ func bregister(opcode Opcode, ctxt *context) error { } func piece(opcode Opcode, ctxt *context) error { - sz, _ := util.DecodeULEB128(ctxt.buf) + sz, _ := leb128.DecodeUnsigned(ctxt.buf) if len(ctxt.stack) == 0 { // nothing on the stack means this piece is unavailable (padding, @@ -340,7 +341,7 @@ func constns(opcode Opcode, ctxt *context) error { } func constu(opcode Opcode, ctxt *context) error { - num, _ := util.DecodeULEB128(ctxt.buf) + num, _ := leb128.DecodeUnsigned(ctxt.buf) ctxt.stack = append(ctxt.stack, int64(num)) return nil } @@ -516,7 +517,7 @@ func stackvalue(_ Opcode, ctxt *context) error { } func implicitvalue(_ Opcode, ctxt *context) error { - sz, _ := util.DecodeULEB128(ctxt.buf) + sz, _ := leb128.DecodeUnsigned(ctxt.buf) block := make([]byte, sz) n, _ := ctxt.buf.Read(block) if uint64(n) != sz { diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go index 4e199cb26d..c7c98beafe 100644 --- a/pkg/dwarf/util/util.go +++ b/pkg/dwarf/util/util.go @@ -6,125 +6,50 @@ import ( "encoding/binary" "fmt" "io" + + "github.com/go-delve/delve/pkg/dwarf/leb128" ) // ByteReaderWithLen is a io.ByteReader with a Len method. This interface is -// satisified by both bytes.Buffer and bytes.Reader. +// satisfied by both bytes.Buffer and bytes.Reader. +// +// Deprecated: use leb128.Reader. type ByteReaderWithLen interface { io.ByteReader io.Reader Len() int } -// The Little Endian Base 128 format is defined in the DWARF v4 standard, -// section 7.6, page 161 and following. - // DecodeULEB128 decodes an unsigned Little Endian Base 128 // represented number. +// +// Deprecated: use leb128.DecodeUnsigned. func DecodeULEB128(buf ByteReaderWithLen) (uint64, uint32) { - var ( - result uint64 - shift uint64 - length uint32 - ) - - if buf.Len() == 0 { - return 0, 0 - } - - for { - b, err := buf.ReadByte() - if err != nil { - panic("Could not parse ULEB128 value") - } - length++ - - result |= uint64((uint(b) & 0x7f) << shift) - - // If high order bit is 1. - if b&0x80 == 0 { - break - } - - shift += 7 - } - - return result, length + return leb128.DecodeUnsigned(buf) } // DecodeSLEB128 decodes a signed Little Endian Base 128 // represented number. +// +// Deprecated: use leb128.DecodeUnsigned. func DecodeSLEB128(buf ByteReaderWithLen) (int64, uint32) { - var ( - b byte - err error - result int64 - shift uint64 - length uint32 - ) - - if buf.Len() == 0 { - return 0, 0 - } - - for { - b, err = buf.ReadByte() - if err != nil { - panic("Could not parse SLEB128 value") - } - length++ - - result |= int64((int64(b) & 0x7f) << shift) - shift += 7 - if b&0x80 == 0 { - break - } - } - - if (shift < 8*uint64(length)) && (b&0x40 > 0) { - result |= -(1 << shift) - } - - return result, length + return leb128.DecodeSigned(buf) } // EncodeULEB128 encodes x to the unsigned Little Endian Base 128 format // into out. +// +// Deprecated: use leb128.EncodeUnsigned. func EncodeULEB128(out io.ByteWriter, x uint64) { - for { - b := byte(x & 0x7f) - x = x >> 7 - if x != 0 { - b = b | 0x80 - } - out.WriteByte(b) - if x == 0 { - break - } - } + leb128.EncodeUnsigned(out, x) } // EncodeSLEB128 encodes x to the signed Little Endian Base 128 format // into out. +// +// Deprecated: use leb128.EncodeSigned. func EncodeSLEB128(out io.ByteWriter, x int64) { - for { - b := byte(x & 0x7f) - x >>= 7 - - signb := b & 0x40 - - last := false - if (x == 0 && signb == 0) || (x == -1 && signb != 0) { - last = true - } else { - b = b | 0x80 - } - out.WriteByte(b) - - if last { - break - } - } + leb128.EncodeSigned(out, x) } // ParseString reads a null-terminated string from data. diff --git a/pkg/dwarf/util/util_test.go b/pkg/dwarf/util/util_test.go index 63bb4190bf..5d21afccb0 100644 --- a/pkg/dwarf/util/util_test.go +++ b/pkg/dwarf/util/util_test.go @@ -5,64 +5,6 @@ import ( "testing" ) -func TestDecodeULEB128(t *testing.T) { - var leb128 = bytes.NewBuffer([]byte{0xE5, 0x8E, 0x26}) - - n, c := DecodeULEB128(leb128) - if n != 624485 { - t.Fatal("Number was not decoded properly, got: ", n, c) - } - - if c != 3 { - t.Fatal("Count not returned correctly") - } -} - -func TestDecodeSLEB128(t *testing.T) { - sleb128 := bytes.NewBuffer([]byte{0x9b, 0xf1, 0x59}) - - n, c := DecodeSLEB128(sleb128) - if n != -624485 { - t.Fatal("Number was not decoded properly, got: ", n, c) - } -} - -func TestEncodeULEB128(t *testing.T) { - tc := []uint64{0x00, 0x7f, 0x80, 0x8f, 0xffff, 0xfffffff7} - for i := range tc { - var buf bytes.Buffer - EncodeULEB128(&buf, tc[i]) - enc := append([]byte{}, buf.Bytes()...) - buf.Write([]byte{0x1, 0x2, 0x3}) - out, c := DecodeULEB128(&buf) - t.Logf("input %x output %x encoded %x", tc[i], out, enc) - if c != uint32(len(enc)) { - t.Errorf("wrong encode") - } - if out != tc[i] { - t.Errorf("wrong encode") - } - } -} - -func TestEncodeSLEB128(t *testing.T) { - tc := []int64{2, -2, 127, -127, 128, -128, 129, -129} - for i := range tc { - var buf bytes.Buffer - EncodeSLEB128(&buf, tc[i]) - enc := append([]byte{}, buf.Bytes()...) - buf.Write([]byte{0x1, 0x2, 0x3}) - out, c := DecodeSLEB128(&buf) - t.Logf("input %x output %x encoded %x", tc[i], out, enc) - if c != uint32(len(enc)) { - t.Errorf("wrong encode") - } - if out != tc[i] { - t.Errorf("wrong encode") - } - } -} - func TestParseString(t *testing.T) { bstr := bytes.NewBuffer([]byte{'h', 'i', 0x0, 0xFF, 0xCC}) str, _ := ParseString(bstr) From 58fc3931e80e8fabe16ff870dae17106c2f395f0 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Mon, 16 Jan 2023 19:20:20 +0200 Subject: [PATCH 43/72] proc/dwarf: Move util functions to dwarf package (#3252) --- pkg/dwarf/frame/parser.go | 16 +++--- pkg/dwarf/godwarf/addr.go | 6 +-- pkg/dwarf/{util => godwarf}/buf.go | 22 ++++---- pkg/dwarf/godwarf/type.go | 3 +- pkg/dwarf/line/line_parser.go | 10 ++-- pkg/dwarf/line/parse_util.go | 4 +- pkg/dwarf/line/state_machine.go | 4 +- pkg/dwarf/line/state_machine_test.go | 4 +- pkg/dwarf/loclist/dwarf5_loclist.go | 12 ++--- pkg/dwarf/op/op.go | 18 +++---- pkg/dwarf/{util/util.go => parseutil.go} | 50 ++----------------- .../{util/util_test.go => parseutil_test.go} | 8 +-- pkg/proc/bininfo.go | 6 +-- 13 files changed, 60 insertions(+), 103 deletions(-) rename pkg/dwarf/{util => godwarf}/buf.go (79%) rename pkg/dwarf/{util/util.go => parseutil.go} (71%) rename pkg/dwarf/{util/util_test.go => parseutil_test.go} (57%) diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go index e9c638ecdf..e789239c4b 100644 --- a/pkg/dwarf/frame/parser.go +++ b/pkg/dwarf/frame/parser.go @@ -9,8 +9,8 @@ import ( "fmt" "io" + "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) type parsefunc func(*parseContext) parsefunc @@ -154,7 +154,7 @@ func parseCIE(ctx *parseContext) parsefunc { ctx.common.Version, _ = buf.ReadByte() // parse augmentation - ctx.common.Augmentation, _ = util.ParseString(buf) + ctx.common.Augmentation, _ = dwarf.ReadString(buf) if ctx.parsingEHFrame() { if ctx.common.Augmentation == "eh" { @@ -241,21 +241,21 @@ func (ctx *parseContext) readEncodedPtr(addr uint64, buf leb128.Reader, ptrEnc p switch ptrEnc & 0xf { case ptrEncAbs, ptrEncSigned: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, ctx.ptrSize) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, ctx.ptrSize) case ptrEncUleb: ptr, _ = leb128.DecodeUnsigned(buf) case ptrEncUdata2: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, 2) case ptrEncSdata2: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 2) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, 2) ptr = uint64(int16(ptr)) case ptrEncUdata4: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, 4) case ptrEncSdata4: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 4) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, 4) ptr = uint64(int32(ptr)) case ptrEncUdata8, ptrEncSdata8: - ptr, _ = util.ReadUintRaw(buf, binary.LittleEndian, 8) + ptr, _ = dwarf.ReadUintRaw(buf, binary.LittleEndian, 8) case ptrEncSleb: n, _ := leb128.DecodeSigned(buf) ptr = uint64(n) diff --git a/pkg/dwarf/godwarf/addr.go b/pkg/dwarf/godwarf/addr.go index f2c55cd463..2dff81de04 100644 --- a/pkg/dwarf/godwarf/addr.go +++ b/pkg/dwarf/godwarf/addr.go @@ -5,7 +5,7 @@ import ( "encoding/binary" "errors" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" ) // DebugAddrSection represents the debug_addr section of DWARFv5. @@ -22,7 +22,7 @@ func ParseAddr(data []byte) *DebugAddrSection { return nil } r := &DebugAddrSection{data: data} - _, dwarf64, _, byteOrder := util.ReadDwarfLengthVersion(data) + _, dwarf64, _, byteOrder := dwarf.ReadDwarfLengthVersion(data) r.byteOrder = byteOrder data = data[6:] if dwarf64 { @@ -56,5 +56,5 @@ func (addr *DebugAddr) Get(idx uint64) (uint64, error) { return 0, errors.New("debug_addr section not present") } off := idx*uint64(addr.ptrSz) + addr.addrBase - return util.ReadUintRaw(bytes.NewReader(addr.data[off:]), addr.byteOrder, addr.ptrSz) + return dwarf.ReadUintRaw(bytes.NewReader(addr.data[off:]), addr.byteOrder, addr.ptrSz) } diff --git a/pkg/dwarf/util/buf.go b/pkg/dwarf/godwarf/buf.go similarity index 79% rename from pkg/dwarf/util/buf.go rename to pkg/dwarf/godwarf/buf.go index ddb97958e5..7752976f31 100644 --- a/pkg/dwarf/util/buf.go +++ b/pkg/dwarf/godwarf/buf.go @@ -2,18 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Buffered reading and decoding of DWARF data streams. - //lint:file-ignore ST1021 imported file -package util +package godwarf import ( "debug/dwarf" "fmt" ) -// Data buffer being decoded. +// buf is data buffer being decoded. type buf struct { dwarf *dwarf.Data format dataFormat @@ -23,7 +21,7 @@ type buf struct { Err error } -// Data format, other than byte order. This affects the handling of +// Data format, other than byte order. This affects the handling of // certain field formats. type dataFormat interface { // DWARF version number. Zero means unknown. @@ -36,25 +34,27 @@ type dataFormat interface { addrsize() int } -// UnknownFormat is a struct for some parts of DWARF that have no data format, e.g., abbrevs. -type UnknownFormat struct{} +// unknownFormat is a struct for some parts of DWARF that have no data format, e.g., abbrevs. +type unknownFormat struct{} -func (u UnknownFormat) version() int { +func (u unknownFormat) version() int { return 0 } -func (u UnknownFormat) dwarf64() (bool, bool) { +func (u unknownFormat) dwarf64() (bool, bool) { return false, false } -func (u UnknownFormat) addrsize() int { +func (u unknownFormat) addrsize() int { return 0 } -func MakeBuf(d *dwarf.Data, format dataFormat, name string, off dwarf.Offset, data []byte) buf { +// makeBuf creates buf for reading and decoding of DWARF data streams. +func makeBuf(d *dwarf.Data, format dataFormat, name string, off dwarf.Offset, data []byte) buf { return buf{d, format, name, off, data, nil} } +// Uint8 reads an uint8. func (b *buf) Uint8() uint8 { if len(b.data) < 1 { b.error("underflow") diff --git a/pkg/dwarf/godwarf/type.go b/pkg/dwarf/godwarf/type.go index af0e17f5d9..5b6df937bc 100644 --- a/pkg/dwarf/godwarf/type.go +++ b/pkg/dwarf/godwarf/type.go @@ -17,7 +17,6 @@ import ( "strconv" "github.com/go-delve/delve/pkg/dwarf/op" - "github.com/go-delve/delve/pkg/dwarf/util" ) const ( @@ -827,7 +826,7 @@ func readType(d *dwarf.Data, name string, r *dwarf.Reader, off dwarf.Offset, typ // Empty exprloc. f.ByteOffset=0. break } - b := util.MakeBuf(d, util.UnknownFormat{}, "location", 0, loc) + b := makeBuf(d, unknownFormat{}, "location", 0, loc) op_ := op.Opcode(b.Uint8()) switch op_ { case op.DW_OP_plus_uconst: diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index a6bb499544..030689a3a1 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -6,8 +6,8 @@ import ( "path" "strings" + "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) // DebugLinePrologue prologue of .debug_line data. @@ -152,7 +152,7 @@ func parseDebugLinePrologue(dbl *DebugLineInfo, buf *bytes.Buffer) { // parseIncludeDirs2 parses the directory table for DWARF version 2 through 4. func parseIncludeDirs2(info *DebugLineInfo, buf *bytes.Buffer) bool { for { - str, err := util.ParseString(buf) + str, err := dwarf.ReadString(buf) if err != nil { if info.Logf != nil { info.Logf("error reading string: %v", err) @@ -186,7 +186,7 @@ func parseIncludeDirs5(info *DebugLineInfo, buf *bytes.Buffer) bool { info.IncludeDirs = append(info.IncludeDirs, dirEntryFormReader.str) case _DW_FORM_line_strp: buf := bytes.NewBuffer(info.debugLineStr[dirEntryFormReader.u64:]) - dir, _ := util.ParseString(buf) + dir, _ := dwarf.ReadString(buf) info.IncludeDirs = append(info.IncludeDirs, dir) default: info.Logf("unsupported string form %#x", dirEntryFormReader.formCode) @@ -228,7 +228,7 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) entry := new(FileEntry) var err error - entry.Path, err = util.ParseString(buf) + entry.Path, err = dwarf.ReadString(buf) if err != nil { if info.Logf != nil { info.Logf("error reading file entry: %v", err) @@ -299,7 +299,7 @@ func parseFileEntries5(info *DebugLineInfo, buf *bytes.Buffer) bool { p = fileEntryFormReader.str case _DW_FORM_line_strp: buf := bytes.NewBuffer(info.debugLineStr[fileEntryFormReader.u64:]) - p, _ = util.ParseString(buf) + p, _ = dwarf.ReadString(buf) default: info.Logf("unsupported string form %#x", fileEntryFormReader.formCode) } diff --git a/pkg/dwarf/line/parse_util.go b/pkg/dwarf/line/parse_util.go index 4f2e5bbf07..b1f22e5452 100644 --- a/pkg/dwarf/line/parse_util.go +++ b/pkg/dwarf/line/parse_util.go @@ -5,8 +5,8 @@ import ( "encoding/binary" "errors" + "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) const ( @@ -157,7 +157,7 @@ func (rdr *formReader) next(buf *bytes.Buffer) bool { rdr.u64, _ = leb128.DecodeUnsigned(buf) case _DW_FORM_string: - rdr.str, _ = util.ParseString(buf) + rdr.str, _ = dwarf.ReadString(buf) case _DW_FORM_strx3: if buf.Len() < 3 { diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index 7922795345..c7d1b65d6a 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -7,8 +7,8 @@ import ( "fmt" "io" + "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) type Location struct { @@ -503,7 +503,7 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) { } func setaddress(sm *StateMachine, buf *bytes.Buffer) { - addr, err := util.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize) + addr, err := dwarf.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize) if err != nil { panic(err) } diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index 3eb47ef35e..a9ff2b3dfa 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -13,8 +13,8 @@ import ( "runtime" "testing" + pdwarf "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) func slurpGzip(path string) ([]byte, error) { @@ -139,7 +139,7 @@ func TestMultipleSequences(t *testing.T) { instr.WriteByte(0) leb128.EncodeUnsigned(instr, 9) // 1 + ptr_size instr.WriteByte(DW_LINE_set_address) - util.WriteUint(instr, binary.LittleEndian, ptrSize, addr) + pdwarf.WriteUint(instr, binary.LittleEndian, ptrSize, addr) } write_DW_LNS_copy := func() { diff --git a/pkg/dwarf/loclist/dwarf5_loclist.go b/pkg/dwarf/loclist/dwarf5_loclist.go index 110bd8c8e5..666f42f5e8 100644 --- a/pkg/dwarf/loclist/dwarf5_loclist.go +++ b/pkg/dwarf/loclist/dwarf5_loclist.go @@ -5,9 +5,9 @@ import ( "encoding/binary" "fmt" + "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" ) // Dwarf5Reader parses and presents DWARF loclist information for DWARF version 5 and later. @@ -24,7 +24,7 @@ func NewDwarf5Reader(data []byte) *Dwarf5Reader { } r := &Dwarf5Reader{data: data} - _, dwarf64, _, byteOrder := util.ReadDwarfLengthVersion(data) + _, dwarf64, _, byteOrder := dwarf.ReadDwarfLengthVersion(data) r.byteOrder = byteOrder data = data[6:] @@ -161,18 +161,18 @@ func (it *loclistsIterator) next() bool { it.onRange = false case _DW_LLE_base_address: - it.base, it.err = util.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) + it.base, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) it.base += it.staticBase it.onRange = false case _DW_LLE_start_end: - it.start, it.err = util.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) - it.end, it.err = util.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) + it.start, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) + it.end, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) it.readInstr() it.onRange = true case _DW_LLE_start_length: - it.start, it.err = util.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) + it.start, it.err = dwarf.ReadUintRaw(it.buf, it.rdr.byteOrder, it.rdr.ptrSz) length, _ := leb128.DecodeUnsigned(it.buf) it.readInstr() it.end = it.start + length diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go index b0889ca937..a3269f2012 100644 --- a/pkg/dwarf/op/op.go +++ b/pkg/dwarf/op/op.go @@ -8,7 +8,7 @@ import ( "io" "github.com/go-delve/delve/pkg/dwarf/leb128" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" ) // Opcode represent a DWARF stack program instruction. @@ -213,7 +213,7 @@ func callframecfa(opcode Opcode, ctxt *context) error { func addr(opcode Opcode, ctxt *context) error { buf := ctxt.buf.Next(ctxt.ptrSize) - stack, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, ctxt.ptrSize) + stack, err := dwarf.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, ctxt.ptrSize) if err != nil { return err } @@ -297,11 +297,11 @@ func constnu(opcode Opcode, ctxt *context) error { b, err = ctxt.buf.ReadByte() n = uint64(b) case DW_OP_const2u: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2) case DW_OP_const4u: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4) case DW_OP_const8u: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8) default: panic("internal error") } @@ -323,13 +323,13 @@ func constns(opcode Opcode, ctxt *context) error { b, err = ctxt.buf.ReadByte() n = uint64(int64(int8(b))) case DW_OP_const2s: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 2) n = uint64(int64(int16(n))) case DW_OP_const4s: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 4) n = uint64(int64(int32(n))) case DW_OP_const8s: - n, err = util.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8) + n, err = dwarf.ReadUintRaw(ctxt.buf, binary.LittleEndian, 8) default: panic("internal error") } @@ -561,7 +561,7 @@ func deref(op Opcode, ctxt *context) error { return err } - x, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, sz) + x, err := dwarf.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, sz) if err != nil { return err } diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/parseutil.go similarity index 71% rename from pkg/dwarf/util/util.go rename to pkg/dwarf/parseutil.go index c7c98beafe..d8ed2b0e78 100644 --- a/pkg/dwarf/util/util.go +++ b/pkg/dwarf/parseutil.go @@ -1,4 +1,4 @@ -package util +package dwarf import ( "bytes" @@ -6,54 +6,10 @@ import ( "encoding/binary" "fmt" "io" - - "github.com/go-delve/delve/pkg/dwarf/leb128" ) -// ByteReaderWithLen is a io.ByteReader with a Len method. This interface is -// satisfied by both bytes.Buffer and bytes.Reader. -// -// Deprecated: use leb128.Reader. -type ByteReaderWithLen interface { - io.ByteReader - io.Reader - Len() int -} - -// DecodeULEB128 decodes an unsigned Little Endian Base 128 -// represented number. -// -// Deprecated: use leb128.DecodeUnsigned. -func DecodeULEB128(buf ByteReaderWithLen) (uint64, uint32) { - return leb128.DecodeUnsigned(buf) -} - -// DecodeSLEB128 decodes a signed Little Endian Base 128 -// represented number. -// -// Deprecated: use leb128.DecodeUnsigned. -func DecodeSLEB128(buf ByteReaderWithLen) (int64, uint32) { - return leb128.DecodeSigned(buf) -} - -// EncodeULEB128 encodes x to the unsigned Little Endian Base 128 format -// into out. -// -// Deprecated: use leb128.EncodeUnsigned. -func EncodeULEB128(out io.ByteWriter, x uint64) { - leb128.EncodeUnsigned(out, x) -} - -// EncodeSLEB128 encodes x to the signed Little Endian Base 128 format -// into out. -// -// Deprecated: use leb128.EncodeSigned. -func EncodeSLEB128(out io.ByteWriter, x int64) { - leb128.EncodeSigned(out, x) -} - -// ParseString reads a null-terminated string from data. -func ParseString(data *bytes.Buffer) (string, error) { +// ReadString reads a null-terminated string from data. +func ReadString(data *bytes.Buffer) (string, error) { str, err := data.ReadString(0x0) if err != nil { return "", err diff --git a/pkg/dwarf/util/util_test.go b/pkg/dwarf/parseutil_test.go similarity index 57% rename from pkg/dwarf/util/util_test.go rename to pkg/dwarf/parseutil_test.go index 5d21afccb0..c8ea66ca99 100644 --- a/pkg/dwarf/util/util_test.go +++ b/pkg/dwarf/parseutil_test.go @@ -1,13 +1,15 @@ -package util +package dwarf_test import ( "bytes" "testing" + + "github.com/go-delve/delve/pkg/dwarf" ) -func TestParseString(t *testing.T) { +func TestReadString(t *testing.T) { bstr := bytes.NewBuffer([]byte{'h', 'i', 0x0, 0xFF, 0xCC}) - str, _ := ParseString(bstr) + str, _ := dwarf.ReadString(bstr) if str != "hi" { t.Fatalf("String was not parsed correctly %#v", str) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index c96cd9643d..3ad16ebe92 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -23,13 +23,13 @@ import ( "sync" "time" + pdwarf "github.com/go-delve/delve/pkg/dwarf" "github.com/go-delve/delve/pkg/dwarf/frame" "github.com/go-delve/delve/pkg/dwarf/godwarf" "github.com/go-delve/delve/pkg/dwarf/line" "github.com/go-delve/delve/pkg/dwarf/loclist" "github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/dwarf/reader" - "github.com/go-delve/delve/pkg/dwarf/util" "github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/proc/debuginfod" @@ -2029,7 +2029,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB image.runtimeTypeToDIE = make(map[uint64]runtimeTypeDIE) - ctxt := newLoadDebugInfoMapsContext(bi, image, util.ReadUnitVersions(debugInfoBytes)) + ctxt := newLoadDebugInfoMapsContext(bi, image, pdwarf.ReadUnitVersions(debugInfoBytes)) reader := image.DwarfReader() @@ -2217,7 +2217,7 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex var addr uint64 if loc, ok := entry.Val(dwarf.AttrLocation).([]byte); ok { if len(loc) == bi.Arch.PtrSize()+1 && op.Opcode(loc[0]) == op.DW_OP_addr { - addr, _ = util.ReadUintRaw(bytes.NewReader(loc[1:]), binary.LittleEndian, bi.Arch.PtrSize()) + addr, _ = pdwarf.ReadUintRaw(bytes.NewReader(loc[1:]), binary.LittleEndian, bi.Arch.PtrSize()) } } if !cu.isgo { From 2be9cf1fab30221af11151eb8ee3cd014bbacb32 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Mon, 16 Jan 2023 18:20:43 +0100 Subject: [PATCH 44/72] terminal: use exact window size for pager (#3249) Instead of using a fixed 100x30 window size query the operating system for the exact size, also fixes a bug where the last line before calling the pager is repeated twice. --- pkg/terminal/out.go | 21 +++++++++--------- pkg/terminal/out_unix.go | 25 +++++++++++++++++++++ pkg/terminal/out_windows.go | 44 +++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 pkg/terminal/out_unix.go create mode 100644 pkg/terminal/out_windows.go diff --git a/pkg/terminal/out.go b/pkg/terminal/out.go index 7b0cab8300..79dd73f9ae 100644 --- a/pkg/terminal/out.go +++ b/pkg/terminal/out.go @@ -100,6 +100,8 @@ type pagingWriter struct { pager string lastnl bool cancel func() + + lines, columns int } type pagingWriterMode uint8 @@ -108,9 +110,6 @@ const ( pagingWriterNormal pagingWriterMode = iota pagingWriterMaybe pagingWriterPaging - - pagingWriterMaxLines = 30 - pagingWriterColsPerLine = 100 ) func (w *pagingWriter) Write(p []byte) (nn int, err error) { @@ -141,7 +140,7 @@ func (w *pagingWriter) Write(p []byte) (nn int, err error) { w.cmdStdin.Write(w.buf) w.buf = nil w.mode = pagingWriterPaging - return w.cmdStdin.Write(p) + return len(p), nil } else { if len(p) > 0 { w.lastnl = p[len(p)-1] == '\n' @@ -202,17 +201,17 @@ func (w *pagingWriter) PageMaybe(cancel func()) { } w.lastnl = true w.cancel = cancel + w.getWindowSize() } func (w *pagingWriter) largeOutput() bool { - if len(w.buf) > pagingWriterMaxLines*pagingWriterColsPerLine { - return true - } - nl := 0 + lines := 0 + lineStart := 0 for i := range w.buf { - if w.buf[i] == '\n' { - nl++ - if nl > pagingWriterMaxLines { + if i-lineStart > w.columns || w.buf[i] == '\n' { + lineStart = i + lines++ + if lines > w.lines { return true } } diff --git a/pkg/terminal/out_unix.go b/pkg/terminal/out_unix.go new file mode 100644 index 0000000000..7b55f632f3 --- /dev/null +++ b/pkg/terminal/out_unix.go @@ -0,0 +1,25 @@ +//go:build linux || darwin || freebsd +// +build linux darwin freebsd + +package terminal + +import ( + "syscall" + "unsafe" +) + +type winSize struct { + row, col uint16 + xpixel, ypixel uint16 +} + +func (w *pagingWriter) getWindowSize() { + var ws winSize + ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) + if int(ok) < 0 { + w.mode = pagingWriterNormal + return + } + w.lines = int(ws.row) + w.columns = int(ws.col) +} diff --git a/pkg/terminal/out_windows.go b/pkg/terminal/out_windows.go new file mode 100644 index 0000000000..f8c6077f58 --- /dev/null +++ b/pkg/terminal/out_windows.go @@ -0,0 +1,44 @@ +package terminal + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetStdHandle = kernel32.NewProc("GetStdHandle") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type coord struct { + x, y int16 +} + +type smallRect struct { + left, top, right, bottom int16 +} + +type consoleScreenBufferInfo struct { + dwSize coord + dwCursorPosition coord + wAttributes int16 + srWindow smallRect + dwMaximumWindowSize coord +} + +func (w *pagingWriter) getWindowSize() { + hout, _, err := procGetStdHandle.Call(uintptr(uint32(-12 & 0xFFFFFFFF))) // stdout handle + if err != syscall.Errno(0) { + w.mode = pagingWriterNormal + return + } + var sbi consoleScreenBufferInfo + _, _, err = procGetConsoleScreenBufferInfo.Call(uintptr(hout), uintptr(unsafe.Pointer(&sbi))) + if err != syscall.Errno(0) { + w.mode = pagingWriterNormal + return + } + w.columns = int(sbi.srWindow.right - sbi.srWindow.left + 1) + w.lines = int(sbi.srWindow.bottom - sbi.srWindow.top + 1) +} From a01fe7384513843b484012497d7b1c547f10318c Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Tue, 24 Jan 2023 06:56:05 -0800 Subject: [PATCH 45/72] pkg/proc: do not check decl line for FunctionArguments (#3254) Fixes a bug where we cannot get locals (including arguments and return values) from a given scope because the line number state machine ends up in an invalid state because of this parameter being set to false. --- _fixtures/traceprog.go | 13 +++++++++++++ cmd/dlv/dlv_test.go | 25 +++++++++++++++++++++++++ pkg/proc/eval.go | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 _fixtures/traceprog.go diff --git a/_fixtures/traceprog.go b/_fixtures/traceprog.go new file mode 100644 index 0000000000..f8ec72cb2c --- /dev/null +++ b/_fixtures/traceprog.go @@ -0,0 +1,13 @@ +package main + +import "fmt" + +func callme(i int) int { + return i * i +} + +func main() { + j := 0 + j += callme(2) + fmt.Println(j) +} diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index b2f35d85a5..7e134ee2a3 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1036,6 +1036,31 @@ func TestTrace(t *testing.T) { cmd.Wait() } +func TestTrace2(t *testing.T) { + dlvbin, tmpdir := getDlvBin(t) + defer os.RemoveAll(tmpdir) + + expected := []byte("> goroutine(1): main.callme(2)\n>> goroutine(1): => (4)\n") + + fixtures := protest.FindFixturesDir() + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "traceprog.go"), "callme") + rdr, err := cmd.StderrPipe() + assertNoError(err, t, "stderr pipe") + defer rdr.Close() + + cmd.Dir = filepath.Join(fixtures, "buildtest") + + assertNoError(cmd.Start(), t, "running trace") + + output, err := ioutil.ReadAll(rdr) + assertNoError(err, t, "ReadAll") + + if !bytes.Contains(output, expected) { + t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) + } + cmd.Wait() +} + func TestTraceMultipleGoroutines(t *testing.T) { dlvbin, tmpdir := getDlvBin(t) defer os.RemoveAll(tmpdir) diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index 1afa229488..d8348fe6a0 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -474,7 +474,7 @@ func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) { // FunctionArguments returns the name, value, and type of all current function arguments. func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) { - vars, err := scope.Locals(0) + vars, err := scope.Locals(localsNoDeclLineCheck) if err != nil { return nil, err } From 64a9024735c8f3019bcfb1b25de2da42a88acd5c Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Tue, 31 Jan 2023 01:04:55 +0200 Subject: [PATCH 46/72] cmd/dlv, pkg/proc: use T.TempDir in tests (#3256) --- cmd/dlv/dlv_test.go | 100 +++++++++++++------------------------ pkg/proc/core/core_test.go | 6 +-- 2 files changed, 37 insertions(+), 69 deletions(-) diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 7e134ee2a3..48b5922830 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -204,7 +204,7 @@ func testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout return } -func getDlvBin(t *testing.T) (string, string) { +func getDlvBin(t *testing.T) string { // In case this was set in the environment // from getDlvBinEBPF lets clear it here so // we can ensure we don't get build errors @@ -217,17 +217,12 @@ func getDlvBin(t *testing.T) (string, string) { return getDlvBinInternal(t, tags) } -func getDlvBinEBPF(t *testing.T) (string, string) { +func getDlvBinEBPF(t *testing.T) string { return getDlvBinInternal(t, "-tags", "ebpf") } -func getDlvBinInternal(t *testing.T, goflags ...string) (string, string) { - tmpdir, err := ioutil.TempDir("", "TestDlv") - if err != nil { - t.Fatal(err) - } - - dlvbin := filepath.Join(tmpdir, "dlv.exe") +func getDlvBinInternal(t *testing.T, goflags ...string) string { + dlvbin := filepath.Join(t.TempDir(), "dlv.exe") args := append([]string{"build", "-o", dlvbin}, goflags...) args = append(args, "github.com/go-delve/delve/cmd/dlv") @@ -236,16 +231,15 @@ func getDlvBinInternal(t *testing.T, goflags ...string) (string, string) { t.Fatalf("go build -o %v github.com/go-delve/delve/cmd/dlv: %v\n%s", dlvbin, err, string(out)) } - return dlvbin, tmpdir + return dlvbin } // TestOutput verifies that the debug executable is created in the correct path // and removed after exit. func TestOutput(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) - for _, output := range []string{"", "myownname", filepath.Join(tmpdir, "absolute.path")} { + for _, output := range []string{"", "myownname", filepath.Join(t.TempDir(), "absolute.path")} { testOutput(t, dlvbin, output, []string{"exit"}) const hello = "hello world!" @@ -260,8 +254,7 @@ func TestOutput(t *testing.T) { func TestContinue(t *testing.T) { const listenAddr = "127.0.0.1:40573" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr) @@ -301,8 +294,7 @@ func TestChildProcessExitWhenNoDebugInfo(t *testing.T) { t.Skip("test skipped, `ps` not found") } - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) fix := protest.BuildFixture("http_server", noDebugFlags) @@ -345,8 +337,7 @@ func TestChildProcessExitWhenNoDebugInfo(t *testing.T) { func TestRedirect(t *testing.T) { const listenAddr = "127.0.0.1:40573" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) catfixture := filepath.Join(protest.FindFixturesDir(), "cat.go") cmd := exec.Command(dlvbin, "debug", "--headless", "--continue", "--accept-multiclient", "--listen", listenAddr, "-r", catfixture, catfixture) @@ -439,12 +430,10 @@ func TestGeneratedDoc(t *testing.T) { checkAutogenDoc(t, "Documentation/cli/README.md", "_scripts/gen-cli-docs.go", generatedBuf.Bytes()) // Checks gen-usage-docs.go - tempDir, err := ioutil.TempDir(os.TempDir(), "test-gen-doc") - assertNoError(err, t, "TempDir") - defer protest.SafeRemoveAll(tempDir) + tempDir := t.TempDir() cmd := exec.Command("go", "run", "_scripts/gen-usage-docs.go", tempDir) cmd.Dir = projectRoot() - err = cmd.Run() + err := cmd.Run() assertNoError(err, t, "go run _scripts/gen-usage-docs.go") entries, err := ioutil.ReadDir(tempDir) assertNoError(err, t, "ReadDir") @@ -476,8 +465,7 @@ func TestGeneratedDoc(t *testing.T) { } func TestExitInInit(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") exitInit := filepath.Join(protest.FindFixturesDir(), "exit.init") @@ -677,8 +665,7 @@ func TestTypecheckRPC(t *testing.T) { func TestDAPCmd(t *testing.T) { const listenAddr = "127.0.0.1:40575" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) cmd := exec.Command(dlvbin, "dap", "--log-output=dap", "--log", "--listen", listenAddr) stdout, err := cmd.StdoutPipe() @@ -727,8 +714,7 @@ func TestDAPCmd(t *testing.T) { func TestDAPCmdWithNoDebugBinary(t *testing.T) { const listenAddr = "127.0.0.1:40579" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) cmd := exec.Command(dlvbin, "dap", "--log", "--listen", listenAddr) stdout, err := cmd.StdoutPipe() @@ -793,8 +779,7 @@ func newDAPRemoteClient(t *testing.T, addr string, isDlvAttach bool, isMulti boo func TestRemoteDAPClient(t *testing.T) { const listenAddr = "127.0.0.1:40576" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") cmd := exec.Command(dlvbin, "debug", "--headless", "--log-output=dap", "--log", "--listen", listenAddr) @@ -847,8 +832,7 @@ func closeDAPRemoteMultiClient(t *testing.T, c *daptest.Client, expectStatus str func TestRemoteDAPClientMulti(t *testing.T) { const listenAddr = "127.0.0.1:40577" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") cmd := exec.Command(dlvbin, "debug", "--headless", "--accept-multiclient", "--log-output=debugger", "--log", "--listen", listenAddr) @@ -915,8 +899,7 @@ func TestRemoteDAPClientMulti(t *testing.T) { func TestRemoteDAPClientAfterContinue(t *testing.T) { const listenAddr = "127.0.0.1:40578" - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) fixture := protest.BuildFixture("loopprog", 0) cmd := exec.Command(dlvbin, "exec", fixture.Path, "--headless", "--continue", "--accept-multiclient", "--log-output=debugger,dap", "--log", "--listen", listenAddr) @@ -977,8 +960,7 @@ func TestDAPCmdWithClient(t *testing.T) { } defer listener.Close() - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) cmd := exec.Command(dlvbin, "dap", "--log-output=dap", "--log", "--client-addr", listener.Addr().String()) buf := &bytes.Buffer{} @@ -1012,13 +994,12 @@ func TestDAPCmdWithClient(t *testing.T) { } func TestTrace(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) expected := []byte("> goroutine(1): main.foo(99, 9801)\n>> goroutine(1): => (9900)\n") fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1037,13 +1018,12 @@ func TestTrace(t *testing.T) { } func TestTrace2(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) expected := []byte("> goroutine(1): main.callme(2)\n>> goroutine(1): => (4)\n") fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "traceprog.go"), "callme") + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "traceprog.go"), "callme") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1062,8 +1042,7 @@ func TestTrace2(t *testing.T) { } func TestTraceMultipleGoroutines(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) // TODO(derekparker) this test has to be a bit vague to avoid flakyness. // I think a future improvement could be to use regexp captures to match the @@ -1072,7 +1051,7 @@ func TestTraceMultipleGoroutines(t *testing.T) { expected2 := []byte("=> (0)\n") fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "goroutines-trace.go"), "callme") + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "goroutines-trace.go"), "callme") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1102,8 +1081,7 @@ func TestTracePid(t *testing.T) { } } - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) expected := []byte("goroutine(1): main.A()\n>> goroutine(1): => ()\n") @@ -1136,14 +1114,13 @@ func TestTracePid(t *testing.T) { } func TestTraceBreakpointExists(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) fixtures := protest.FindFixturesDir() // We always set breakpoints on some runtime functions at startup, so this would return with // a breakpoints exists error. // TODO: Perhaps we shouldn't be setting these default breakpoints in trace mode, however. - cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "runtime.*") + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "runtime.*") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1163,11 +1140,10 @@ func TestTraceBreakpointExists(t *testing.T) { } func TestTracePrintStack(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(tmpdir, "__debug"), "--stack", "2", filepath.Join(fixtures, "issue573.go"), "foo") + cmd := exec.Command(dlvbin, "trace", "--output", filepath.Join(t.TempDir(), "__debug"), "--stack", "2", filepath.Join(fixtures, "issue573.go"), "foo") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1203,13 +1179,12 @@ func TestTraceEBPF(t *testing.T) { t.Skip("test must be run as root") } - dlvbin, tmpdir := getDlvBinEBPF(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBinEBPF(t) expected := []byte("> (1) main.foo(99, 9801)\n=> \"9900\"") fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") + cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "issue573.go"), "foo") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1243,8 +1218,7 @@ func TestTraceEBPF2(t *testing.T) { t.Skip("test must be run as root") } - dlvbin, tmpdir := getDlvBinEBPF(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBinEBPF(t) expected := []byte(`> (1) main.callme(10) > (1) main.callme(9) @@ -1270,7 +1244,7 @@ func TestTraceEBPF2(t *testing.T) { => "100"`) fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(tmpdir, "__debug"), filepath.Join(fixtures, "ebpf_trace.go"), "main.callme") + cmd := exec.Command(dlvbin, "trace", "--ebpf", "--output", filepath.Join(t.TempDir(), "__debug"), filepath.Join(fixtures, "ebpf_trace.go"), "main.callme") rdr, err := cmd.StderrPipe() assertNoError(err, t, "stderr pipe") defer rdr.Close() @@ -1287,8 +1261,7 @@ func TestTraceEBPF2(t *testing.T) { } func TestDlvTestChdir(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) fixtures := protest.FindFixturesDir() @@ -1320,8 +1293,7 @@ func TestDlvTestChdir(t *testing.T) { } func TestVersion(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) got, err := exec.Command(dlvbin, "version", "-v").CombinedOutput() if err != nil { diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 29a40108bb..3ee4a3e1be 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -201,11 +201,7 @@ func TestSplicedReader(t *testing.T) { func withCoreFile(t *testing.T, name, args string) *proc.Target { // This is all very fragile and won't work on hosts with non-default core patterns. // Might be better to check in the binary and core? - tempDir, err := ioutil.TempDir("", "") - if err != nil { - t.Fatal(err) - } - test.PathsToRemove = append(test.PathsToRemove, tempDir) + tempDir := t.TempDir() var buildFlags test.BuildFlags if buildMode == "pie" { buildFlags = test.BuildModePIE From 011fe673fedf889af14f77f74a97c250f86f9b58 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Tue, 31 Jan 2023 15:30:04 +0200 Subject: [PATCH 47/72] _scripts: fix log.Printf format specifier (#3260) --- _scripts/make.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_scripts/make.go b/_scripts/make.go index dd0e7a4295..3b2fc299bf 100644 --- a/_scripts/make.go +++ b/_scripts/make.go @@ -304,7 +304,7 @@ func buildFlags() []string { var ldFlags string buildSHA, err := getBuildSHA() if err != nil { - log.Printf("error getting build SHA via git: %w", err) + log.Printf("error getting build SHA via git: %v", err) } else { ldFlags = "-X main.Build=" + buildSHA } From 436fed8ec49a19f2f0cfd84dbbd54464588f4969 Mon Sep 17 00:00:00 2001 From: Frederic Branczyk Date: Tue, 31 Jan 2023 17:40:19 +0100 Subject: [PATCH 48/72] godwarf: Allow extracting a DWARF entry's field (#3258) Previously it was only possible to extract a value of type `any` using an attribute name. This poses challenges when fields are allowed to have different classes, and it is ambiguous how to handle them. --- pkg/dwarf/godwarf/tree.go | 12 ++++++++++-- pkg/proc/fncall.go | 20 ++++++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pkg/dwarf/godwarf/tree.go b/pkg/dwarf/godwarf/tree.go index 379da1a9a5..b9be92ba1f 100644 --- a/pkg/dwarf/godwarf/tree.go +++ b/pkg/dwarf/godwarf/tree.go @@ -11,14 +11,22 @@ import ( // entry specified by DW_AT_abstract_origin will be searched recursively. type Entry interface { Val(dwarf.Attr) interface{} + AttrField(dwarf.Attr) *dwarf.Field } type compositeEntry []*dwarf.Entry func (ce compositeEntry) Val(attr dwarf.Attr) interface{} { + if f := ce.AttrField(attr); f != nil { + return f.Val + } + return nil +} + +func (ce compositeEntry) AttrField(a dwarf.Attr) *dwarf.Field { for _, e := range ce { - if r := e.Val(attr); r != nil { - return r + if f := e.AttrField(a); f != nil { + return f } } return nil diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 5f378a00c9..31a0332b8d 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -1248,9 +1248,17 @@ func debugCallProtocolReg(archName string, version int) (uint64, bool) { } } -type fakeEntry map[dwarf.Attr]interface{} +type fakeEntry map[dwarf.Attr]*dwarf.Field func (e fakeEntry) Val(attr dwarf.Attr) interface{} { + if e[attr] == nil { + return nil + } + + return e[attr].Val +} + +func (e fakeEntry) AttrField(attr dwarf.Attr) *dwarf.Field { return e[attr] } @@ -1273,11 +1281,11 @@ func regabiMallocgcWorkaround(bi *BinaryInfo) ([]*godwarf.Tree, error) { if err1 != nil { return nil } - var e fakeEntry = map[dwarf.Attr]interface{}{ - dwarf.AttrName: name, - dwarf.AttrType: typ.Common().Offset, - dwarf.AttrLocation: []byte{byte(op.DW_OP_reg0) + byte(reg)}, - dwarf.AttrVarParam: isret, + var e fakeEntry = map[dwarf.Attr]*dwarf.Field{ + dwarf.AttrName: &dwarf.Field{Attr: dwarf.AttrName, Val: name, Class: dwarf.ClassString}, + dwarf.AttrType: &dwarf.Field{Attr: dwarf.AttrType, Val: typ.Common().Offset, Class: dwarf.ClassReference}, + dwarf.AttrLocation: &dwarf.Field{Attr: dwarf.AttrLocation, Val: []byte{byte(op.DW_OP_reg0) + byte(reg)}, Class: dwarf.ClassBlock}, + dwarf.AttrVarParam: &dwarf.Field{Attr: dwarf.AttrVarParam, Val: isret, Class: dwarf.ClassFlag}, } return &godwarf.Tree{ From 98f6d0b619f5301afc7584c41b606ca5b73e13aa Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Thu, 2 Feb 2023 02:11:31 -0800 Subject: [PATCH 49/72] cmd/dlv: fix exit status for trace subcommand (#3263) We currently return an error when the command exits because a process exited error is always returned. Detect this like we do in other code paths to return a 0 exit code instead of nonzero which indicates the command itself failed and can confuse other tools. --- cmd/dlv/cmds/commands.go | 4 +++- cmd/dlv/dlv_test.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 6813de0b82..39c0b3ea5a 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -732,7 +732,9 @@ func traceCmd(cmd *cobra.Command, args []string) { err = cmds.Call("continue", t) if err != nil { fmt.Fprintln(os.Stderr, err) - return 1 + if !strings.Contains(err.Error(), "exited") { + return 1 + } } return 0 }() diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 48b5922830..5b01a51b98 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1038,7 +1038,7 @@ func TestTrace2(t *testing.T) { if !bytes.Contains(output, expected) { t.Fatalf("expected:\n%s\ngot:\n%s", string(expected), string(output)) } - cmd.Wait() + assertNoError(cmd.Wait(), t, "cmd.Wait()") } func TestTraceMultipleGoroutines(t *testing.T) { From 4303ae45a8e2996b30d2318f239677a771aef9c1 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Thu, 2 Feb 2023 14:46:19 +0200 Subject: [PATCH 50/72] service/dap: correct typos in comments (#3264) --- service/dap/server.go | 6 +++--- service/dap/server_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/service/dap/server.go b/service/dap/server.go index 3c953ddf72..d0df77b92c 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -405,7 +405,7 @@ func (s *Session) Close() { // can send client notifications. // Unless Stop() was called after read loop in ServeDAPCodec() // returned, this will result in a closed connection error - // on next read, breaking out the read loop andd + // on next read, breaking out the read loop and // allowing the run goroutinee to exit. // This connection is closed here and in serveDAPCodec(). // If this was a forced shutdown, external stop logic can close this first. @@ -1775,7 +1775,7 @@ func (s *Session) onAttachRequest(request *dap.AttachRequest) { s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}}) // TODO(polina): support SupportSuspendDebuggee when available } else if s.config.Debugger.AttachPid > 0 { - // User can stop debugger with process or leave the processs running + // User can stop debugger with process or leave the process running s.send(&dap.CapabilitiesEvent{Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{SupportTerminateDebuggee: true}}}) } // else program was launched and the only option will be to stop both default: @@ -2743,7 +2743,7 @@ func (s *Session) doCall(goid, frame int, expr string) (*api.DebuggerState, []*p // After the call is done, the goroutine where we injected the call should // return to the original stopped line with return values. However, // it is not guaranteed to be selected due to the possibility of the - // of simultaenous breakpoints. Therefore, we check all threads. + // of simultaneous breakpoints. Therefore, we check all threads. var retVars []*proc.Variable found := false for _, t := range state.Threads { diff --git a/service/dap/server_test.go b/service/dap/server_test.go index e7469e06f3..4e8b7a61ed 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -6739,7 +6739,7 @@ func (s *MultiClientCloseServerMock) verifyStopped(t *testing.T) { } // TestAttachRemoteMultiClientDisconnect tests that that remote attach doesn't take down -// the server in multi-client mode unless terminateDebuggee is explicitely set. +// the server in multi-client mode unless terminateDebuggee is explicitly set. func TestAttachRemoteMultiClientDisconnect(t *testing.T) { closingClientSessionOnly := fmt.Sprintf(daptest.ClosingClient, "halted") detachingAndTerminating := "Detaching and terminating target process" From e11e8858ea87dda6f70308e61c80cabfa67ae0cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Geisend=C3=B6rfer?= Date: Mon, 13 Feb 2023 15:27:14 +0100 Subject: [PATCH 51/72] Documentation: watch example for arbitrary address (#3268) Fixes #3266 --- Documentation/cli/README.md | 3 ++- pkg/terminal/command.go | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index bb3224d502..8d49e9dfa8 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -699,8 +699,9 @@ Set watchpoint. The memory location is specified with the same expression language used by 'print', for example: watch v + watch -w *(*int)(0x1400007c018) -will watch the address of variable 'v'. +will watch the address of variable 'v' and writes to an int at addr '0x1400007c018'. Note that writes that do not change the value of the watched memory address might not be reported. diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index c3f802ac24..3553e78eb5 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -144,8 +144,9 @@ See also: "help on", "help cond" and "help clear"`}, The memory location is specified with the same expression language used by 'print', for example: watch v + watch -w *(*int)(0x1400007c018) -will watch the address of variable 'v'. +will watch the address of variable 'v' and writes to an int at addr '0x1400007c018'. Note that writes that do not change the value of the watched memory address might not be reported. From 19368d8ed3590f7f17232f655d85688def474a9f Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 14 Feb 2023 16:34:34 +0100 Subject: [PATCH 52/72] dwarf: fix broken external url to arm dwarf mapping (#3272) --- pkg/dwarf/regnum/arm64.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/dwarf/regnum/arm64.go b/pkg/dwarf/regnum/arm64.go index 3f7805b93c..80aff3f254 100644 --- a/pkg/dwarf/regnum/arm64.go +++ b/pkg/dwarf/regnum/arm64.go @@ -5,9 +5,9 @@ import ( ) // The mapping between hardware registers and DWARF registers is specified -// in the DWARF for the ARM® Architecture page 7, -// Table 1 -// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0040b/IHI0040B_aadwarf.pdf +// in the DWARF for the DWARF for the Arm® 64-bit Architecture (AArch64), +// Section 4, Table 1 +// https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#4arm-specific-dwarf-definitions const ( ARM64_X0 = 0 // X1 through X30 follow From 95e33edf530af5bbd0fe7bf554bf0edb6f88240a Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 14 Feb 2023 18:32:13 +0100 Subject: [PATCH 53/72] proc/native: fix syscalls to SYS_PROCESS_VM_READV (and WRITEV) (#3273) Per https://pkg.go.dev/unsafe#Pointer conversions from unsafe.Pointer to uintptr are only safe in limited circumstances. In particular only conversions made in the syscall call are pinned. Additionally add a call to runtime.KeepAlive to mitigate the bug described in: https://github.com/golang/go/issues/58351 --- pkg/proc/native/ptrace_freebsd.go | 5 ++++- pkg/proc/native/ptrace_linux_386.go | 8 ++------ pkg/proc/native/ptrace_linux_64bit.go | 8 ++------ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/pkg/proc/native/ptrace_freebsd.go b/pkg/proc/native/ptrace_freebsd.go index 93cc90c22b..6ccffe6b84 100644 --- a/pkg/proc/native/ptrace_freebsd.go +++ b/pkg/proc/native/ptrace_freebsd.go @@ -9,8 +9,9 @@ package native import "C" import ( - "unsafe" + "runtime" "syscall" + "unsafe" sys "golang.org/x/sys/unix" ) @@ -60,11 +61,13 @@ func ptraceGetLwpInfo(wpid int) (info sys.PtraceLwpInfoStruct, err error) { // id may be a PID or an LWPID func ptraceReadData(id int, addr uintptr, data []byte) (n int, err error) { + defer runtime.KeepAlive(&data[0]) // PIN return sys.PtraceIO(sys.PIOD_READ_D, id, addr, data, len(data)) } // id may be a PID or an LWPID func ptraceWriteData(id int, addr uintptr, data []byte) (n int, err error) { + defer runtime.KeepAlive(&data[0]) // PIN return sys.PtraceIO(sys.PIOD_WRITE_D, id, addr, data, len(data)) } diff --git a/pkg/proc/native/ptrace_linux_386.go b/pkg/proc/native/ptrace_linux_386.go index 82e97878b3..3139e3d3d6 100644 --- a/pkg/proc/native/ptrace_linux_386.go +++ b/pkg/proc/native/ptrace_linux_386.go @@ -69,9 +69,7 @@ func processVmRead(tid int, addr uintptr, data []byte) (int, error) { len_iov := uint32(len(data)) local_iov := sys.Iovec{Base: &data[0], Len: len_iov} remote_iov := remoteIovec{base: addr, len: uintptr(len_iov)} - p_local := uintptr(unsafe.Pointer(&local_iov)) - p_remote := uintptr(unsafe.Pointer(&remote_iov)) - n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0) + n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), uintptr(unsafe.Pointer(&local_iov)), 1, uintptr(unsafe.Pointer(&remote_iov)), 1, 0) if err != syscall.Errno(0) { return 0, err } @@ -83,9 +81,7 @@ func processVmWrite(tid int, addr uintptr, data []byte) (int, error) { len_iov := uint32(len(data)) local_iov := sys.Iovec{Base: &data[0], Len: len_iov} remote_iov := remoteIovec{base: addr, len: uintptr(len_iov)} - p_local := uintptr(unsafe.Pointer(&local_iov)) - p_remote := uintptr(unsafe.Pointer(&remote_iov)) - n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0) + n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), uintptr(unsafe.Pointer(&local_iov)), 1, uintptr(unsafe.Pointer(&remote_iov)), 1, 0) if err != syscall.Errno(0) { return 0, err } diff --git a/pkg/proc/native/ptrace_linux_64bit.go b/pkg/proc/native/ptrace_linux_64bit.go index f4ab69c8b2..542645bdd4 100644 --- a/pkg/proc/native/ptrace_linux_64bit.go +++ b/pkg/proc/native/ptrace_linux_64bit.go @@ -15,9 +15,7 @@ func processVmRead(tid int, addr uintptr, data []byte) (int, error) { len_iov := uint64(len(data)) local_iov := sys.Iovec{Base: &data[0], Len: len_iov} remote_iov := remoteIovec{base: addr, len: uintptr(len_iov)} - p_local := uintptr(unsafe.Pointer(&local_iov)) - p_remote := uintptr(unsafe.Pointer(&remote_iov)) - n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0) + n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), uintptr(unsafe.Pointer(&local_iov)), 1, uintptr(unsafe.Pointer(&remote_iov)), 1, 0) if err != syscall.Errno(0) { return 0, err } @@ -29,9 +27,7 @@ func processVmWrite(tid int, addr uintptr, data []byte) (int, error) { len_iov := uint64(len(data)) local_iov := sys.Iovec{Base: &data[0], Len: len_iov} remote_iov := remoteIovec{base: addr, len: uintptr(len_iov)} - p_local := uintptr(unsafe.Pointer(&local_iov)) - p_remote := uintptr(unsafe.Pointer(&remote_iov)) - n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0) + n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), uintptr(unsafe.Pointer(&local_iov)), 1, uintptr(unsafe.Pointer(&remote_iov)), 1, 0) if err != syscall.Errno(0) { return 0, err } From bc5c0d4a9bbe42ebc3670d89b793ba373377bead Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 14 Feb 2023 18:36:00 +0100 Subject: [PATCH 54/72] _scripts: Update staticcheck (#3270) --- _scripts/test_linux.sh | 2 +- cmd/dlv/dlv_test.go | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/_scripts/test_linux.sh b/_scripts/test_linux.sh index 4b18efc2d1..44ca5d7b44 100755 --- a/_scripts/test_linux.sh +++ b/_scripts/test_linux.sh @@ -44,7 +44,7 @@ GOPATH=$(pwd)/go export GOPATH export PATH=$PATH:$GOROOT/bin:$GOPATH/bin go version -go install honnef.co/go/tools/cmd/staticcheck@2022.1.2 || true +go install honnef.co/go/tools/cmd/staticcheck@2023.1 || true uname -a echo "$PATH" diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 5b01a51b98..e1c3006528 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -1311,10 +1311,6 @@ func TestStaticcheck(t *testing.T) { if err != nil { t.Skip("staticcheck not installed") } - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 20) { - //TODO(aarzilli): remove this when there is a version of staticcheck that can support go1.20 - t.Skip("staticcheck does not support go1.20 currently") - } // default checks minus SA1019 which complains about deprecated identifiers, which change between versions of Go. args := []string{"-tests=false", "-checks=all,-SA1019,-ST1000,-ST1003,-ST1016,-S1021,-ST1023", "github.com/go-delve/delve/..."} // * SA1019 is disabled because new deprecations get added on every version From f6e6eadd2203affcc59f605a0692b972023fae40 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Tue, 14 Feb 2023 19:36:24 +0200 Subject: [PATCH 55/72] pkg/proc,service/test: refactor to strings.ReplaceAll (#3269) Use strings.ReplaceAll instead of strings.Replace with -1 as the last argument. --- pkg/proc/bininfo.go | 10 +++++----- pkg/proc/native/proc_freebsd.go | 2 +- pkg/proc/proc_test.go | 4 ++-- pkg/proc/scope_test.go | 2 +- pkg/proc/test/support.go | 2 +- service/test/common_test.go | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 3ad16ebe92..dd92fe58d8 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -1899,8 +1899,8 @@ func (bi *BinaryInfo) macOSDebugFrameBugWorkaround() { // Do not call this function directly it isn't able to deal correctly with package paths func (bi *BinaryInfo) findType(name string) (godwarf.Type, error) { - name = strings.Replace(name, "interface{", "interface {", -1) - name = strings.Replace(name, "struct{", "struct {", -1) + name = strings.ReplaceAll(name, "interface{", "interface {") + name = strings.ReplaceAll(name, "struct{", "struct {") ref, found := bi.types[name] if !found { return nil, reader.ErrTypeNotFound @@ -2096,7 +2096,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB } gopkg, _ := entry.Val(godwarf.AttrGoPackageName).(string) if cu.isgo && gopkg != "" { - bi.PackageMap[gopkg] = append(bi.PackageMap[gopkg], escapePackagePath(strings.Replace(cu.name, "\\", "/", -1))) + bi.PackageMap[gopkg] = append(bi.PackageMap[gopkg], escapePackagePath(strings.ReplaceAll(cu.name, "\\", "/"))) } image.compileUnits = append(image.compileUnits, cu) if entry.Children { @@ -2515,7 +2515,7 @@ func escapePackagePath(pkg string) string { if slash < 0 { slash = 0 } - return pkg[:slash] + strings.Replace(pkg[slash:], ".", "%2e", -1) + return pkg[:slash] + strings.ReplaceAll(pkg[slash:], ".", "%2e") } // Looks up symbol (either functions or global variables) at address addr. @@ -2566,7 +2566,7 @@ func (bi *BinaryInfo) ListPackagesBuildInfo(includeFiles bool) []*PackageBuildIn continue } - ip := strings.Replace(cu.name, "\\", "/", -1) + ip := strings.ReplaceAll(cu.name, "\\", "/") if _, ok := m[ip]; !ok { path := cu.lineInfo.FirstFile() if ext := filepath.Ext(path); ext != ".go" && ext != ".s" { diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index be659065f4..44fbc6b6eb 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -146,7 +146,7 @@ func initialize(dbp *nativeProcess) error { comm, _ := C.find_command_name(C.int(dbp.pid)) defer C.free(unsafe.Pointer(comm)) comm_str := C.GoString(comm) - dbp.os.comm = strings.Replace(string(comm_str), "%", "%%", -1) + dbp.os.comm = strings.ReplaceAll(string(comm_str), "%", "%%") return nil } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 5ee67eee30..adb3635aad 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -4795,7 +4795,7 @@ func TestListPackagesBuildInfo(t *testing.T) { if fidx < 0 { continue } - if !strings.HasSuffix(strings.Replace(pkg.DirectoryPath, "\\", "/", -1), pkg.ImportPath[fidx:]) { + if !strings.HasSuffix(strings.ReplaceAll(pkg.DirectoryPath, "\\", "/"), pkg.ImportPath[fidx:]) { t.Errorf("unexpected suffix: %q %q", pkg.ImportPath, pkg.DirectoryPath) } } @@ -5872,7 +5872,7 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { withTestProcess("asmnilptr/", t, func(p *proc.Target, fixture protest.Fixture) { f := filepath.Join(fixture.BuildDir, asmfile) - f = strings.Replace(f, "\\", "/", -1) + f = strings.ReplaceAll(f, "\\", "/") setFileBreakpoint(p, t, f, 5) t.Logf("first continue") assertNoError(p.Continue(), t, "Continue()") diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go index a477281e62..800316906d 100644 --- a/pkg/proc/scope_test.go +++ b/pkg/proc/scope_test.go @@ -293,7 +293,7 @@ func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testi func (varCheck *varCheck) check(line int, v *proc.Variable, t *testing.T, ctxt string) { typ := v.DwarfType.String() - typ = strings.Replace(typ, " ", "", -1) + typ = strings.ReplaceAll(typ, " ", "") if typ != varCheck.typ { t.Errorf("%d: wrong type for %s (%s), got %s, expected %s", line, v.Name, ctxt, typ, varCheck.typ) } diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index a9261e1ada..d58c93381c 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -195,7 +195,7 @@ func BuildFixture(name string, flags BuildFlags) Fixture { source = filepath.ToSlash(source) sympath, err := filepath.EvalSymlinks(source) if err == nil { - source = strings.Replace(sympath, "\\", "/", -1) + source = strings.ReplaceAll(sympath, "\\", "/") } absdir, _ := filepath.Abs(dir) diff --git a/service/test/common_test.go b/service/test/common_test.go index 473e3cc62b..ef6338e0f4 100644 --- a/service/test/common_test.go +++ b/service/test/common_test.go @@ -56,7 +56,7 @@ func testProgPath(t *testing.T, name string) string { } sympath, err := filepath.EvalSymlinks(fp) if err == nil { - fp = strings.Replace(sympath, "\\", "/", -1) + fp = strings.ReplaceAll(sympath, "\\", "/") } return fp } From 7c835342d331936a204183c806c5fc815774f066 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 14 Feb 2023 18:38:15 +0100 Subject: [PATCH 56/72] *: remove some code for Go 1.12 or earlier (#3271) Delve no longer compiles on Go1.12 and earlier, we don't test it on these versions and they are 4 years old and unsupported. Remove some code related to Go 1.12 and earlier, mostly from tests. --- pkg/proc/fncall.go | 15 ++-- pkg/proc/proc_test.go | 126 ++++++------------------------ pkg/proc/variables_test.go | 11 +-- service/test/integration2_test.go | 39 --------- 4 files changed, 31 insertions(+), 160 deletions(-) diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index 31a0332b8d..5e9b3bc67d 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -152,6 +152,10 @@ func EvalExpressionWithCalls(grp *TargetGroup, g *G, expr string, retLoadCfg Loa if !t.SupportsFunctionCalls() { return errFuncCallUnsupportedBackend } + producer := bi.Producer() + if producer == "" || !goversion.ProducerAfterOrEqual(bi.Producer(), 1, 12) { + return errFuncCallUnsupported + } // check that the target goroutine is running if g == nil { @@ -641,9 +645,6 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i return 0, nil, fmt.Errorf("DWARF read error: %v", err) } - producer := bi.Producer() - trustArgOrder := producer != "" && goversion.ProducerAfterOrEqual(bi.Producer(), 1, 12) - if bi.regabi && fn.cu.optimized && fn.Name != "runtime.mallocgc" { // Debug info for function arguments on optimized functions is currently // too incomplete to attempt injecting calls to arbitrary optimized @@ -672,7 +673,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i if bi.regabi { formalArg, err = funcCallArgRegABI(fn, bi, entry, argname, typ, &argFrameSize) } else { - formalArg, err = funcCallArgOldABI(fn, bi, entry, argname, typ, trustArgOrder, &argFrameSize) + formalArg, err = funcCallArgOldABI(fn, bi, entry, argname, typ, &argFrameSize) } if err != nil { return 0, nil, err @@ -707,7 +708,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i return argFrameSize, formalArgs, nil } -func funcCallArgOldABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argname string, typ godwarf.Type, trustArgOrder bool, pargFrameSize *int64) (*funcCallArg, error) { +func funcCallArgOldABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argname string, typ godwarf.Type, pargFrameSize *int64) (*funcCallArg, error) { const CFA = 0x1000 var off int64 @@ -726,10 +727,6 @@ func funcCallArgOldABI(fn *Function, bi *BinaryInfo, entry reader.Variable, argn off -= CFA } if err != nil { - if !trustArgOrder { - return nil, err - } - // With Go version 1.12 or later we can trust that the arguments appear // in the same order as declared, which means we can calculate their // address automatically. diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index adb3635aad..5a27329b31 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -2278,19 +2278,11 @@ func TestNextPanicAndDirectCall(t *testing.T) { // Next should not step into a deferred function if it is called // directly, only if it is called through a panic or a deferreturn. // Here we test the case where the function is called by a panic - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { - testseq("defercall", contNext, []nextTest{ - {15, 16}, - {16, 17}, - {17, 18}, - {18, 6}}, "main.callAndPanic2", t) - } else { - testseq("defercall", contNext, []nextTest{ - {15, 16}, - {16, 17}, - {17, 18}, - {18, 5}}, "main.callAndPanic2", t) - } + testseq("defercall", contNext, []nextTest{ + {15, 16}, + {16, 17}, + {17, 18}, + {18, 6}}, "main.callAndPanic2", t) } func TestStepCall(t *testing.T) { @@ -2321,71 +2313,26 @@ func TestStepCallPtr(t *testing.T) { func TestStepReturnAndPanic(t *testing.T) { // Tests that Step works correctly when returning from functions // and when a deferred function is called when panic'ing. - switch { - case goversion.VersionAfterOrEqual(runtime.Version(), 1, 11): - testseq("defercall", contStep, []nextTest{ - {17, 6}, - {6, 7}, - {7, 18}, - {18, 6}, - {6, 7}}, "", t) - case goversion.VersionAfterOrEqual(runtime.Version(), 1, 10): - testseq("defercall", contStep, []nextTest{ - {17, 5}, - {5, 6}, - {6, 7}, - {7, 18}, - {18, 5}, - {5, 6}, - {6, 7}}, "", t) - case goversion.VersionAfterOrEqual(runtime.Version(), 1, 9): - testseq("defercall", contStep, []nextTest{ - {17, 5}, - {5, 6}, - {6, 7}, - {7, 17}, - {17, 18}, - {18, 5}, - {5, 6}, - {6, 7}}, "", t) - default: - testseq("defercall", contStep, []nextTest{ - {17, 5}, - {5, 6}, - {6, 7}, - {7, 18}, - {18, 5}, - {5, 6}, - {6, 7}}, "", t) - } + testseq("defercall", contStep, []nextTest{ + {17, 6}, + {6, 7}, + {7, 18}, + {18, 6}, + {6, 7}}, "", t) } func TestStepDeferReturn(t *testing.T) { // Tests that Step works correctly when a deferred function is // called during a return. - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { - testseq("defercall", contStep, []nextTest{ - {11, 6}, - {6, 7}, - {7, 12}, - {12, 13}, - {13, 6}, - {6, 7}, - {7, 13}, - {13, 28}}, "", t) - } else { - testseq("defercall", contStep, []nextTest{ - {11, 5}, - {5, 6}, - {6, 7}, - {7, 12}, - {12, 13}, - {13, 5}, - {5, 6}, - {6, 7}, - {7, 13}, - {13, 28}}, "", t) - } + testseq("defercall", contStep, []nextTest{ + {11, 6}, + {6, 7}, + {7, 12}, + {12, 13}, + {13, 6}, + {6, 7}, + {7, 13}, + {13, 28}}, "", t) } func TestStepIgnorePrivateRuntime(t *testing.T) { @@ -2410,27 +2357,8 @@ func TestStepIgnorePrivateRuntime(t *testing.T) { {21, 14}, {14, 15}, {15, 22}}, "", t) - case goversion.VersionAfterOrEqual(runtime.Version(), 1, 10): - testseq("teststepprog", contStep, []nextTest{ - {21, 13}, - {13, 14}, - {14, 15}, - {15, 22}}, "", t) - case goversion.VersionAfterOrEqual(runtime.Version(), 1, 7): - testseq("teststepprog", contStep, []nextTest{ - {21, 13}, - {13, 14}, - {14, 15}, - {15, 14}, - {14, 17}, - {17, 22}}, "", t) default: - testseq("teststepprog", contStep, []nextTest{ - {21, 13}, - {13, 14}, - {14, 15}, - {15, 17}, - {17, 22}}, "", t) + panic("too old") } } @@ -2790,15 +2718,9 @@ func TestStepOutPanicAndDirectCall(t *testing.T) { // StepOut should not step into a deferred function if it is called // directly, only if it is called through a panic. // Here we test the case where the function is called by a panic - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { - testseq2(t, "defercall", "", []seqTest{ - {contContinue, 17}, - {contStepout, 6}}) - } else { - testseq2(t, "defercall", "", []seqTest{ - {contContinue, 17}, - {contStepout, 5}}) - } + testseq2(t, "defercall", "", []seqTest{ + {contContinue, 17}, + {contStepout, 6}}) } func TestWorkDir(t *testing.T) { diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 9af5956b04..53d3fdb28d 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -1051,11 +1051,6 @@ func TestPackageRenames(t *testing.T) { {"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil}, } - testcases1_8 := []varTest{ - // before 1.9 embedded struct fields have fieldname == type - {"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil}, - } - testcases1_9 := []varTest{ {"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil}, } @@ -1074,11 +1069,7 @@ func TestPackageRenames(t *testing.T) { assertNoError(p.Continue(), t, "Continue() returned an error") testPackageRenamesHelper(t, p, testcases) - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 9) { - testPackageRenamesHelper(t, p, testcases1_9) - } else { - testPackageRenamesHelper(t, p, testcases1_8) - } + testPackageRenamesHelper(t, p, testcases1_9) if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) { testPackageRenamesHelper(t, p, testcases1_13) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 5550f7a3c0..a5478131fe 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -2000,19 +2000,6 @@ func TestClientServer_StepOutReturn(t *testing.T) { stridx := 0 numidx := 1 - if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) { - // in 1.11 and earlier the order of return values in DWARF is - // unspecified, in 1.11 and later it follows the order of definition - // specified by the user - for i := range ret { - if ret[i].Name == "str" { - stridx = i - numidx = 1 - i - break - } - } - } - if ret[stridx].Name != "str" { t.Fatalf("(str) bad return value name %s", ret[stridx].Name) } @@ -2139,32 +2126,6 @@ func TestClientServerFunctionCall(t *testing.T) { }) } -func TestClientServerFunctionCallBadPos(t *testing.T) { - protest.MustSupportFunctionCalls(t, testBackend) - if goversion.VersionAfterOrEqual(runtime.Version(), 1, 12) { - t.Skip("this is a safe point for Go 1.12") - } - withTestClient2("fncall", t, func(c service.Client) { - loc, err := c.FindLocation(api.EvalScope{GoroutineID: -1}, "fmt/print.go:649", false, nil) - assertNoError(err, t, "could not find location") - - _, err = c.CreateBreakpoint(&api.Breakpoint{File: loc[0].File, Line: loc[0].Line}) - assertNoError(err, t, "CreateBreakpoin") - - state := <-c.Continue() - assertNoError(state.Err, t, "Continue()") - - state = <-c.Continue() - assertNoError(state.Err, t, "Continue()") - - c.SetReturnValuesLoadConfig(&normalLoadConfig) - state, err = c.Call(-1, "main.call1(main.zero, main.zero)", false) - if err == nil || err.Error() != "call not at safe point" { - t.Fatalf("wrong error or no error: %v", err) - } - }) -} - func TestClientServerFunctionCallPanic(t *testing.T) { protest.MustSupportFunctionCalls(t, testBackend) withTestClient2("fncall", t, func(c service.Client) { From 260229b979c73576bb7e68f90b622eff211cb117 Mon Sep 17 00:00:00 2001 From: Gregor Noczinski Date: Tue, 14 Feb 2023 18:46:35 +0100 Subject: [PATCH 57/72] terminal/logflags: Added `SetLoggerFactory(LoggerFactory)` (#3257) * terminal/logflags: Added `SetLoggerFactory(LoggerFactory)` This change will enable people who want to embed Delve into their applications to adjust the logging better to their needs. * terminal/logflags: Added `SetLoggerFactory(LoggerFactory)` Added changes from code review. * terminal/logflags: Added `SetLoggerFactory(LoggerFactory)` Reworked requested changes. * terminal/logflags: Added `SetLoggerFactory(LoggerFactory)` Reworked requested changes. --- pkg/logflags/logflags.go | 63 +++++++++------ pkg/logflags/logflags_test.go | 117 +++++++++++++++++++++++++++ pkg/logflags/logger.go | 77 ++++++++++++++++++ pkg/proc/bininfo.go | 13 +-- pkg/proc/gdbserial/gdbserver_conn.go | 3 +- service/dap/server.go | 4 +- service/debugger/debugger.go | 3 +- service/rpccommon/server.go | 5 +- 8 files changed, 243 insertions(+), 42 deletions(-) create mode 100644 pkg/logflags/logflags_test.go create mode 100644 pkg/logflags/logger.go diff --git a/pkg/logflags/logflags.go b/pkg/logflags/logflags.go index 430cd566b1..233e9bb920 100644 --- a/pkg/logflags/logflags.go +++ b/pkg/logflags/logflags.go @@ -29,17 +29,20 @@ var minidump = false var logOut io.WriteCloser -func makeLogger(flag bool, fields logrus.Fields) *logrus.Entry { - logger := logrus.New().WithFields(fields) - logger.Logger.Formatter = &textFormatter{} +func makeLogger(flag bool, fields Fields) Logger { + if lf := loggerFactory; lf != nil { + return lf(flag, fields, logOut) + } + logger := logrus.New().WithFields(logrus.Fields(fields)) + logger.Logger.Formatter = DefaultFormatter() if logOut != nil { logger.Logger.Out = logOut } - logger.Logger.Level = logrus.DebugLevel - if !flag { - logger.Logger.Level = logrus.ErrorLevel + logger.Logger.Level = logrus.ErrorLevel + if flag { + logger.Logger.Level = logrus.DebugLevel } - return logger + return &logrusLogger{logger} } // Any returns true if any logging is enabled. @@ -54,8 +57,8 @@ func GdbWire() bool { } // GdbWireLogger returns a configured logger for the gdbserial wire protocol. -func GdbWireLogger() *logrus.Entry { - return makeLogger(gdbWire, logrus.Fields{"layer": "gdbconn"}) +func GdbWireLogger() Logger { + return makeLogger(gdbWire, Fields{"layer": "gdbconn"}) } // Debugger returns true if the debugger package should log. @@ -64,8 +67,8 @@ func Debugger() bool { } // DebuggerLogger returns a logger for the debugger package. -func DebuggerLogger() *logrus.Entry { - return makeLogger(debugger, logrus.Fields{"layer": "debugger"}) +func DebuggerLogger() Logger { + return makeLogger(debugger, Fields{"layer": "debugger"}) } // LLDBServerOutput returns true if the output of the LLDB server should be @@ -80,14 +83,24 @@ func DebugLineErrors() bool { return debugLineErrors } +// DebugLineLogger returns a logger for the dwarf/line package. +func DebugLineLogger() Logger { + return makeLogger(debugLineErrors, Fields{"layer": "dwarf-line"}) +} + // RPC returns true if RPC messages should be logged. func RPC() bool { return rpc } // RPCLogger returns a logger for RPC messages. -func RPCLogger() *logrus.Entry { - return makeLogger(rpc, logrus.Fields{"layer": "rpc"}) +func RPCLogger() Logger { + return rpcLogger(rpc) +} + +// rpcLogger returns a logger for RPC messages set to a specific minimal log level. +func rpcLogger(flag bool) Logger { + return makeLogger(flag, Fields{"layer": "rpc"}) } // DAP returns true if dap package should log. @@ -96,8 +109,8 @@ func DAP() bool { } // DAPLogger returns a logger for dap package. -func DAPLogger() *logrus.Entry { - return makeLogger(dap, logrus.Fields{"layer": "dap"}) +func DAPLogger() Logger { + return makeLogger(dap, Fields{"layer": "dap"}) } // FnCall returns true if the function call protocol should be logged. @@ -105,8 +118,8 @@ func FnCall() bool { return fnCall } -func FnCallLogger() *logrus.Entry { - return makeLogger(fnCall, logrus.Fields{"layer": "proc", "kind": "fncall"}) +func FnCallLogger() Logger { + return makeLogger(fnCall, Fields{"layer": "proc", "kind": "fncall"}) } // Minidump returns true if the minidump loader should be logged. @@ -114,8 +127,8 @@ func Minidump() bool { return minidump } -func MinidumpLogger() *logrus.Entry { - return makeLogger(minidump, logrus.Fields{"layer": "core", "kind": "minidump"}) +func MinidumpLogger() Logger { + return makeLogger(minidump, Fields{"layer": "core", "kind": "minidump"}) } // WriteDAPListeningMessage writes the "DAP server listening" message in dap mode. @@ -139,8 +152,7 @@ func writeListeningMessage(server string, addr net.Addr) { if tcpAddr == nil || tcpAddr.IP.IsLoopback() { return } - logger := RPCLogger() - logger.Logger.Level = logrus.WarnLevel + logger := rpcLogger(true) logger.Warnln("Listening for remote connections (connections are not authenticated nor encrypted)") } @@ -217,12 +229,17 @@ func Close() { } } -// textFormatter is a simplified version of logrus.TextFormatter that +// DefaultFormatter provides a simplified version of logrus.TextFormatter that // doesn't make logs unreadable when they are output to a text file or to a // terminal that doesn't support colors. -type textFormatter struct { +func DefaultFormatter() logrus.Formatter { + return textFormatterInstance } +type textFormatter struct{} + +var textFormatterInstance = &textFormatter{} + func (f *textFormatter) Format(entry *logrus.Entry) ([]byte, error) { var b *bytes.Buffer if entry.Buffer != nil { diff --git a/pkg/logflags/logflags_test.go b/pkg/logflags/logflags_test.go new file mode 100644 index 0000000000..d2392df1da --- /dev/null +++ b/pkg/logflags/logflags_test.go @@ -0,0 +1,117 @@ +package logflags + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/sirupsen/logrus" +) + +func TestMakeLogger_usingLoggerFactory(t *testing.T) { + if loggerFactory != nil { + t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory) + } + defer func() { + loggerFactory = nil + }() + if logOut != nil { + t.Fatalf("expected logOut to be nil; but was <%v>", logOut) + } + logOut = &bufferWriter{} + defer func() { + logOut = nil + }() + + expectedLogger := &logrusLogger{} + SetLoggerFactory(func(flag bool, fields Fields, out io.Writer) Logger { + if flag != true { + t.Fatalf("expected flag to be <%v>; but was <%v>", true, flag) + } + if len(fields) != 1 || fields["foo"] != "bar" { + t.Fatalf("expected fields to be {'foo':'bar'}; but was <%v>", fields) + } + if out != logOut { + t.Fatalf("expected out to be <%v>; but was <%v>", logOut, out) + } + return expectedLogger + }) + + actual := makeLogger(true, Fields{"foo": "bar"}) + if actual != expectedLogger { + t.Fatalf("expected actual to <%v>; but was <%v>", expectedLogger, actual) + } +} + +func TestMakeLogger_usingDefaultBehavior(t *testing.T) { + if loggerFactory != nil { + t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory) + } + if logOut != nil { + t.Fatalf("expected logOut to be nil; but was <%v>", logOut) + } + logOut = &bufferWriter{} + defer func() { + logOut = nil + }() + + actual := makeLogger(false, Fields{"foo": "bar"}) + + actualEntry, expectedType := actual.(*logrusLogger) + if !expectedType { + t.Fatalf("expected actual to be of type <%v>; but was <%v>", reflect.TypeOf((*logrus.Entry)(nil)), reflect.TypeOf(actualEntry)) + } + if actualEntry.Entry.Logger.Level != logrus.ErrorLevel { + t.Fatalf("expected actualEntry.Entry.Logger.Level to be <%v>; but was <%v>", logrus.ErrorLevel, actualEntry.Logger.Level) + } + if actualEntry.Entry.Logger.Out != logOut { + t.Fatalf("expected actualEntry.Entry.Logger.Out to be <%v>; but was <%v>", logOut, actualEntry.Logger.Out) + } + if actualEntry.Entry.Logger.Formatter != textFormatterInstance { + t.Fatalf("expected actualEntry.Entry.Logger.Formatter to be <%v>; but was <%v>", textFormatterInstance, actualEntry.Logger.Formatter) + } + if len(actualEntry.Entry.Data) != 1 || actualEntry.Entry.Data["foo"] != "bar" { + t.Fatalf("expected actualEntry.Entry.Data to be {'foo':'bar'}; but was <%v>", actualEntry.Data) + } +} + +func TestMakeLogger_usingDefaultBehaviorAndFlagged(t *testing.T) { + if loggerFactory != nil { + t.Fatalf("expected loggerFactory to be nil; but was <%v>", loggerFactory) + } + if logOut != nil { + t.Fatalf("expected logOut to be nil; but was <%v>", logOut) + } + logOut = &bufferWriter{} + defer func() { + logOut = nil + }() + + actual := makeLogger(true, Fields{"foo": "bar"}) + + actualEntry, expectedType := actual.(*logrusLogger) + if !expectedType { + t.Fatalf("expected actual to be of type <%v>; but was <%v>", reflect.TypeOf((*logrus.Entry)(nil)), reflect.TypeOf(actualEntry)) + } + if actualEntry.Entry.Logger.Level != logrus.DebugLevel { + t.Fatalf("expected actualEntry.Entry.Logger.Level to be <%v>; but was <%v>", logrus.DebugLevel, actualEntry.Logger.Level) + } + if actualEntry.Entry.Logger.Out != logOut { + t.Fatalf("expected actualEntry.Entry.Logger.Out to be <%v>; but was <%v>", logOut, actualEntry.Logger.Out) + } + if actualEntry.Entry.Logger.Formatter != textFormatterInstance { + t.Fatalf("expected actualEntry.Entry.Logger.Formatter to be <%v>; but was <%v>", textFormatterInstance, actualEntry.Logger.Formatter) + } + if len(actualEntry.Entry.Data) != 1 || actualEntry.Entry.Data["foo"] != "bar" { + t.Fatalf("expected actualEntry.Entry.Data to be {'foo':'bar'}; but was <%v>", actualEntry.Data) + } +} + +type bufferWriter struct { + bytes.Buffer +} + +func (bw bufferWriter) Close() error { + return nil +} diff --git a/pkg/logflags/logger.go b/pkg/logflags/logger.go new file mode 100644 index 0000000000..14fd07ead3 --- /dev/null +++ b/pkg/logflags/logger.go @@ -0,0 +1,77 @@ +package logflags + +import ( + "github.com/sirupsen/logrus" + "io" +) + +// Logger represents a generic interface for logging inside of +// Delve codebase. +type Logger interface { + // WithField returns a new Logger enriched with the given field. + WithField(key string, value interface{}) Logger + // WithFields returns a new Logger enriched with the given fields. + WithFields(fields Fields) Logger + // WithError returns a new Logger enriched with the given error. + WithError(err error) Logger + + Debugf(format string, args ...interface{}) + Infof(format string, args ...interface{}) + Printf(format string, args ...interface{}) + Warnf(format string, args ...interface{}) + Warningf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + Fatalf(format string, args ...interface{}) + Panicf(format string, args ...interface{}) + + Debug(args ...interface{}) + Info(args ...interface{}) + Print(args ...interface{}) + Warn(args ...interface{}) + Warning(args ...interface{}) + Error(args ...interface{}) + Fatal(args ...interface{}) + Panic(args ...interface{}) + + Debugln(args ...interface{}) + Infoln(args ...interface{}) + Println(args ...interface{}) + Warnln(args ...interface{}) + Warningln(args ...interface{}) + Errorln(args ...interface{}) + Fatalln(args ...interface{}) + Panicln(args ...interface{}) +} + +// LoggerFactory is used to create new Logger instances. +// SetLoggerFactory can be used to configure it. +// +// The given parameters fields and out can be both be nil. +type LoggerFactory func(flag bool, fields Fields, out io.Writer) Logger + +var loggerFactory LoggerFactory + +// SetLoggerFactory will ensure that every Logger created by this package, will be now created +// by the given LoggerFactory. Default behavior will be a logrus based Logger instance using DefaultFormatter. +func SetLoggerFactory(lf LoggerFactory) { + loggerFactory = lf +} + +// Fields type wraps many fields for Logger +type Fields map[string]interface{} + +type logrusLogger struct { + *logrus.Entry +} + +func (l *logrusLogger) WithField(key string, value interface{}) Logger { + return &logrusLogger{l.Entry.WithField(key, value)} +} + +func (l *logrusLogger) WithFields(fields Fields) Logger { + return &logrusLogger{l.Entry.WithFields(logrus.Fields(fields))} +} + +func (l *logrusLogger) WithError(err error) Logger { + return &logrusLogger{l.Entry.WithError(err)} +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index dd92fe58d8..950bdda1f8 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -34,7 +34,6 @@ import ( "github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/proc/debuginfod" "github.com/hashicorp/golang-lru/simplelru" - "github.com/sirupsen/logrus" ) const ( @@ -111,7 +110,7 @@ type BinaryInfo struct { // Go 1.17 register ABI is enabled. regabi bool - logger *logrus.Entry + logger logflags.Logger } var ( @@ -944,7 +943,7 @@ func (image *Image) Close() error { return err2 } -func (image *Image) setLoadError(logger *logrus.Entry, fmtstr string, args ...interface{}) { +func (image *Image) setLoadError(logger logflags.Logger, fmtstr string, args ...interface{}) { image.loadErrMu.Lock() image.loadErr = fmt.Errorf(fmtstr, args...) image.loadErrMu.Unlock() @@ -1586,7 +1585,7 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync. } } -func getSymbol(image *Image, logger *logrus.Entry, exe *elf.File, name string) *elf.Symbol { +func getSymbol(image *Image, logger logflags.Logger, exe *elf.File, name string) *elf.Symbol { symbols, err := exe.Symbols() if err != nil { image.setLoadError(logger, "could not parse ELF symbols: %v", err) @@ -2069,11 +2068,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugInfoBytes, debugLineB if hasLineInfo && lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { var logfn func(string, ...interface{}) if logflags.DebugLineErrors() { - logger := logrus.New().WithFields(logrus.Fields{"layer": "dwarf-line"}) - logger.Logger.Level = logrus.DebugLevel - logfn = func(fmt string, args ...interface{}) { - logger.Printf(fmt, args) - } + logfn = logflags.DebugLineLogger().Printf } cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), image.debugLineStr, logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize()) } diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index 0449ed1475..f6bde758ef 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -17,7 +17,6 @@ import ( "github.com/go-delve/delve/pkg/logflags" "github.com/go-delve/delve/pkg/proc" - "github.com/sirupsen/logrus" ) type gdbConn struct { @@ -49,7 +48,7 @@ type gdbConn struct { useXcmd bool // forces writeMemory to use the 'X' command - log *logrus.Entry + log logflags.Logger } var ErrTooManyAttempts = errors.New("too many transmit attempts") diff --git a/service/dap/server.go b/service/dap/server.go index d0df77b92c..e505bab7ac 100644 --- a/service/dap/server.go +++ b/service/dap/server.go @@ -43,8 +43,6 @@ import ( "github.com/go-delve/delve/service/debugger" "github.com/go-delve/delve/service/internal/sameuser" "github.com/google/go-dap" - - "github.com/sirupsen/logrus" ) // Server implements a DAP server that can accept a single client for @@ -167,7 +165,7 @@ type Config struct { *service.Config // log is used for structured logging. - log *logrus.Entry + log logflags.Logger // StopTriggered is closed when the server is Stop()-ed. // Can be used to safeguard against duplicate shutdown sequences. StopTriggered chan struct{} diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 746d167472..66dbf2850b 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -31,7 +31,6 @@ import ( "github.com/go-delve/delve/pkg/proc/gdbserial" "github.com/go-delve/delve/pkg/proc/native" "github.com/go-delve/delve/service/api" - "github.com/sirupsen/logrus" ) var ( @@ -70,7 +69,7 @@ type Debugger struct { targetMutex sync.Mutex target *proc.TargetGroup - log *logrus.Entry + log logflags.Logger running bool runningMutex sync.Mutex diff --git a/service/rpccommon/server.go b/service/rpccommon/server.go index a688941738..d7916cb5c3 100644 --- a/service/rpccommon/server.go +++ b/service/rpccommon/server.go @@ -25,7 +25,6 @@ import ( "github.com/go-delve/delve/service/internal/sameuser" "github.com/go-delve/delve/service/rpc1" "github.com/go-delve/delve/service/rpc2" - "github.com/sirupsen/logrus" ) // ServerImpl implements a JSON-RPC server that can switch between two @@ -45,7 +44,7 @@ type ServerImpl struct { s2 *rpc2.RPCServer // maps of served methods, one for each supported API. methodMaps []map[string]*methodType - log *logrus.Entry + log logflags.Logger } type RPCCallback struct { @@ -216,7 +215,7 @@ func isExportedOrBuiltinType(t reflect.Type) bool { // // func (rcvr ReceiverType) Method(in InputType, out *ReplyType) error // func (rcvr ReceiverType) Method(in InputType, cb service.RPCCallback) -func suitableMethods(rcvr interface{}, methods map[string]*methodType, log *logrus.Entry) { +func suitableMethods(rcvr interface{}, methods map[string]*methodType, log logflags.Logger) { typ := reflect.TypeOf(rcvr) rcvrv := reflect.ValueOf(rcvr) sname := reflect.Indirect(rcvrv).Type().Name() From c728f90dcf2cd03ef3e69e197d2d2c5b61c4f097 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Wed, 15 Feb 2023 12:11:42 +0200 Subject: [PATCH 58/72] TeamCity,Documentation: use new TeamCity URL (#3275) Change delve.beta.teamcity.com to delve.teamcity.com. --- .teamcity/pom.xml | 2 +- README.md | 2 +- service/dap/server_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml index 1292800ac5..77a3459586 100644 --- a/.teamcity/pom.xml +++ b/.teamcity/pom.xml @@ -22,7 +22,7 @@ teamcity-server - https://delve.beta.teamcity.com/app/dsl-plugins-repository + https://delve.teamcity.com/app/dsl-plugins-repository true diff --git a/README.md b/README.md index a607a13a2e..597cef12b6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/go-delve/delve/master/LICENSE) [![GoDoc](https://godoc.org/github.com/go-delve/delve?status.svg)](https://godoc.org/github.com/go-delve/delve) -[![Build Status](https://delve.beta.teamcity.com/app/rest/builds/buildType:(id:Delve_AggregatorBuild)/statusIcon.svg)](https://delve.beta.teamcity.com/viewType.html?buildTypeId=Delve_AggregatorBuild&guest=1) +[![Build Status](https://delve.teamcity.com/app/rest/builds/buildType:(id:Delve_AggregatorBuild)/statusIcon.svg)](https://delve.teamcity.com/viewType.html?buildTypeId=Delve_AggregatorBuild&guest=1) The GitHub issue tracker is for **bugs** only. Please use the [developer mailing list](https://groups.google.com/forum/#!forum/delve-dev) for any feature proposals and discussions. diff --git a/service/dap/server_test.go b/service/dap/server_test.go index 4e8b7a61ed..8b775487c4 100644 --- a/service/dap/server_test.go +++ b/service/dap/server_test.go @@ -3680,7 +3680,7 @@ func TestLaunchSubstitutePath(t *testing.T) { // in the launch configuration to take care of the mapping. func TestAttachSubstitutePath(t *testing.T) { if runtime.GOOS == "windows" { - t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details") + t.Skip("test skipped on Windows, see https://delve.teamcity.com/project/Delve_windows for details") } runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) { cmd := execFixture(t, fixture) @@ -5694,7 +5694,7 @@ func TestLaunchRequestWithEnv(t *testing.T) { func TestAttachRequest(t *testing.T) { if runtime.GOOS == "windows" { - t.Skip("test skipped on windows, see https://delve.beta.teamcity.com/project/Delve_windows for details") + t.Skip("test skipped on Windows, see https://delve.teamcity.com/project/Delve_windows for details") } runTest(t, "loopprog", func(client *daptest.Client, fixture protest.Fixture) { // Start the program to attach to From cf65f947765f9ac7de28ca03e1090d080d8b92c0 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Wed, 15 Feb 2023 20:35:37 +0100 Subject: [PATCH 59/72] proc: remove unused parameter from resolveParametricType (#3276) --- pkg/proc/types.go | 2 +- pkg/proc/variables.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 88a4e2612d..1940b26a5a 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -134,7 +134,7 @@ func runtimeTypeToDIE(_type *Variable, dataAddr uint64) (typ godwarf.Type, kind // resolveParametricType returns the real type of t if t is a parametric // type, by reading the correct dictionary entry. -func resolveParametricType(tgt *Target, bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type, dictAddr uint64) (godwarf.Type, error) { +func resolveParametricType(bi *BinaryInfo, mem MemoryReadWriter, t godwarf.Type, dictAddr uint64) (godwarf.Type, error) { ptyp, _ := t.(*godwarf.ParametricType) if ptyp == nil { return t, nil diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 5830894235..e24c8240b7 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -1188,7 +1188,7 @@ func extractVarInfoFromEntry(tgt *Target, bi *BinaryInfo, image *Image, regs op. return nil, err } - t, err = resolveParametricType(tgt, bi, mem, t, dictAddr) + t, err = resolveParametricType(bi, mem, t, dictAddr) if err != nil { // Log the error, keep going with t, which will be the shape type logflags.DebuggerLogger().Errorf("could not resolve parametric type of %s", n) From cac86b879197f49f3c45f4557a5a5cef2941eb11 Mon Sep 17 00:00:00 2001 From: Derek Parker Date: Mon, 20 Feb 2023 11:36:09 +0200 Subject: [PATCH 60/72] dwarf/line: handle end_seq properly (#3277) This patch changes how we handle end_seq in the debug_line state machine program. Instead of always considering the state machine invalid at the end_seq instead simply consider the *current* address invalid. This solves a number of issues such as incorrect disassemble output for the last few instructions in certain functions, and creating an eval scope at an address within the last few instructions of certain functions. It also handles the case where the end_seq address is the same as the start address of the next adjacent function, which would previously confuse Delve which is why we initially marked end_seq as invalid for the entire state machine. This approach is more nuanced and still solves that initial problem while fixing some problems introduced by that patch. --- pkg/dwarf/line/state_machine.go | 7 ++++--- pkg/proc/eval.go | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index c7d1b65d6a..6c31fb4610 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -108,6 +108,7 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *Stat if len(dbl.FileNames) > 0 { file = dbl.FileNames[0].Path } + dbl.endSeqIsValid = true sm := &StateMachine{ dbl: dbl, file: file, @@ -261,7 +262,7 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { if (sm.address > pc) && (pc >= sm.lastAddress) { return sm.lastFile, sm.lastLine, true } - if sm.address == pc { + if sm.address == pc && !sm.endSeq { return sm.file, sm.line, true } } @@ -272,7 +273,7 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { break } } - if sm.valid { + if sm.valid && !sm.endSeq { return sm.file, sm.line, true } return "", 0, false @@ -301,7 +302,7 @@ func (lineInfo *DebugLineInfo) LineToPCs(filename string, lineno int) []PCStmt { } break } - if sm.line == lineno && sm.file == filename && sm.valid { + if sm.line == lineno && sm.file == filename && sm.valid && !sm.endSeq { pcstmts = append(pcstmts, PCStmt{sm.address, sm.isStmt}) } } diff --git a/pkg/proc/eval.go b/pkg/proc/eval.go index d8348fe6a0..1afa229488 100644 --- a/pkg/proc/eval.go +++ b/pkg/proc/eval.go @@ -474,7 +474,7 @@ func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) { // FunctionArguments returns the name, value, and type of all current function arguments. func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) { - vars, err := scope.Locals(localsNoDeclLineCheck) + vars, err := scope.Locals(0) if err != nil { return nil, err } From 3d6730d12e693438b6ca90585cf215bf228c8d7c Mon Sep 17 00:00:00 2001 From: Quim Muntal Date: Wed, 22 Feb 2023 18:25:29 +0100 Subject: [PATCH 61/72] Default to stop test_windows.ps1 when there is an error (#3285) * default to stop test_windows.ps1 when there is an error * fix script --- _scripts/test_windows.ps1 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index 751483efab..6c2b595c0e 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -24,12 +24,12 @@ if ($arch -eq "amd64") { New-Item "$binDir\llvm-mingw" -ItemType Directory -ErrorAction SilentlyContinue $url = "https://github.com/mstorsjo/llvm-mingw/releases/download/$llvmVersion/$name.zip" - Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$name.zip" + Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$name.zip" -ErrorAction Stop Expand-Archive -Force -LiteralPath "$env:TEMP\$name.zip" -DestinationPath "$binDir\llvm-mingw\" } $env:PATH = "$binDir\llvm-mingw\$name\bin;$env:PATH" } else { - Write-Error "Unsupported architecture: $arch" -ErrorAction Stop + Throw "Unsupported architecture: $arch" } # Install Procdump @@ -48,7 +48,7 @@ function GetGo($version) { { $file = "$version.windows-$arch.zip" $url = "https://dl.google.com/go/$file" - Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$file" + Invoke-WebRequest -UserAgent wget -Uri $url -OutFile "$env:TEMP\$file" -ErrorAction Stop Expand-Archive -Force -LiteralPath "$env:TEMP\$file" -DestinationPath "$env:TEMP\$version" New-Item $env:GOROOT -ItemType Directory Move-Item -Path "$env:TEMP\$version\go\*" -Destination $env:GOROOT -Force @@ -57,7 +57,7 @@ function GetGo($version) { if ($version -eq "gotip") { #Exit 0 - $latest = Invoke-WebRequest -Uri "https://golang.org/VERSION?m=text" -UseBasicParsing | Select-Object -ExpandProperty Content + $latest = Invoke-WebRequest -Uri "https://golang.org/VERSION?m=text" -UseBasicParsing | Select-Object -ExpandProperty Content -ErrorAction Stop GetGo $latest $env:GOROOT_BOOTSTRAP = $env:GOROOT $env:GOROOT = "$binDir\go\go-tip" @@ -74,7 +74,7 @@ if ($version -eq "gotip") { } else { # Install Go Write-Host "Finding latest patch version for $version" - $versions = Invoke-WebRequest -Uri "https://golang.org/dl/?mode=json&include=all" -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json + $versions = Invoke-WebRequest -Uri "https://golang.org/dl/?mode=json&include=all" -UseBasicParsing | foreach {$_.Content} | ConvertFrom-Json -ErrorAction Stop $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version($|\.)" | Sort-Object -Descending | Select-Object -First 1 if ($v -eq $null) { $v = $versions | foreach {$_.version} | Select-String -Pattern "^$version(rc)" | Sort-Object -Descending | Select-Object -First 1 From 37e44bf6033edbd549cb18cc475d127314756c35 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Wed, 22 Feb 2023 18:26:28 +0100 Subject: [PATCH 62/72] proc,proc/native: adds ability to automatically debug child processes (#3165) Adds the ability to automatically debug child processes executed by the target to the linux native backend. This commit does not contain user interface or API to access this functionality. Updates #2551 --- _fixtures/spawn.go | 48 ++ pkg/proc/core/core.go | 16 +- pkg/proc/core/core_test.go | 15 +- pkg/proc/gdbserial/gdbserver.go | 46 +- pkg/proc/gdbserial/rr.go | 4 +- pkg/proc/gdbserial/rr_test.go | 4 +- pkg/proc/interface.go | 8 + pkg/proc/native/followexec_other.go | 11 + pkg/proc/native/nonative_darwin.go | 8 +- pkg/proc/native/proc.go | 185 ++++-- pkg/proc/native/proc_darwin.go | 16 +- pkg/proc/native/proc_freebsd.go | 14 +- pkg/proc/native/proc_linux.go | 231 +++++-- pkg/proc/native/proc_windows.go | 10 +- pkg/proc/native/threads_linux.go | 3 + pkg/proc/proc_export_test.go | 42 -- pkg/proc/proc_test.go | 948 +++++++++++++++------------- pkg/proc/proc_unix_test.go | 3 +- pkg/proc/scope_test.go | 8 +- pkg/proc/target.go | 31 +- pkg/proc/target_exec.go | 256 ++++++-- pkg/proc/target_group.go | 167 +++-- pkg/proc/variable_test.go | 6 +- pkg/proc/variables_test.go | 97 ++- pkg/terminal/command_test.go | 1 + service/debugger/debugger.go | 42 +- 26 files changed, 1391 insertions(+), 829 deletions(-) create mode 100644 _fixtures/spawn.go create mode 100644 pkg/proc/native/followexec_other.go delete mode 100644 pkg/proc/proc_export_test.go diff --git a/_fixtures/spawn.go b/_fixtures/spawn.go new file mode 100644 index 0000000000..75f5c418f8 --- /dev/null +++ b/_fixtures/spawn.go @@ -0,0 +1,48 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strconv" + "time" +) + +func traceme1() { + fmt.Printf("parent starting\n") +} + +func traceme2(n string) { + fmt.Printf("hello from %s\n", n) +} + +func traceme3() { + fmt.Printf("done\n") +} + +func main() { + exe, _ := os.Executable() + switch os.Args[1] { + case "spawn": + traceme1() + n, _ := strconv.Atoi(os.Args[2]) + cmds := []*exec.Cmd{} + for i := 0; i < n; i++ { + cmd := exec.Command(exe, "child", fmt.Sprintf("C%d", i)) + cmd.Stdout = os.Stdout + cmd.Start() + cmds = append(cmds, cmd) + } + + for _, cmd := range cmds { + cmd.Wait() + } + + time.Sleep(1 * time.Second) + traceme3() + + case "child": + traceme2(os.Args[2]) + + } +} diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 148d3405c9..1407627c08 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -204,7 +204,7 @@ var ErrUnrecognizedFormat = errors.New("unrecognized core format") // OpenCore will open the core file and return a Process struct. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, error) { +func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.TargetGroup, error) { var p *process var currentThread proc.Thread var err error @@ -222,14 +222,13 @@ func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.Target, e return nil, ErrNoThreads } - return proc.NewTarget(p, p.pid, currentThread, proc.NewTargetConfig{ - Path: exePath, + grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, DisableAsyncPreempt: false, - StopReason: proc.StopAttached, CanDump: false, - ContinueOnce: continueOnce, }) + _, err = addTarget(p, p.pid, currentThread, exePath, proc.StopAttached) + return grp, err } // BinInfo will return the binary info. @@ -310,6 +309,11 @@ func (p *process) WriteMemory(addr uint64, data []byte) (int, error) { return 0, ErrWriteCore } +// FollowExec enables (or disables) follow exec mode +func (p *process) FollowExec(bool) error { + return nil +} + // ProcessMemory returns the memory of this thread's process. func (t *thread) ProcessMemory() proc.MemoryReadWriter { return t.p @@ -419,7 +423,7 @@ func (p *process) ClearInternalBreakpoints() error { return nil } -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { +func (*process) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { return nil, proc.StopUnknown, ErrContinueCore } diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 3ee4a3e1be..4daabd9cbf 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -198,7 +198,7 @@ func TestSplicedReader(t *testing.T) { } } -func withCoreFile(t *testing.T, name, args string) *proc.Target { +func withCoreFile(t *testing.T, name, args string) *proc.TargetGroup { // This is all very fragile and won't work on hosts with non-default core patterns. // Might be better to check in the binary and core? tempDir := t.TempDir() @@ -251,8 +251,8 @@ func TestCore(t *testing.T) { if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { t.Skip("disabled on linux, Github Actions, with PIE buildmode") } - p := withCoreFile(t, "panic", "") - grp := proc.NewGroup(p) + grp := withCoreFile(t, "panic", "") + p := grp.Selected recorded, _ := grp.Recorded() if !recorded { @@ -324,7 +324,8 @@ func TestCoreFpRegisters(t *testing.T) { t.Skip("not supported in go1.10 and later") } - p := withCoreFile(t, "fputest/", "panic") + grp := withCoreFile(t, "fputest/", "panic") + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { @@ -407,7 +408,8 @@ func TestCoreWithEmptyString(t *testing.T) { if runtime.GOOS == "linux" && os.Getenv("CI") == "true" && buildMode == "pie" { t.Skip("disabled on linux, Github Actions, with PIE buildmode") } - p := withCoreFile(t, "coreemptystring", "") + grp := withCoreFile(t, "coreemptystring", "") + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -452,10 +454,11 @@ func TestMinidump(t *testing.T) { fix := test.BuildFixture("sleep", buildFlags) mdmpPath := procdump(t, fix.Path) - p, err := OpenCore(mdmpPath, fix.Path, []string{}) + grp, err := OpenCore(mdmpPath, fix.Path, []string{}) if err != nil { t.Fatalf("OpenCore: %v", err) } + p := grp.Selected gs, _, err := proc.GoroutinesInfo(p, 0, 0) if err != nil || len(gs) == 0 { t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 8f99c999d0..2bb871991e 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -278,7 +278,7 @@ func newProcess(process *os.Process) *gdbProcess { } // Listen waits for a connection from the stub. -func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { acceptChan := make(chan net.Conn) go func() { @@ -300,7 +300,7 @@ func (p *gdbProcess) Listen(listener net.Listener, path string, pid int, debugIn } // Dial attempts to connect to the stub. -func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { for { conn, err := net.Dial("tcp", addr) if err == nil { @@ -321,7 +321,7 @@ func (p *gdbProcess) Dial(addr string, path string, pid int, debugInfoDirs []str // program and the PID of the target process, both are optional, however // some stubs do not provide ways to determine path and pid automatically // and Connect will be unable to function without knowing them. -func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) Connect(conn net.Conn, path string, pid int, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { p.conn.conn = conn p.conn.pid = pid err := p.conn.handshake(p.regnames) @@ -450,7 +450,7 @@ func getLdEnvVars() []string { // LLDBLaunch starts an instance of lldb-server and connects to it, asking // it to launch the specified target program with the specified arguments // (cmd) on the specified directory wd. -func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { if runtime.GOOS == "windows" { return nil, ErrUnsupportedOS } @@ -567,11 +567,11 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ p := newProcess(process.Process) p.conn.isDebugserver = isDebugserver - var tgt *proc.Target + var grp *proc.TargetGroup if listener != nil { - tgt, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched) + grp, err = p.Listen(listener, cmd[0], 0, debugInfoDirs, proc.StopLaunched) } else { - tgt, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched) + grp, err = p.Dial(port, cmd[0], 0, debugInfoDirs, proc.StopLaunched) } if p.conn.pid != 0 && foreground && isatty.IsTerminal(os.Stdin.Fd()) { // Make the target process the controlling process of the tty if it is a foreground process. @@ -580,7 +580,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ logflags.DebuggerLogger().Errorf("could not set controlling process: %v", err) } } - return tgt, err + return grp, err } // LLDBAttach starts an instance of lldb-server and connects to it, asking @@ -588,7 +588,7 @@ func LLDBLaunch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs [ // Path is path to the target's executable, path only needs to be specified // for some stubs that do not provide an automated way of determining it // (for example debugserver). -func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, error) { +func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.TargetGroup, error) { if runtime.GOOS == "windows" { return nil, ErrUnsupportedOS } @@ -633,13 +633,13 @@ func LLDBAttach(pid int, path string, debugInfoDirs []string) (*proc.Target, err p := newProcess(process.Process) p.conn.isDebugserver = isDebugserver - var tgt *proc.Target + var grp *proc.TargetGroup if listener != nil { - tgt, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached) + grp, err = p.Listen(listener, path, pid, debugInfoDirs, proc.StopAttached) } else { - tgt, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached) + grp, err = p.Dial(port, path, pid, debugInfoDirs, proc.StopAttached) } - return tgt, err + return grp, err } // EntryPoint will return the process entry point address, useful for @@ -673,7 +673,7 @@ func (p *gdbProcess) EntryPoint() (uint64, error) { // initialize uses qProcessInfo to load the inferior's PID and // executable path. This command is not supported by all stubs and not all // stubs will report both the PID and executable path. -func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.Target, error) { +func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason proc.StopReason) (*proc.TargetGroup, error) { var err error if path == "" { // If we are attaching to a running process and the user didn't specify @@ -724,19 +724,18 @@ func (p *gdbProcess) initialize(path string, debugInfoDirs []string, stopReason return nil, err } } - tgt, err := proc.NewTarget(p, p.conn.pid, p.currentThread, proc.NewTargetConfig{ - Path: path, + grp, addTarget := proc.NewGroup(p, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, DisableAsyncPreempt: runtime.GOOS == "darwin", StopReason: stopReason, CanDump: runtime.GOOS == "darwin", - ContinueOnce: continueOnce, }) + _, err = addTarget(p, p.conn.pid, p.currentThread, path, stopReason) if err != nil { p.Detach(true) return nil, err } - return tgt, nil + return grp, nil } func queryProcessInfo(p *gdbProcess, pid int) (int, string, error) { @@ -821,11 +820,7 @@ const ( debugServerTargetExcBreakpoint = 0x96 ) -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { - if len(procs) != 1 { - panic("not implemented") - } - p := procs[0].(*gdbProcess) +func (p *gdbProcess) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { if p.exited { return nil, proc.StopExited, proc.ErrProcessExited{Pid: p.conn.pid} } @@ -1307,6 +1302,11 @@ func (p *gdbProcess) EraseBreakpoint(bp *proc.Breakpoint) error { return p.conn.clearBreakpoint(bp.Addr, watchTypeToBreakpointType(bp.WatchType), kind) } +// FollowExec enables (or disables) follow exec mode +func (p *gdbProcess) FollowExec(bool) error { + return errors.New("follow exec not supported") +} + type threadUpdater struct { p *gdbProcess seen map[int]bool diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index e133b12bd2..52a32080c5 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -124,7 +124,7 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir // Replay starts an instance of rr in replay mode, with the specified trace // directory, and connects to it. -func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) { +func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.TargetGroup, error) { if err := checkRRAvailable(); err != nil { return nil, err } @@ -279,7 +279,7 @@ func rrParseGdbCommand(line string) rrInit { } // RecordAndReplay acts like calling Record and then Replay. -func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) { +func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.TargetGroup, string, error) { tracedir, err := Record(cmd, wd, quiet, redirects) if tracedir == "" { return nil, "", err diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index a83e249f64..9c96280f65 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -30,14 +30,12 @@ func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup, t.Skip("test skipped, rr not found") } t.Log("recording") - p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{}) + grp, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{}) if err != nil { t.Fatal("Launch():", err) } t.Logf("replaying %q", tracedir) - grp := proc.NewGroup(p) - defer grp.Detach(true) fn(grp, fixture) diff --git a/pkg/proc/interface.go b/pkg/proc/interface.go index 4027b40efc..d2a0ef3550 100644 --- a/pkg/proc/interface.go +++ b/pkg/proc/interface.go @@ -7,6 +7,11 @@ import ( "github.com/go-delve/delve/pkg/proc/internal/ebpf" ) +// ProcessGroup is a group of processes that are resumed at the same time. +type ProcessGroup interface { + ContinueOnce(*ContinueOnceContext) (Thread, StopReason, error) +} + // Process represents the target of the debugger. This // target could be a system process, core file, etc. // @@ -57,6 +62,9 @@ type ProcessInternal interface { // StartCallInjection notifies the backend that we are about to inject a function call. StartCallInjection() (func(), error) + + // FollowExec enables (or disables) follow exec mode + FollowExec(bool) error } // RecordingManipulation is an interface for manipulating process recordings. diff --git a/pkg/proc/native/followexec_other.go b/pkg/proc/native/followexec_other.go new file mode 100644 index 0000000000..2520ab0dbd --- /dev/null +++ b/pkg/proc/native/followexec_other.go @@ -0,0 +1,11 @@ +//go:build !linux +// +build !linux + +package native + +import "errors" + +// FollowExec enables (or disables) follow exec mode +func (*nativeProcess) FollowExec(bool) error { + return errors.New("follow exec not implemented") +} diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go index 3aae7e4689..d0f7b19a51 100644 --- a/pkg/proc/native/nonative_darwin.go +++ b/pkg/proc/native/nonative_darwin.go @@ -16,12 +16,12 @@ import ( var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") // Launch returns ErrNativeBackendDisabled. -func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) { +func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) { return nil, ErrNativeBackendDisabled } // Attach returns ErrNativeBackendDisabled. -func Attach(_ int, _ []string) (*proc.Target, error) { +func Attach(_ int, _ []string) (*proc.TargetGroup, error) { return nil, ErrNativeBackendDisabled } @@ -57,11 +57,11 @@ func (dbp *nativeProcess) resume() error { panic(ErrNativeBackendDisabled) } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { panic(ErrNativeBackendDisabled) } -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (*processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { panic(ErrNativeBackendDisabled) } diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index b1c906a48e..c3d6c86433 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -24,11 +24,11 @@ type nativeProcess struct { // Thread used to read and write memory memthread *nativeThread - os *osProcessDetails - firstStart bool - ptraceChan chan func() - ptraceDoneChan chan interface{} - childProcess bool // this process was launched, not attached to + os *osProcessDetails + firstStart bool + ptraceThread *ptraceThread + childProcess bool // this process was launched, not attached to + followExec bool // automatically attach to new processes // Controlling terminal file descriptor for // this process. @@ -45,19 +45,30 @@ type nativeProcess struct { // `handlePtraceFuncs`. func newProcess(pid int) *nativeProcess { dbp := &nativeProcess{ - pid: pid, - threads: make(map[int]*nativeThread), - breakpoints: proc.NewBreakpointMap(), - firstStart: true, - os: new(osProcessDetails), - ptraceChan: make(chan func()), - ptraceDoneChan: make(chan interface{}), - bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), + pid: pid, + threads: make(map[int]*nativeThread), + breakpoints: proc.NewBreakpointMap(), + firstStart: true, + os: new(osProcessDetails), + ptraceThread: newPtraceThread(), + bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), } - go dbp.handlePtraceFuncs() return dbp } +// newChildProcess is like newProcess but uses the same ptrace thread as dbp. +func newChildProcess(dbp *nativeProcess, pid int) *nativeProcess { + return &nativeProcess{ + pid: pid, + threads: make(map[int]*nativeThread), + breakpoints: proc.NewBreakpointMap(), + firstStart: true, + os: new(osProcessDetails), + ptraceThread: dbp.ptraceThread.acquire(), + bi: proc.NewBinaryInfo(runtime.GOOS, runtime.GOARCH), + } +} + // BinInfo will return the binary info struct associated with this process. func (dbp *nativeProcess) BinInfo() *proc.BinaryInfo { return dbp.bi @@ -172,23 +183,58 @@ func (dbp *nativeProcess) EraseBreakpoint(bp *proc.Breakpoint) error { return dbp.memthread.clearSoftwareBreakpoint(bp) } -func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { - if len(procs) != 1 { - panic("not implemented") +type processGroup struct { + procs []*nativeProcess + addTarget proc.AddTargetFunc +} + +func (procgrp *processGroup) numValid() int { + n := 0 + for _, p := range procgrp.procs { + if ok, _ := p.Valid(); ok { + n++ + } } - dbp := procs[0].(*nativeProcess) - if dbp.exited { - return nil, proc.StopExited, proc.ErrProcessExited{Pid: dbp.pid} + return n +} + +func (procgrp *processGroup) procForThread(tid int) *nativeProcess { + for _, p := range procgrp.procs { + if p.threads[tid] != nil { + return p + } } + return nil +} - for { +func (procgrp *processGroup) add(p *nativeProcess, pid int, currentThread proc.Thread, path string, stopReason proc.StopReason) (*proc.Target, error) { + tgt, err := procgrp.addTarget(p, pid, currentThread, path, stopReason) + if err != nil { + return nil, err + } + procgrp.procs = append(procgrp.procs, p) + return tgt, nil +} - if err := dbp.resume(); err != nil { - return nil, proc.StopUnknown, err - } +func (procgrp *processGroup) ContinueOnce(cctx *proc.ContinueOnceContext) (proc.Thread, proc.StopReason, error) { + if len(procgrp.procs) != 1 && runtime.GOOS != "linux" { + panic("not implemented") + } + if procgrp.numValid() == 0 { + return nil, proc.StopExited, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} + } - for _, th := range dbp.threads { - th.CurrentBreakpoint.Clear() + for { + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + if err := dbp.resume(); err != nil { + return nil, proc.StopUnknown, err + } + for _, th := range dbp.threads { + th.CurrentBreakpoint.Clear() + } } if cctx.ResumeChan != nil { @@ -196,16 +242,29 @@ func continueOnce(procs []proc.ProcessInternal, cctx *proc.ContinueOnceContext) cctx.ResumeChan = nil } - trapthread, err := dbp.trapWait(-1) + trapthread, err := trapWait(procgrp, -1) if err != nil { return nil, proc.StopUnknown, err } - trapthread, err = dbp.stop(cctx, trapthread) + trapthread, err = procgrp.stop(cctx, trapthread) if err != nil { return nil, proc.StopUnknown, err } if trapthread != nil { + dbp := procgrp.procForThread(trapthread.ID) dbp.memthread = trapthread + // refresh memthread for every other process + for _, p2 := range procgrp.procs { + if p2.exited || p2 == dbp { + continue + } + for _, th := range p2.threads { + p2.memthread = th + if th.SoftExc() { + break + } + } + } return trapthread, proc.StopUnknown, nil } } @@ -226,21 +285,26 @@ func (dbp *nativeProcess) FindBreakpoint(pc uint64, adjustPC bool) (*proc.Breakp return nil, false } -// initialize will ensure that all relevant information is loaded -// so the process is ready to be debugged. -func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.Target, error) { +func (dbp *nativeProcess) initializeBasic() error { if err := initialize(dbp); err != nil { - return nil, err + return err } if err := dbp.updateThreadList(); err != nil { - return nil, err + return err } + return nil +} + +// initialize will ensure that all relevant information is loaded +// so the process is ready to be debugged. +func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc.TargetGroup, error) { + dbp.initializeBasic() stopReason := proc.StopLaunched if !dbp.childProcess { stopReason = proc.StopAttached } - tgt, err := proc.NewTarget(dbp, dbp.pid, dbp.memthread, proc.NewTargetConfig{ - Path: path, + procgrp := &processGroup{} + grp, addTarget := proc.NewGroup(procgrp, proc.NewTargetGroupConfig{ DebugInfoDirs: debugInfoDirs, // We disable asyncpreempt for the following reasons: @@ -253,20 +317,21 @@ func (dbp *nativeProcess) initialize(path string, debugInfoDirs []string) (*proc // See: https://go-review.googlesource.com/c/go/+/208126 DisableAsyncPreempt: runtime.GOOS == "windows" || (runtime.GOOS == "linux" && runtime.GOARCH == "arm64"), - StopReason: stopReason, - CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"), - ContinueOnce: continueOnce, + StopReason: stopReason, + CanDump: runtime.GOOS == "linux" || (runtime.GOOS == "windows" && runtime.GOARCH == "amd64"), }) + procgrp.addTarget = addTarget + tgt, err := procgrp.add(dbp, dbp.pid, dbp.memthread, path, stopReason) if err != nil { return nil, err } if dbp.bi.Arch.Name == "arm64" { dbp.iscgo = tgt.IsCgo() } - return tgt, nil + return grp, nil } -func (dbp *nativeProcess) handlePtraceFuncs() { +func (pt *ptraceThread) handlePtraceFuncs() { // We must ensure here that we are running on the same thread during // while invoking the ptrace(2) syscall. This is due to the fact that ptrace(2) expects // all commands after PTRACE_ATTACH to come from the same thread. @@ -279,21 +344,20 @@ func (dbp *nativeProcess) handlePtraceFuncs() { defer runtime.UnlockOSThread() } - for fn := range dbp.ptraceChan { + for fn := range pt.ptraceChan { fn() - dbp.ptraceDoneChan <- nil + pt.ptraceDoneChan <- nil } } func (dbp *nativeProcess) execPtraceFunc(fn func()) { - dbp.ptraceChan <- fn - <-dbp.ptraceDoneChan + dbp.ptraceThread.ptraceChan <- fn + <-dbp.ptraceThread.ptraceDoneChan } func (dbp *nativeProcess) postExit() { dbp.exited = true - close(dbp.ptraceChan) - close(dbp.ptraceDoneChan) + dbp.ptraceThread.release() dbp.bi.Close() if dbp.ctty != nil { dbp.ctty.Close() @@ -349,3 +413,32 @@ func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr return stdin, stdout, stderr, closefn, nil } + +type ptraceThread struct { + ptraceRefCnt int + ptraceChan chan func() + ptraceDoneChan chan interface{} +} + +func newPtraceThread() *ptraceThread { + pt := &ptraceThread{ + ptraceChan: make(chan func()), + ptraceDoneChan: make(chan interface{}), + ptraceRefCnt: 1, + } + go pt.handlePtraceFuncs() + return pt +} + +func (pt *ptraceThread) acquire() *ptraceThread { + pt.ptraceRefCnt++ + return pt +} + +func (pt *ptraceThread) release() { + pt.ptraceRefCnt-- + if pt.ptraceRefCnt == 0 { + close(pt.ptraceChan) + close(pt.ptraceDoneChan) + } +} diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index fa0f755895..c8eca1da2e 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -42,7 +42,7 @@ func (os *osProcessDetails) Close() {} // custom fork/exec process in order to take advantage of // PT_SIGEXC on Darwin which will turn Unix signals into // Mach exceptions. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err @@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin if err != nil { return nil, err } - if _, err := dbp.stop(nil, nil); err != nil { + if _, err := dbp.stop(nil); err != nil { return nil, err } @@ -137,7 +137,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ strin } // Attach to an existing process with the given PID. -func Attach(pid int, _ []string) (*proc.Target, error) { +func Attach(pid int, _ []string) (*proc.TargetGroup, error) { if err := macutil.CheckRosetta(); err != nil { return nil, err } @@ -291,6 +291,10 @@ func findExecutable(path string, pid int) string { return path } +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return procgrp.procs[0].trapWait(pid) +} + func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { for { task := dbp.os.task @@ -429,7 +433,11 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + return procgrp.procs[0].stop(trapthread) +} + +func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index 44fbc6b6eb..40581e5ae9 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -55,7 +55,7 @@ func (os *osProcessDetails) Close() {} // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { var ( process *exec.Cmd err error @@ -121,7 +121,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str // Attach to an existing process with the given PID. Once attached, if // the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { +func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) { dbp := newProcess(pid) var err error @@ -227,8 +227,8 @@ func findExecutable(path string, pid int) string { return path } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, trapWaitNormal) +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return procgrp.procs[0].trapWaitInternal(pid, trapWaitNormal) } type trapWaitMode uint8 @@ -403,7 +403,11 @@ func (dbp *nativeProcess) resume() error { // Used by ContinueOnce // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + return procgrp.procs[0].stop(trapthread) +} + +func (dbp *nativeProcess) stop(trapthread *nativeThread) (*nativeThread, error) { if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 29a08b506c..4686ebe6e6 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -62,7 +62,7 @@ func (os *osProcessDetails) Close() { // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) { var ( process *exec.Cmd err error @@ -141,7 +141,7 @@ func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []str // Attach to an existing process with the given PID. Once attached, if // the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Attach(pid int, debugInfoDirs []string) (*proc.Target, error) { +func Attach(pid int, debugInfoDirs []string) (*proc.TargetGroup, error) { dbp := newProcess(pid) var err error @@ -237,6 +237,11 @@ func (dbp *nativeProcess) requestManualStop() (err error) { return sys.Kill(dbp.pid, sys.SIGTRAP) } +const ( + ptraceOptionsNormal = syscall.PTRACE_O_TRACECLONE + ptraceOptionsFollowExec = syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEVFORK | syscall.PTRACE_O_TRACEEXEC +) + // Attach to a newly created thread, and store that thread in our list of // known threads. func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) { @@ -244,6 +249,11 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) return thread, nil } + ptraceOptions := ptraceOptionsNormal + if dbp.followExec { + ptraceOptions = ptraceOptionsFollowExec + } + var err error if attach { dbp.execPtraceFunc(func() { err = sys.PtraceAttach(tid) }) @@ -263,12 +273,12 @@ func (dbp *nativeProcess) addThread(tid int, attach bool) (*nativeThread, error) } } - dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) + dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) if err == syscall.ESRCH { if _, _, err = dbp.waitFast(tid); err != nil { return nil, fmt.Errorf("error while waiting after adding thread: %d %s", tid, err) } - dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, syscall.PTRACE_O_TRACECLONE) }) + dbp.execPtraceFunc(func() { err = syscall.PtraceSetOptions(tid, ptraceOptions) }) if err == syscall.ESRCH { return nil, err } @@ -318,8 +328,8 @@ func findExecutable(path string, pid int) string { return path } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, 0) +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return trapWaitInternal(procgrp, pid, 0) } type trapWaitOptions uint8 @@ -330,14 +340,21 @@ const ( trapWaitDontCallExitGuard ) -func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*nativeThread, error) { +func trapWaitInternal(procgrp *processGroup, pid int, options trapWaitOptions) (*nativeThread, error) { + var waitdbp *nativeProcess = nil + if len(procgrp.procs) == 1 { + // Note that waitdbp is only used to call (*nativeProcess).wait which will + // behave correctly if waitdbp == nil. + waitdbp = procgrp.procs[0] + } + halt := options&trapWaitHalt != 0 for { wopt := 0 if options&trapWaitNohang != 0 { wopt = sys.WNOHANG } - wpid, status, err := dbp.wait(pid, wopt) + wpid, status, err := waitdbp.wait(pid, wopt) if err != nil { return nil, fmt.Errorf("wait err %s %d", err, pid) } @@ -347,14 +364,27 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n } continue } - th, ok := dbp.threads[wpid] - if ok { - th.Status = (*waitStatus)(status) + dbp := procgrp.procForThread(wpid) + var th *nativeThread + if dbp != nil { + var ok bool + th, ok = dbp.threads[wpid] + if ok { + th.Status = (*waitStatus)(status) + } + } else { + dbp = procgrp.procs[0] } if status.Exited() { if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + } + if halt { + return nil, nil + } + continue } delete(dbp.threads, wpid) continue @@ -363,15 +393,24 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n // Signaled means the thread was terminated due to a signal. if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: -int(status.Signal())} + } + if halt { + return nil, nil + } + continue } // does this ever happen? delete(dbp.threads, wpid) continue } - if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { + if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_CLONE || status.TrapCause() == sys.PTRACE_EVENT_VFORK) { // A traced thread has cloned a new thread, grab the pid and // add it to our list of traced threads. + // If TrapCause() is sys.PTRACE_EVENT_VFORK this is actually a new + // process, but treat it as a normal thread until exec happens, so that + // we can initialize the new process normally. var cloned uint dbp.execPtraceFunc(func() { cloned, err = sys.PtraceGetEventMsg(wpid) }) if err != nil { @@ -410,6 +449,34 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n } continue } + if status.StopSignal() == sys.SIGTRAP && (status.TrapCause() == sys.PTRACE_EVENT_EXEC) { + // A thread called exec and we now have a new process. Retrieve the + // thread ID of the exec'ing thread with PtraceGetEventMsg to remove it + // and create a new nativeProcess object to track the new process. + var tid uint + dbp.execPtraceFunc(func() { tid, err = sys.PtraceGetEventMsg(wpid) }) + if err == nil { + delete(dbp.threads, int(tid)) + } + dbp = newChildProcess(procgrp.procs[0], wpid) + dbp.followExec = true + dbp.initializeBasic() + _, err := procgrp.add(dbp, dbp.pid, dbp.memthread, findExecutable("", dbp.pid), proc.StopLaunched) + if err != nil { + _ = dbp.Detach(false) + return nil, err + } + if halt { + return nil, nil + } + // TODO(aarzilli): if we want to give users the ability to stop the target + // group on exec here is where we should return + err = dbp.threads[dbp.pid].Continue() + if err != nil { + return nil, err + } + continue + } if th == nil { // Sometimes we get an unknown thread, ignore it? continue @@ -439,7 +506,10 @@ func (dbp *nativeProcess) trapWaitInternal(pid int, options trapWaitOptions) (*n // do the same thing we do if a thread quit if wpid == dbp.pid { dbp.postExit() - return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: wpid, Status: status.ExitStatus()} + } + continue } delete(dbp.threads, wpid) } @@ -476,7 +546,7 @@ func (dbp *nativeProcess) waitFast(pid int) (int, *sys.WaitStatus, error) { func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { var s sys.WaitStatus - if (pid != dbp.pid) || (options != 0) { + if (dbp == nil) || (pid != dbp.pid) || (options != 0) { wpid, err := sys.Wait4(pid, &s, sys.WALL|options, nil) return wpid, &s, err } @@ -506,12 +576,12 @@ func (dbp *nativeProcess) wait(pid, options int) (int, *sys.WaitStatus, error) { } } -func (dbp *nativeProcess) exitGuard(err error) error { +func exitGuard(dbp *nativeProcess, procgrp *processGroup, err error) error { if err != sys.ESRCH { return err } if status(dbp.pid, dbp.os.comm) == statusZombie { - _, err := dbp.trapWaitInternal(-1, trapWaitDontCallExitGuard) + _, err := trapWaitInternal(procgrp, -1, trapWaitDontCallExitGuard) return err } @@ -538,21 +608,27 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { - if dbp.exited { - return nil, proc.ErrProcessExited{Pid: dbp.pid} +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + if procgrp.numValid() == 0 { + return nil, proc.ErrProcessExited{Pid: procgrp.procs[0].pid} } - for _, th := range dbp.threads { - th.os.setbp = false + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + th.os.setbp = false + } } trapthread.os.setbp = true // check if any other thread simultaneously received a SIGTRAP for { - th, err := dbp.trapWaitInternal(-1, trapWaitNohang) + th, err := trapWaitInternal(procgrp, -1, trapWaitNohang) if err != nil { - return nil, dbp.exitGuard(err) + p := procgrp.procForThread(th.ID) + return nil, exitGuard(p, procgrp, err) } if th == nil { break @@ -560,10 +636,20 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ } // stop all threads that are still running - for _, th := range dbp.threads { - if th.os.running { - if err := th.stop(); err != nil { - return nil, dbp.exitGuard(err) + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.running { + if err := th.stop(); err != nil { + if err == sys.ESRCH { + // thread exited + delete(dbp.threads, th.ID) + } else { + return nil, exitGuard(dbp, procgrp, err) + } + } } } } @@ -571,26 +657,60 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ // wait for all threads to stop for { allstopped := true - for _, th := range dbp.threads { - if th.os.running { - allstopped = false - break + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.running { + allstopped = false + break + } } } if allstopped { break } - _, err := dbp.trapWaitInternal(-1, trapWaitHalt) + _, err := trapWaitInternal(procgrp, -1, trapWaitHalt) if err != nil { return nil, err } } - if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { - return nil, err + switchTrapthread := false + + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + err := stop1(cctx, dbp, trapthread, &switchTrapthread) + if err != nil { + return nil, err + } } - switchTrapthread := false + if switchTrapthread { + trapthreadID := trapthread.ID + trapthread = nil + for _, dbp := range procgrp.procs { + if dbp.exited { + continue + } + for _, th := range dbp.threads { + if th.os.setbp && th.ThreadID() != trapthreadID { + return th, nil + } + } + } + } + + return trapthread, nil +} + +func stop1(cctx *proc.ContinueOnceContext, dbp *nativeProcess, trapthread *nativeThread, switchTrapthread *bool) error { + if err := linutil.ElfUpdateSharedObjects(dbp); err != nil { + return err + } // set breakpoints on SIGTRAP threads var err1 error @@ -649,27 +769,13 @@ func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativ // Will switch to a different thread for trapthread because we don't // want pkg/proc to believe that this thread was stopped by a // hardcoded breakpoint. - switchTrapthread = true + *switchTrapthread = true } } } } } - if err1 != nil { - return nil, err1 - } - - if switchTrapthread { - trapthreadID := trapthread.ID - trapthread = nil - for _, th := range dbp.threads { - if th.os.setbp && th.ThreadID() != trapthreadID { - return th, nil - } - } - } - - return trapthread, nil + return err1 } func (dbp *nativeProcess) detach(kill bool) error { @@ -788,6 +894,25 @@ func (dbp *nativeProcess) SetUProbe(fnName string, goidOffset int64, args []ebpf return dbp.os.ebpf.AttachUprobe(dbp.pid, debugname, off) } +// FollowExec enables (or disables) follow exec mode +func (dbp *nativeProcess) FollowExec(v bool) error { + dbp.followExec = v + ptraceOptions := ptraceOptionsNormal + if dbp.followExec { + ptraceOptions = ptraceOptionsFollowExec + } + var err error + dbp.execPtraceFunc(func() { + for tid := range dbp.threads { + err = syscall.PtraceSetOptions(tid, ptraceOptions) + if err != nil { + return + } + } + }) + return err +} + func killProcess(pid int) error { return sys.Kill(pid, sys.SIGINT) } diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index d37249ed0d..705fe6e500 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -23,7 +23,7 @@ type osProcessDetails struct { func (os *osProcessDetails) Close() {} // Launch creates and begins debugging a new process. -func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.Target, error) { +func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.TargetGroup, error) { argv0Go := cmd[0] env := proc.DisableAsyncPreemptEnv() @@ -140,7 +140,7 @@ func findExePath(pid int) (string, error) { var debugPrivilegeRequested = false // Attach to an existing process with the given PID. -func Attach(pid int, _ []string) (*proc.Target, error) { +func Attach(pid int, _ []string) (*proc.TargetGroup, error) { var aperr error if !debugPrivilegeRequested { debugPrivilegeRequested = true @@ -427,7 +427,8 @@ func (dbp *nativeProcess) waitForDebugEvent(flags waitForDebugEventFlags) (threa } } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + dbp := procgrp.procs[0] var err error var tid, exitCode int dbp.execPtraceFunc(func() { @@ -474,7 +475,8 @@ func (dbp *nativeProcess) resume() error { } // stop stops all running threads threads and sets breakpoints -func (dbp *nativeProcess) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { +func (procgrp *processGroup) stop(cctx *proc.ContinueOnceContext, trapthread *nativeThread) (*nativeThread, error) { + dbp := procgrp.procs[0] if dbp.exited { return nil, proc.ErrProcessExited{Pid: dbp.pid} } diff --git a/pkg/proc/native/threads_linux.go b/pkg/proc/native/threads_linux.go index 4c25cc7b4f..5d5572abd2 100644 --- a/pkg/proc/native/threads_linux.go +++ b/pkg/proc/native/threads_linux.go @@ -22,6 +22,9 @@ type osSpecificDetails struct { func (t *nativeThread) stop() (err error) { err = sys.Tgkill(t.dbp.pid, t.ID, sys.SIGSTOP) if err != nil { + if err == sys.ESRCH { + return + } err = fmt.Errorf("stop err %s on thread %d", err, t.ID) return } diff --git a/pkg/proc/proc_export_test.go b/pkg/proc/proc_export_test.go deleted file mode 100644 index 45b67e5ff6..0000000000 --- a/pkg/proc/proc_export_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package proc - -// Wrapper functions so that most tests proc_test.go don't need to worry -// about TargetGroup when they just need to resume a single process. - -func newGroupTransient(tgt *Target) *TargetGroup { - grp := NewGroup(tgt) - tgt.partOfGroup = false - return grp -} - -func (tgt *Target) Detach(kill bool) error { - return tgt.detach(kill) -} - -func (tgt *Target) Continue() error { - return newGroupTransient(tgt).Continue() -} - -func (tgt *Target) Next() error { - return newGroupTransient(tgt).Next() -} - -func (tgt *Target) Step() error { - return newGroupTransient(tgt).Step() -} - -func (tgt *Target) StepOut() error { - return newGroupTransient(tgt).StepOut() -} - -func (tgt *Target) ChangeDirection(dir Direction) error { - return tgt.recman.ChangeDirection(dir) -} - -func (tgt *Target) StepInstruction() error { - return newGroupTransient(tgt).StepInstruction() -} - -func (tgt *Target) Recorded() (bool, string) { - return tgt.recman.Recorded() -} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 5a27329b31..013d1c83b7 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -88,28 +88,28 @@ func skipUnlessOn(t testing.TB, reason string, conditions ...string) { } } -func withTestProcess(name string, t testing.TB, fn func(p *proc.Target, fixture protest.Fixture)) { +func withTestProcess(name string, t testing.TB, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) { withTestProcessArgs(name, t, ".", []string{}, 0, fn) } -func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, fixture protest.Fixture)) { +func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture)) { if buildMode == "pie" { buildFlags |= protest.BuildModePIE } fixture := protest.BuildFixture(name, buildFlags) - var p *proc.Target + var grp *proc.TargetGroup var err error var tracedir string switch testBackend { case "native": - p, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) + grp, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) case "lldb": - p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) + grp, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{}) case "rr": protest.MustHaveRecordingAllowed(t) t.Log("recording") - p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) + grp, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) t.Logf("replaying %q", tracedir) default: t.Fatal("unknown backend") @@ -119,10 +119,10 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu } defer func() { - p.Detach(true) + grp.Detach(true) }() - fn(p, fixture) + fn(grp.Selected, grp, fixture) } func getRegisters(p *proc.Target, t *testing.T) proc.Registers { @@ -174,8 +174,8 @@ func assertLineNumber(p *proc.Target, t *testing.T, lineno int, descr string) (s func TestExit(t *testing.T) { protest.AllowRecording(t) - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", err) @@ -191,10 +191,10 @@ func TestExit(t *testing.T) { func TestExitAfterContinue(t *testing.T) { protest.AllowRecording(t) - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "First Continue()") - err := p.Continue() + assertNoError(grp.Continue(), t, "First Continue()") + err := grp.Continue() pe, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %s", pe) @@ -272,8 +272,7 @@ func findFileLocation(p *proc.Target, t *testing.T, file string, lineno int) uin func TestHalt(t *testing.T) { stopChan := make(chan interface{}, 1) - withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.loop") assertNoError(grp.Continue(), t, "Continue") resumeChan := make(chan struct{}, 1) @@ -294,9 +293,9 @@ func TestHalt(t *testing.T) { func TestStep(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.helloworld") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") regs := getRegisters(p, t) rip := regs.PC() @@ -313,9 +312,9 @@ func TestStep(t *testing.T) { func TestBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.helloworld") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -334,10 +333,10 @@ func TestBreakpoint(t *testing.T) { func TestBreakpointInSeparateGoRoutine(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testthreads", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testthreads", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.anotherthread") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -351,7 +350,7 @@ func TestBreakpointInSeparateGoRoutine(t *testing.T) { } func TestBreakpointWithNonExistantFunction(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { _, err := p.SetBreakpoint(0, 0, proc.UserBreakpoint, nil) if err == nil { t.Fatal("Should not be able to break at non existent function") @@ -360,7 +359,7 @@ func TestBreakpointWithNonExistantFunction(t *testing.T) { } func TestClearBreakpointBreakpoint(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sleepytime") err := p.ClearBreakpoint(bp.Addr) @@ -434,7 +433,7 @@ func testseq2(t *testing.T, program string, initialLocation string, testcases [] func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *testing.T, program string, initialLocation string, testcases []seqTest) { protest.AllowRecording(t) - withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs(program, t, wd, args, buildFlags, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { var bp *proc.Breakpoint if initialLocation != "" { bp = setFunctionBreakpoint(p, t, initialLocation) @@ -456,22 +455,22 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te if traceTestseq2 { t.Log("next") } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") case contStep: if traceTestseq2 { t.Log("step") } - assertNoError(p.Step(), t, "Step() returned an error") + assertNoError(grp.Step(), t, "Step() returned an error") case contStepout: if traceTestseq2 { t.Log("stepout") } - assertNoError(p.StepOut(), t, "StepOut() returned an error") + assertNoError(grp.StepOut(), t, "StepOut() returned an error") case contContinue: if traceTestseq2 { t.Log("continue") } - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") if i == 0 { if traceTestseq2 { t.Log("clearing initial breakpoint") @@ -483,29 +482,29 @@ func testseq2Args(wd string, args []string, buildFlags protest.BuildFlags, t *te if traceTestseq2 { t.Log("reverse-next") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.Next(), t, "reverse Next() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Next(), t, "reverse Next() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contReverseStep: if traceTestseq2 { t.Log("reverse-step") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.Step(), t, "reverse Step() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.Step(), t, "reverse Step() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contReverseStepout: if traceTestseq2 { t.Log("reverse-stepout") } - assertNoError(p.ChangeDirection(proc.Backward), t, "direction switch") - assertNoError(p.StepOut(), t, "reverse StepOut() returned an error") - assertNoError(p.ChangeDirection(proc.Forward), t, "direction switch") + assertNoError(grp.ChangeDirection(proc.Backward), t, "direction switch") + assertNoError(grp.StepOut(), t, "reverse StepOut() returned an error") + assertNoError(grp.ChangeDirection(proc.Forward), t, "direction switch") case contContinueToBreakpoint: bp := setFileBreakpoint(p, t, fixture.Source, tc.pos.(int)) if traceTestseq2 { t.Log("continue") } - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint() returned an error") } @@ -592,9 +591,9 @@ func TestNextConcurrent(t *testing.T) { {10, 11}, } protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") f, ln := currentLineNumber(p, t) initV := evalVariable(p, t, "n") initVval, _ := constant.Int64Val(initV.Value) @@ -609,7 +608,7 @@ func TestNextConcurrent(t *testing.T) { if ln != tc.begin { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location") v := evalVariable(p, t, "n") vval, _ := constant.Int64Val(v.Value) @@ -628,9 +627,9 @@ func TestNextConcurrentVariant2(t *testing.T) { {10, 11}, } protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.sayhi") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") f, ln := currentLineNumber(p, t) initV := evalVariable(p, t, "n") initVval, _ := constant.Int64Val(initV.Value) @@ -644,7 +643,7 @@ func TestNextConcurrentVariant2(t *testing.T) { if ln != tc.begin { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") var vval int64 for { v := evalVariable(p, t, "n") @@ -661,7 +660,7 @@ func TestNextConcurrentVariant2(t *testing.T) { if vval == initVval { t.Fatal("Initial breakpoint triggered twice for the same goroutine") } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") } } f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to the expected location") @@ -706,7 +705,7 @@ func TestNextNetHTTP(t *testing.T) { {11, 12}, {12, 13}, } - withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { go func() { // Wait for program to start listening. for { @@ -719,7 +718,7 @@ func TestNextNetHTTP(t *testing.T) { } http.Get("http://127.0.0.1:9191") }() - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { t.Fatal(err) } f, ln := currentLineNumber(p, t) @@ -728,7 +727,7 @@ func TestNextNetHTTP(t *testing.T) { t.Fatalf("Program not stopped at correct spot expected %d was %s:%d", tc.begin, filepath.Base(f), ln) } - assertNoError(p.Next(), t, "Next() returned an error") + assertNoError(grp.Next(), t, "Next() returned an error") f, ln = assertLineNumber(p, t, tc.end, "Program did not continue to correct next location") } @@ -736,8 +735,8 @@ func TestNextNetHTTP(t *testing.T) { } func TestRuntimeBreakpoint(t *testing.T) { - withTestProcess("testruntimebreakpoint", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testruntimebreakpoint", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() if err != nil { t.Fatal(err) } @@ -764,9 +763,9 @@ func returnAddress(thread proc.Thread) (uint64, error) { func TestFindReturnAddress(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 24) - err := p.Continue() + err := grp.Continue() if err != nil { t.Fatal(err) } @@ -783,10 +782,10 @@ func TestFindReturnAddress(t *testing.T) { func TestFindReturnAddressTopOfStackFn(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testreturnaddress", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testreturnaddress", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fnName := "runtime.rt0_go" setFunctionBreakpoint(p, t, fnName) - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { t.Fatal(err) } if _, err := returnAddress(p.CurrentThread()); err == nil { @@ -797,14 +796,14 @@ func TestFindReturnAddressTopOfStackFn(t *testing.T) { func TestSwitchThread(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // With invalid thread id err := p.SwitchThread(-1) if err == nil { t.Fatal("Expected error for invalid thread id") } setFunctionBreakpoint(p, t, "main.main") - err = p.Continue() + err = grp.Continue() if err != nil { t.Fatal(err) } @@ -842,10 +841,10 @@ func TestCGONext(t *testing.T) { skipOn(t, "broken - see https://github.com/go-delve/delve/issues/3158", "darwin", "amd64") protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") }) } @@ -853,7 +852,7 @@ func TestCGOBreakpointLocation(t *testing.T) { protest.MustHaveCgo(t) protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "C.foo") if !strings.Contains(bp.File, "cgotest.go") { t.Fatalf("incorrect breakpoint location, expected cgotest.go got %s", bp.File) @@ -881,11 +880,11 @@ func TestStacktrace(t *testing.T) { {{4, "main.stacktraceme"}, {8, "main.func1"}, {12, "main.func2"}, {17, "main.main"}}, } protest.AllowRecording(t) - withTestProcess("stacktraceprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stacktraceprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.stacktraceme") for i := range stacks { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") @@ -906,13 +905,13 @@ func TestStacktrace(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } func TestStacktrace2(t *testing.T) { - withTestProcess("retstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("retstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") @@ -923,7 +922,7 @@ func TestStacktrace2(t *testing.T) { t.Fatalf("Stack error at main.f()\n%v\n", locations) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") locations, err = proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "Stacktrace()") if !stackMatch([]loc{{-1, "main.g"}, {17, "main.main"}}, locations, false) { @@ -977,10 +976,10 @@ func TestStacktraceGoroutine(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -1030,14 +1029,14 @@ func TestStacktraceGoroutine(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } func TestKill(t *testing.T) { skipOn(t, "N/A", "lldb") // k command presumably works but leaves the process around? - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { - if err := p.Detach(true); err != nil { + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + if err := grp.Detach(true); err != nil { t.Fatal(err) } if valid, _ := p.Valid(); valid { @@ -1056,10 +1055,10 @@ func TestKill(t *testing.T) { }) } -func testGSupportFunc(name string, t *testing.T, p *proc.Target, fixture protest.Fixture) { +func testGSupportFunc(name string, t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, name+": Continue()") + assertNoError(grp.Continue(), t, name+": Continue()") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, name+": GetG()") @@ -1074,8 +1073,8 @@ func testGSupportFunc(name string, t *testing.T, p *proc.Target, fixture protest } func TestGetG(t *testing.T) { - withTestProcess("testprog", t, func(p *proc.Target, fixture protest.Fixture) { - testGSupportFunc("nocgo", t, p, fixture) + withTestProcess("testprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testGSupportFunc("nocgo", t, p, grp, fixture) }) // On OSX with Go < 1.5 CGO is not supported due to: https://github.com/golang/go/issues/8973 @@ -1085,21 +1084,21 @@ func TestGetG(t *testing.T) { protest.MustHaveCgo(t) protest.AllowRecording(t) - withTestProcess("cgotest", t, func(p *proc.Target, fixture protest.Fixture) { - testGSupportFunc("cgo", t, p, fixture) + withTestProcess("cgotest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testGSupportFunc("cgo", t, p, grp, fixture) }) } func TestContinueMulti(t *testing.T) { protest.AllowRecording(t) - withTestProcess("integrationprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("integrationprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp1 := setFunctionBreakpoint(p, t, "main.main") bp2 := setFunctionBreakpoint(p, t, "main.sayhi") mainCount := 0 sayhiCount := 0 for { - err := p.Continue() + err := grp.Continue() if valid, _ := p.Valid(); !valid { break } @@ -1130,8 +1129,8 @@ func TestBreakpointOnFunctionEntry(t *testing.T) { func TestProcessReceivesSIGCHLD(t *testing.T) { protest.AllowRecording(t) - withTestProcess("sigchldprog", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("sigchldprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() _, ok := err.(proc.ErrProcessExited) if !ok { t.Fatalf("Continue() returned unexpected error type %v", err) @@ -1140,9 +1139,9 @@ func TestProcessReceivesSIGCHLD(t *testing.T) { } func TestIssue239(t *testing.T) { - withTestProcess("is sue239", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("is sue239", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 17) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") }) } @@ -1235,8 +1234,8 @@ func TestVariableEvaluation(t *testing.T) { {"ba", reflect.Slice, nil, 200, 200, 64}, } - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, tc := range testcases { v := evalVariable(p, t, tc.name) @@ -1289,9 +1288,9 @@ func TestFrameEvaluation(t *testing.T) { if runtime.GOOS == "windows" { lenient = true } - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine()) @@ -1344,7 +1343,7 @@ func TestFrameEvaluation(t *testing.T) { } // Testing evaluation on frames - assertNoError(p.Continue(), t, "Continue() 2") + assertNoError(grp.Continue(), t, "Continue() 2") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") @@ -1372,8 +1371,8 @@ func TestThreadFrameEvaluation(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { t.SkipNow() } - withTestProcess("testdeadlock", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp { @@ -1390,8 +1389,8 @@ func TestThreadFrameEvaluation(t *testing.T) { } func TestPointerSetting(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") pval := func(n int64) { variable := evalVariable(p, t, "p1") @@ -1418,15 +1417,15 @@ func TestPointerSetting(t *testing.T) { } func TestVariableFunctionScoping(t *testing.T) { - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") evalVariable(p, t, "a1") evalVariable(p, t, "a2") // Move scopes, a1 exists here by a2 does not - err = p.Continue() + err = grp.Continue() assertNoError(err, t, "Continue() returned an error") evalVariable(p, t, "a1") @@ -1440,8 +1439,8 @@ func TestVariableFunctionScoping(t *testing.T) { func TestRecursiveStructure(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "aas") t.Logf("v: %v\n", v) }) @@ -1450,8 +1449,8 @@ func TestRecursiveStructure(t *testing.T) { func TestIssue316(t *testing.T) { // A pointer loop that includes one interface should not send dlv into an infinite loop protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") evalVariable(p, t, "iface5") }) } @@ -1459,8 +1458,8 @@ func TestIssue316(t *testing.T) { func TestIssue325(t *testing.T) { // nil pointer dereference when evaluating interfaces to function pointers protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") iface2fn1v := evalVariable(p, t, "iface2fn1") t.Logf("iface2fn1: %v\n", iface2fn1v) @@ -1471,11 +1470,11 @@ func TestIssue325(t *testing.T) { func TestBreakpointCounts(t *testing.T) { protest.AllowRecording(t) - withTestProcess("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 12) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1501,10 +1500,10 @@ func TestBreakpointCounts(t *testing.T) { } func TestHardcodedBreakpointCounts(t *testing.T) { - withTestProcess("hcbpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("hcbpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { counts := map[int64]int{} for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1540,8 +1539,8 @@ func TestHardcodedBreakpointCounts(t *testing.T) { func BenchmarkArray(b *testing.B) { // each bencharr struct is 128 bytes, bencharr is 64 elements long b.SetBytes(int64(64 * 128)) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "bencharr") @@ -1557,11 +1556,11 @@ func TestBreakpointCountsWithDetection(t *testing.T) { } m := map[int64]int64{} protest.AllowRecording(t) - withTestProcess("bpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("bpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 12) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -1613,8 +1612,8 @@ func BenchmarkArrayPointer(b *testing.B) { // each bencharr struct is 128 bytes, benchparr is an array of 64 pointers to bencharr // each read will read 64 bencharr structs plus the 64 pointers of benchparr b.SetBytes(int64(64*128 + 64*8)) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "bencharr") @@ -1627,8 +1626,8 @@ func BenchmarkMap(b *testing.B) { // each string key has an average of 9 character // reading strings and the map structure imposes a overhead that we ignore here b.SetBytes(int64(41 * (2*8 + 9))) - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { evalVariable(p, b, "m1") @@ -1637,8 +1636,8 @@ func BenchmarkMap(b *testing.B) { } func BenchmarkGoroutinesInfo(b *testing.B) { - withTestProcess("testvariables2", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue()") + withTestProcess("testvariables2", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue()") b.ResetTimer() for i := 0; i < b.N; i++ { p.ClearCaches() @@ -1651,11 +1650,11 @@ func BenchmarkGoroutinesInfo(b *testing.B) { func TestIssue262(t *testing.T) { // Continue does not work when the current breakpoint is set on a NOP instruction protest.AllowRecording(t) - withTestProcess("issue262", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue262", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) - assertNoError(p.Continue(), t, "Continue()") - err := p.Continue() + assertNoError(grp.Continue(), t, "Continue()") + err := grp.Continue() if err == nil { t.Fatalf("No error on second continue") } @@ -1671,16 +1670,16 @@ func TestIssue305(t *testing.T) { // the internal breakpoints aren't cleared preventing further use of // 'next' command protest.AllowRecording(t) - withTestProcess("issue305", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue305", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 5) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next() 1") - assertNoError(p.Next(), t, "Next() 2") - assertNoError(p.Next(), t, "Next() 3") - assertNoError(p.Next(), t, "Next() 4") - assertNoError(p.Next(), t, "Next() 5") + assertNoError(grp.Next(), t, "Next() 1") + assertNoError(grp.Next(), t, "Next() 2") + assertNoError(grp.Next(), t, "Next() 3") + assertNoError(grp.Next(), t, "Next() 4") + assertNoError(grp.Next(), t, "Next() 5") }) } @@ -1688,8 +1687,8 @@ func TestPointerLoops(t *testing.T) { // Pointer loops through map entries, pointers and slices // Regression test for issue #341 protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, expr := range []string{"mapinf", "ptrinf", "sliceinf"} { t.Logf("requesting %s", expr) v := evalVariable(p, t, expr) @@ -1699,8 +1698,8 @@ func TestPointerLoops(t *testing.T) { } func BenchmarkLocalVariables(b *testing.B) { - withTestProcess("testvariables", b, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), b, "Continue() returned an error") + withTestProcess("testvariables", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), b, "Continue() returned an error") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, b, "Scope()") b.ResetTimer() @@ -1713,7 +1712,7 @@ func BenchmarkLocalVariables(b *testing.B) { func TestCondBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -1721,7 +1720,7 @@ func TestCondBreakpoint(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") nvar := evalVariable(p, t, "n") @@ -1734,7 +1733,7 @@ func TestCondBreakpoint(t *testing.T) { func TestCondBreakpointError(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -1742,7 +1741,7 @@ func TestCondBreakpointError(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - err := p.Continue() + err := grp.Continue() if err == nil { t.Fatalf("No error on first Continue()") } @@ -1757,7 +1756,7 @@ func TestCondBreakpointError(t *testing.T) { Y: &ast.BasicLit{Kind: token.INT, Value: "7"}, } - err = p.Continue() + err = grp.Continue() if err != nil { if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on second Continue(): %v", err) @@ -1774,14 +1773,14 @@ func TestCondBreakpointError(t *testing.T) { } func TestHitCondBreakpointEQ(t *testing.T) { - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token Val int }{token.EQL, 3} - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1789,7 +1788,7 @@ func TestHitCondBreakpointEQ(t *testing.T) { t.Fatalf("Stopped on wrong hitcount %d\n", i) } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on Continue(): %v", err) } @@ -1798,7 +1797,7 @@ func TestHitCondBreakpointEQ(t *testing.T) { func TestHitCondBreakpointGEQ(t *testing.T) { protest.AllowRecording(t) - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token @@ -1806,7 +1805,7 @@ func TestHitCondBreakpointGEQ(t *testing.T) { }{token.GEQ, 3} for it := 3; it <= 10; it++ { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1815,13 +1814,13 @@ func TestHitCondBreakpointGEQ(t *testing.T) { } } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") }) } func TestHitCondBreakpointREM(t *testing.T) { protest.AllowRecording(t) - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 7) bp.Logical.HitCond = &struct { Op token.Token @@ -1829,7 +1828,7 @@ func TestHitCondBreakpointREM(t *testing.T) { }{token.REM, 2} for it := 2; it <= 10; it += 2 { - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ivar := evalVariable(p, t, "i") i, _ := constant.Int64Val(ivar.Value) @@ -1838,7 +1837,7 @@ func TestHitCondBreakpointREM(t *testing.T) { } } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { t.Fatalf("Unexpected error on Continue(): %v", err) } @@ -1848,8 +1847,8 @@ func TestHitCondBreakpointREM(t *testing.T) { func TestIssue356(t *testing.T) { // slice with a typedef does not get printed correctly protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") mmvar := evalVariable(p, t, "mainMenu") if mmvar.Kind != reflect.Slice { t.Fatalf("Wrong kind for mainMenu: %v\n", mmvar.Kind) @@ -1858,11 +1857,11 @@ func TestIssue356(t *testing.T) { } func TestStepIntoFunction(t *testing.T) { - withTestProcess("teststep", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // Continue until breakpoint - assertNoError(p.Continue(), t, "Continue() returned an error") + assertNoError(grp.Continue(), t, "Continue() returned an error") // Step into function - assertNoError(p.Step(), t, "Step() returned an error") + assertNoError(grp.Step(), t, "Step() returned an error") // We should now be inside the function. loc, err := p.CurrentThread().Location() if err != nil { @@ -1883,10 +1882,10 @@ func TestStepIntoFunction(t *testing.T) { func TestIssue332_Part1(t *testing.T) { // Next shouldn't step inside a function call protest.AllowRecording(t) - withTestProcess("issue332", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 8) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "first Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "first Next()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { @@ -1907,13 +1906,13 @@ func TestIssue332_Part2(t *testing.T) { // which leads to 'next' and 'stack' failing with error "could not find FDE for PC: " // because the incorrect FDE data leads to reading the wrong stack address as the return address protest.AllowRecording(t) - withTestProcess("issue332", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue332", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 8) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") // step until we enter changeMe for { - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") locations, err := proc.ThreadStacktrace(p.CurrentThread(), 2) assertNoError(err, t, "Stacktrace()") if locations[0].Call.Fn == nil { @@ -1935,10 +1934,10 @@ func TestIssue332_Part2(t *testing.T) { t.Fatalf("Step did not skip the prologue: current pc: %x, first instruction after prologue: %x", pc, pcAfterPrologue) } - assertNoError(p.Next(), t, "first Next()") - assertNoError(p.Next(), t, "second Next()") - assertNoError(p.Next(), t, "third Next()") - err = p.Continue() + assertNoError(grp.Next(), t, "first Next()") + assertNoError(grp.Next(), t, "second Next()") + assertNoError(grp.Next(), t, "third Next()") + err = grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { assertNoError(err, t, "final Continue()") } @@ -1949,9 +1948,9 @@ func TestIssue414(t *testing.T) { skipOn(t, "broken", "linux", "386", "pie") // test occasionally hangs on linux/386/pie // Stepping until the program exits protest.AllowRecording(t) - withTestProcess("math", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("math", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") for { pc := currentPC(p, t) f, ln := currentLineNumber(p, t) @@ -1961,9 +1960,9 @@ func TestIssue414(t *testing.T) { // of main.main just use Next. // See: https://github.com/go-delve/delve/pull/2082 if f == fixture.Source { - err = p.Step() + err = grp.Step() } else { - err = p.Next() + err = grp.Next() } if err != nil { if _, exited := err.(proc.ErrProcessExited); exited { @@ -1985,8 +1984,8 @@ func TestPackageVariables(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "Scope()") @@ -2014,15 +2013,15 @@ func TestIssue149(t *testing.T) { return } // setting breakpoint on break statement - withTestProcess("break", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { findFileLocation(p, t, fixture.Source, 8) }) } func TestPanicBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic { t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) @@ -2031,8 +2030,8 @@ func TestPanicBreakpoint(t *testing.T) { } func TestCmdLineArgs(t *testing.T) { - expectSuccess := func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + expectSuccess := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() bp := p.CurrentThread().Breakpoint() if bp.Breakpoint != nil && bp.Logical.Name == proc.UnrecoveredPanic { t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", bp) @@ -2047,8 +2046,8 @@ func TestCmdLineArgs(t *testing.T) { } } - expectPanic := func(p *proc.Target, fixture protest.Fixture) { - p.Continue() + expectPanic := func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.Continue() bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != proc.UnrecoveredPanic { t.Fatalf("not on unrecovered-panic breakpoint: %v", bp) @@ -2072,8 +2071,7 @@ func TestCmdLineArgs(t *testing.T) { func TestIssue462(t *testing.T) { skipOn(t, "broken", "windows") // Stacktrace of Goroutine 0 fails with an error - withTestProcess("testnextnethttp", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testnextnethttp", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { go func() { // Wait for program to start listening. for { @@ -2096,13 +2094,13 @@ func TestIssue462(t *testing.T) { func TestNextParked(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") // continue until a parked goroutine exists var parkedg *proc.G for parkedg == nil { - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return @@ -2136,7 +2134,7 @@ func TestNextParked(t *testing.T) { assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()") p.ClearBreakpoint(bp.Addr) - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") if p.SelectedGoroutine().ID != parkedg.ID { t.Fatalf("Next did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID) @@ -2146,14 +2144,14 @@ func TestNextParked(t *testing.T) { func TestStepParked(t *testing.T) { protest.AllowRecording(t) - withTestProcess("parallel_next", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("parallel_next", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.sayhi") // continue until a parked goroutine exists var parkedg *proc.G LookForParkedG: for { - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); exited { t.Log("could not find parked goroutine") return @@ -2183,7 +2181,7 @@ func TestStepParked(t *testing.T) { assertNoError(p.SwitchGoroutine(parkedg), t, "SwitchGoroutine()") p.ClearBreakpoint(bp.Addr) - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") if p.SelectedGoroutine().ID != parkedg.ID { t.Fatalf("Step did not continue on the selected goroutine, expected %d got %d", parkedg.ID, p.SelectedGoroutine().ID) @@ -2215,7 +2213,7 @@ func TestUnsupportedArch(t *testing.T) { } defer os.Remove(outfile) - var p *proc.Target + var p *proc.TargetGroup switch testBackend { case "native": @@ -2243,17 +2241,17 @@ func TestIssue573(t *testing.T) { // calls to runtime.duffzero and runtime.duffcopy jump directly into the middle // of the function and the internal breakpoint set by StepInto may be missed. protest.AllowRecording(t) - withTestProcess("issue573", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue573", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.foo") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step() #1") - assertNoError(p.Step(), t, "Step() #2") // Bug exits here. - assertNoError(p.Step(), t, "Step() #3") // Third step ought to be possible; program ought not have exited. + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step() #1") + assertNoError(grp.Step(), t, "Step() #2") // Bug exits here. + assertNoError(grp.Step(), t, "Step() #3") // Third step ought to be possible; program ought not have exited. }) } func TestTestvariables2Prologue(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { addrEntry := p.BinInfo().LookupFunc["main.main"].Entry addrPrologue := findFunctionLocation(p, t, "main.main") if addrEntry == addrPrologue { @@ -2366,24 +2364,24 @@ func TestIssue561(t *testing.T) { // Step fails to make progress when PC is at a CALL instruction // where a breakpoint is also set. protest.AllowRecording(t) - withTestProcess("issue561", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue561", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 10) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 5, "wrong line number after Step,") }) } func TestGoroutineLables(t *testing.T) { - withTestProcess("goroutineLabels", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("goroutineLabels", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") if len(g.Labels()) != 0 { t.Fatalf("No labels expected") } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") g, err = proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG()") labels := g.Labels() @@ -2403,10 +2401,10 @@ func TestStepOut(t *testing.T) { func TestStepConcurrentDirect(t *testing.T) { skipOn(t, "broken - step concurrent", "windows", "arm64") protest.AllowRecording(t) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 37) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint()") @@ -2455,7 +2453,7 @@ func TestStepConcurrentDirect(t *testing.T) { if i == 0 { count++ } - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") } if count != 100 { @@ -2466,7 +2464,7 @@ func TestStepConcurrentDirect(t *testing.T) { func TestStepConcurrentPtr(t *testing.T) { protest.AllowRecording(t) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 24) for _, b := range p.Breakpoints().M { @@ -2480,7 +2478,7 @@ func TestStepConcurrentPtr(t *testing.T) { kvals := map[int64]int64{} count := 0 for { - err := p.Continue() + err := grp.Continue() _, exited := err.(proc.ErrProcessExited) if exited { break @@ -2508,12 +2506,12 @@ func TestStepConcurrentPtr(t *testing.T) { } kvals[gid] = k - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") for p.Breakpoints().HasSteppingBreakpoints() { if p.SelectedGoroutine().ID == gid { t.Fatalf("step did not step into function call (but internal breakpoints still active?) (%d %d)", gid, p.SelectedGoroutine().ID) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") } if p.SelectedGoroutine().ID != gid { @@ -2538,14 +2536,14 @@ func TestStepConcurrentPtr(t *testing.T) { func TestStepOutBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 13) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) // StepOut should be interrupted by a breakpoint on the same goroutine. setFileBreakpoint(p, t, fixture.Source, 14) - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") assertLineNumber(p, t, 14, "wrong line number") if p.Breakpoints().HasSteppingBreakpoints() { t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine") @@ -2555,14 +2553,14 @@ func TestStepOutBreakpoint(t *testing.T) { func TestNextBreakpoint(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 34) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) // Next should be interrupted by a breakpoint on the same goroutine. setFileBreakpoint(p, t, fixture.Source, 14) - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 14, "wrong line number") if p.Breakpoints().HasSteppingBreakpoints() { t.Fatal("has internal breakpoints after hitting breakpoint on same goroutine") @@ -2572,8 +2570,7 @@ func TestNextBreakpoint(t *testing.T) { func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { grp.KeepSteppingBreakpoints = proc.TracepointKeepsSteppingBreakpoints bp := setFileBreakpoint(p, t, fixture.Source, 34) assertNoError(grp.Continue(), t, "Continue()") @@ -2599,14 +2596,14 @@ func TestNextBreakpointKeepsSteppingBreakpoints(t *testing.T) { func TestStepOutDefer(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testnextdefer", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextdefer", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") p.ClearBreakpoint(bp.Addr) assertLineNumber(p, t, 9, "wrong line number") - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") f, l, _ := p.BinInfo().PCToLine(currentPC(p, t)) if f == fixture.Source || l == 6 { @@ -2631,13 +2628,13 @@ func TestStepInstructionOnBreakpoint(t *testing.T) { // StepInstruction should step one instruction forward when // PC is on a 1 byte instruction with a software breakpoint. protest.AllowRecording(t) - withTestProcess("break/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("break/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, filepath.ToSlash(filepath.Join(fixture.BuildDir, "break_amd64.s")), 4) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") pc := getRegisters(p, t).PC() - assertNoError(p.StepInstruction(), t, "StepInstruction()") + assertNoError(grp.StepInstruction(), t, "StepInstruction()") if pc == getRegisters(p, t).PC() { t.Fatal("Could not step a single instruction") } @@ -2646,10 +2643,10 @@ func TestStepInstructionOnBreakpoint(t *testing.T) { func TestStepOnCallPtrInstr(t *testing.T) { protest.AllowRecording(t) - withTestProcess("teststepprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 10) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") found := false @@ -2667,14 +2664,14 @@ func TestStepOnCallPtrInstr(t *testing.T) { found = true break } - assertNoError(p.StepInstruction(), t, "StepInstruction()") + assertNoError(grp.StepInstruction(), t, "StepInstruction()") } if !found { t.Fatal("Could not find CALL instruction") } - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) && !protest.RegabiSupported() { assertLineNumber(p, t, 6, "Step continued to wrong line,") @@ -2697,8 +2694,8 @@ func TestIssue594(t *testing.T) { // In particular the target should be able to cause a nil pointer // dereference panic and recover from it. protest.AllowRecording(t) - withTestProcess("issue594", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue594", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") var f string var ln int if testBackend == "rr" { @@ -2730,9 +2727,9 @@ func TestWorkDir(t *testing.T) { wd = "/private/tmp" } protest.AllowRecording(t) - withTestProcessArgs("workdir", t, wd, []string{}, 0, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("workdir", t, wd, []string{}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 14) - p.Continue() + grp.Continue() v := evalVariable(p, t, "pwd") str := constant.StringVal(v.Value) if wd != str { @@ -2752,8 +2749,8 @@ func TestNegativeIntEvaluation(t *testing.T) { {"ni32", "int32", int64(-5)}, } protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { v := evalVariable(p, t, tc.name) if typ := v.RealType.String(); typ != tc.typ { @@ -2769,13 +2766,13 @@ func TestNegativeIntEvaluation(t *testing.T) { func TestIssue683(t *testing.T) { // Step panics when source file can not be found protest.AllowRecording(t) - withTestProcess("issue683", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue683", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") for i := 0; i < 20; i++ { // eventually an error about the source file not being found will be // returned, the important thing is that we shouldn't panic - err := p.Step() + err := grp.Step() if err != nil { break } @@ -2785,21 +2782,21 @@ func TestIssue683(t *testing.T) { func TestIssue664(t *testing.T) { protest.AllowRecording(t) - withTestProcess("issue664", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue664", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 4) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 5, "Did not continue to correct location,") }) } // Benchmarks (*Process).Continue + (*Scope).FunctionArguments func BenchmarkTrace(b *testing.B) { - withTestProcess("traceperf", b, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("traceperf", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, b, "main.PerfCheck") b.ResetTimer() for i := 0; i < b.N; i++ { - assertNoError(p.Continue(), b, "Continue()") + assertNoError(grp.Continue(), b, "Continue()") s, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, b, "Scope()") _, err = s.FunctionArguments(proc.LoadConfig{false, 0, 64, 0, 3, 0}) @@ -2815,9 +2812,9 @@ func TestNextInDeferReturn(t *testing.T) { // field being nil. // We need to deal with this without panicing. protest.AllowRecording(t) - withTestProcess("defercall", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("defercall", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "runtime.deferreturn") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") // Set a breakpoint on the deferred function so that the following loop // can not step out of the runtime.deferreturn and all the way to the @@ -2830,7 +2827,7 @@ func TestNextInDeferReturn(t *testing.T) { if loc.Fn != nil && loc.Fn.Name == "main.sampleFunction" { break } - assertNoError(p.Next(), t, fmt.Sprintf("Next() %d", i)) + assertNoError(grp.Next(), t, fmt.Sprintf("Next() %d", i)) } }) } @@ -2879,7 +2876,7 @@ func TestAttachDetach(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -2902,7 +2899,7 @@ func TestAttachDetach(t *testing.T) { }() assertNoError(p.Continue(), t, "Continue") - assertLineNumber(p, t, 11, "Did not continue to correct location,") + assertLineNumber(p.Selected, t, 11, "Did not continue to correct location,") assertNoError(p.Detach(false), t, "Detach") @@ -2923,8 +2920,8 @@ func TestAttachDetach(t *testing.T) { func TestVarSum(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") sumvar := evalVariable(p, t, "s1[0] + s1[1]") sumvarstr := constant.StringVal(sumvar.Value) if sumvarstr != "onetwo" { @@ -2938,8 +2935,8 @@ func TestVarSum(t *testing.T) { func TestPackageWithPathVar(t *testing.T) { protest.AllowRecording(t) - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") evalVariable(p, t, "pkg.SomeVar") evalVariable(p, t, "pkg.SomeVar.X") }) @@ -2948,8 +2945,8 @@ func TestPackageWithPathVar(t *testing.T) { func TestEnvironment(t *testing.T) { protest.AllowRecording(t) os.Setenv("SOMEVAR", "bah") - withTestProcess("testenv", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testenv", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "x") vv := constant.StringVal(v.Value) t.Logf("v = %q", vv) @@ -2975,24 +2972,24 @@ func TestRecursiveNext(t *testing.T) { } testseq("increment", contNext, testcases, "main.Increment", t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.Increment") - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") err := p.ClearBreakpoint(bp.Addr) assertNoError(err, t, "ClearBreakpoint") - assertNoError(p.Next(), t, "Next 1") - assertNoError(p.Next(), t, "Next 2") - assertNoError(p.Next(), t, "Next 3") + assertNoError(grp.Next(), t, "Next 1") + assertNoError(grp.Next(), t, "Next 2") + assertNoError(grp.Next(), t, "Next 3") frameoff0 := getFrameOff(p, t) - assertNoError(p.Step(), t, "Step") + assertNoError(grp.Step(), t, "Step") frameoff1 := getFrameOff(p, t) if frameoff0 == frameoff1 { t.Fatalf("did not step into function?") } assertLineNumber(p, t, 6, "program did not continue to expected location,") - assertNoError(p.Next(), t, "Next 4") + assertNoError(grp.Next(), t, "Next 4") assertLineNumber(p, t, 7, "program did not continue to expected location,") - assertNoError(p.StepOut(), t, "StepOut") + assertNoError(grp.StepOut(), t, "StepOut") assertLineNumber(p, t, 11, "program did not continue to expected location,") frameoff2 := getFrameOff(p, t) if frameoff0 != frameoff2 { @@ -3014,8 +3011,8 @@ func TestIssue877(t *testing.T) { } const envval = "/usr/local/lib" os.Setenv("DYLD_LIBRARY_PATH", envval) - withTestProcess("issue877", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue877", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "dyldenv") vv := constant.StringVal(v.Value) t.Logf("v = %q", vv) @@ -3030,8 +3027,8 @@ func TestIssue893(t *testing.T) { // executable, acceptable behaviors are: (a) no error, (b) no source at PC // error, (c) program runs to completion protest.AllowRecording(t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Next() + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Next() if err == nil { return } @@ -3050,17 +3047,17 @@ func TestIssue893(t *testing.T) { func TestStepInstructionNoGoroutine(t *testing.T) { protest.AllowRecording(t) - withTestProcess("increment", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("increment", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { // Call StepInstruction immediately after launching the program, it should // work even though no goroutine is selected. - assertNoError(p.StepInstruction(), t, "StepInstruction") + assertNoError(grp.StepInstruction(), t, "StepInstruction") }) } func TestIssue871(t *testing.T) { protest.AllowRecording(t) - withTestProcess("issue871", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue871", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") var scope *proc.EvalScope var err error @@ -3107,8 +3104,8 @@ func TestShadowedFlag(t *testing.T) { if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { return } - withTestProcess("testshadow", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("testshadow", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") locals, err := scope.LocalVariables(normalLoadConfig) @@ -3181,7 +3178,7 @@ func TestAttachStripped(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -3211,7 +3208,7 @@ func TestAttachStripped(t *testing.T) { func TestIssue844(t *testing.T) { // Conditional breakpoints should not prevent next from working if their // condition isn't met. - withTestProcess("nextcond", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nextcond", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) condbp := setFileBreakpoint(p, t, fixture.Source, 10) condbp.UserBreaklet().Cond = &ast.BinaryExpr{ @@ -3219,8 +3216,8 @@ func TestIssue844(t *testing.T) { X: &ast.Ident{Name: "n"}, Y: &ast.BasicLit{Kind: token.INT, Value: "11"}, } - assertNoError(p.Continue(), t, "Continue") - assertNoError(p.Next(), t, "Next") + assertNoError(grp.Continue(), t, "Continue") + assertNoError(grp.Next(), t, "Next") assertLineNumber(p, t, 10, "continued to wrong location,") }) } @@ -3362,11 +3359,11 @@ func TestCgoStacktrace(t *testing.T) { frameOffs := map[string]int64{} framePointerOffs := map[string]int64{} - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for itidx, tc := range testCases { t.Logf("iteration step %d", itidx) - assertNoError(p.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) + assertNoError(grp.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, fmt.Sprintf("GetG at iteration step %d", itidx)) @@ -3446,7 +3443,7 @@ func TestCgoSources(t *testing.T) { protest.MustHaveCgo(t) - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { sources := p.BinInfo().Sources for _, needle := range []string{"main.go", "hello.c"} { found := false @@ -3465,10 +3462,10 @@ func TestCgoSources(t *testing.T) { func TestSystemstackStacktrace(t *testing.T) { // check that we can follow a stack switch initiated by runtime.systemstack() - withTestProcess("panic", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("panic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "runtime.startpanic_m") - assertNoError(p.Continue(), t, "first continue") - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "second continue") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") frames, err := g.Stacktrace(100, 0) @@ -3487,9 +3484,9 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { // - try to look at stacktraces of other goroutines // If one of the other goroutines is resizing its own stack the stack // command won't work for it. - withTestProcess("binarytrees", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("binarytrees", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "first continue") g, err := proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") @@ -3497,7 +3494,7 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) { setFunctionBreakpoint(p, t, "runtime.newstack") for { - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "second continue") g, err = proc.GetG(p.CurrentThread()) assertNoError(err, t, "GetG") if g.ID == mainGoroutineID { @@ -3520,9 +3517,9 @@ func TestIssue1034(t *testing.T) { // The external linker on macOS produces an abbrev for DW_TAG_subprogram // without the "has children" flag, we should support this. - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") frames, err := p.SelectedGoroutine().Stacktrace(10, 0) assertNoError(err, t, "Stacktrace") scope := proc.FrameToScope(p, p.Memory(), nil, frames[2:]...) @@ -3540,9 +3537,9 @@ func TestIssue1008(t *testing.T) { // The external linker on macOS inserts "end of sequence" extended opcodes // in debug_line. which we should support correctly. - withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("cgostacktest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") loc, err := p.CurrentThread().Location() assertNoError(err, t, "CurrentThread().Location()") t.Logf("location %v\n", loc) @@ -3585,41 +3582,41 @@ func TestDeclLine(t *testing.T) { t.Skip("go 1.9 and prior versions do not emit DW_AT_decl_line") } - withTestProcess("decllinetest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("decllinetest", t, func(p *proc.Target, grp *proc.TargetGroup, 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 1") + assertNoError(grp.Continue(), t, "Continue 1") if goversion.VersionAfterOrEqual(runtime.Version(), 1, 15) { testDeclLineCount(t, p, 8, []string{}) } else { testDeclLineCount(t, p, 8, []string{"a"}) } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") testDeclLineCount(t, p, 9, []string{"a"}) - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") 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 4") + assertNoError(grp.Continue(), t, "Continue 4") testDeclLineCount(t, p, 11, []string{"a", "b"}) - assertNoError(p.Continue(), t, "Continue 5") + assertNoError(grp.Continue(), t, "Continue 5") testDeclLineCount(t, p, 14, []string{"a", "b"}) }) } func TestIssue1137(t *testing.T) { - withTestProcess("dotpackagesiface", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("dotpackagesiface", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") v := evalVariable(p, t, "iface") assertNoError(v.Unreadable, t, "iface unreadable") v2 := evalVariable(p, t, "iface2") @@ -3644,16 +3641,16 @@ func TestIssue1101(t *testing.T) { // close proximity to main.main calling os.Exit() and causing the death of // the thread group leader. - withTestProcess("issue1101", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1101", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.f") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next() 1") - assertNoError(p.Next(), t, "Next() 2") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next() 1") + assertNoError(grp.Next(), t, "Next() 2") lastCmd := "Next() 3" - exitErr := p.Next() + exitErr := grp.Next() if exitErr == nil { lastCmd = "final Continue()" - exitErr = p.Continue() + exitErr = grp.Continue() } if pexit, exited := exitErr.(proc.ErrProcessExited); exited { if pexit.Status != 2 && testBackend != "lldb" && (runtime.GOOS != "linux" || runtime.GOARCH != "386") { @@ -3673,8 +3670,7 @@ func TestIssue1101(t *testing.T) { } func TestIssue1145(t *testing.T) { - withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 18) assertNoError(grp.Continue(), t, "Continue()") resumeChan := make(chan struct{}, 1) @@ -3693,8 +3689,7 @@ func TestIssue1145(t *testing.T) { } func TestHaltKeepsSteppingBreakpoints(t *testing.T) { - withTestProcess("sleep", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("sleep", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { grp.KeepSteppingBreakpoints = proc.HaltKeepsSteppingBreakpoints setFileBreakpoint(p, t, fixture.Source, 18) assertNoError(grp.Continue(), t, "Continue()") @@ -3723,7 +3718,7 @@ func TestDisassembleGlobalVars(t *testing.T) { if runtime.GOARCH == "386" && runtime.GOOS == "linux" && buildMode == "pie" { t.Skip("On 386 linux when pie, symLookup can't look up global variables") } - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { mainfn := p.BinInfo().LookupFunc["main.main"] regs, _ := p.CurrentThread().Registers() text, err := proc.Disassemble(p.Memory(), regs, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) @@ -3764,7 +3759,7 @@ func TestAllPCsForFileLines(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { l2pcs := p.BinInfo().AllPCsForFileLines(fixture.Source, []int{7, 20}) if len(l2pcs) != 2 { t.Fatalf("expected two map entries for %s:{%d,%d} (got %d: %v)", fixture.Source, 7, 20, len(l2pcs), l2pcs) @@ -3828,7 +3823,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { }, } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pcs, err := proc.FindFileLocation(p, fixture.Source, 7) assertNoError(err, t, "LineToPC") if len(pcs) < 2 { @@ -3841,7 +3836,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { } // first inlined call - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 20) assertNoError(err, t, "ThreadStacktrace") t.Logf("Stacktrace:\n") @@ -3868,7 +3863,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { } // second inlined call - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") frames, err = proc.ThreadStacktrace(p.CurrentThread(), 20) assertNoError(err, t, "ThreadStacktrace (2)") t.Logf("Stacktrace 2:\n") @@ -3965,7 +3960,7 @@ func TestInlineFunctionList(t *testing.T) { // TODO(qmuntal): seems to be an upstream issue, investigate. t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { var found bool for _, fn := range p.BinInfo().Functions { if strings.Contains(fn.Name, "inlineThis") { @@ -3985,7 +3980,7 @@ func TestInlineBreakpoint(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pcs, err := proc.FindFileLocation(p, fixture.Source, 17) if err != nil { t.Fatal(err) @@ -4013,7 +4008,7 @@ func TestDoubleInlineBreakpoint(t *testing.T) { // Versions of go before 1.10 do not have DWARF information for inlined calls t.Skip("inlining not supported") } - withTestProcessArgs("doubleinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("doubleinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fns, err := p.BinInfo().FindFunction("main.(*Rectangle).Height") if err != nil { t.Fatal(err) @@ -4032,8 +4027,8 @@ func TestIssue951(t *testing.T) { t.Skip("scopes not implemented in <=go1.8") } - withTestProcess("issue951", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue951", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") args, err := scope.FunctionArguments(normalLoadConfig) @@ -4065,9 +4060,9 @@ func TestDWZCompression(t *testing.T) { t.Skip("dwz not installed") } - withTestProcessArgs("dwzcompression", t, ".", []string{}, protest.EnableDWZCompression, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("dwzcompression", t, ".", []string{}, protest.EnableDWZCompression, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "C.fortytwo") - assertNoError(p.Continue(), t, "first Continue()") + assertNoError(grp.Continue(), t, "first Continue()") val := evalVariable(p, t, "stdin") if val.RealType == nil { t.Errorf("Can't find type for \"stdin\" global variable") @@ -4077,9 +4072,9 @@ func TestDWZCompression(t *testing.T) { func TestMapLoadConfigWithReslice(t *testing.T) { // Check that load configuration is respected for resliced maps. - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { zolotovLoadCfg := proc.LoadConfig{FollowPointers: true, MaxStructFields: -1, MaxVariableRecurse: 3, MaxStringLen: 10, MaxArrayValues: 10} - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") m1, err := scope.EvalExpression("m1", zolotovLoadCfg) @@ -4105,10 +4100,10 @@ func TestStepOutReturn(t *testing.T) { if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 10, Rev: -1}) { t.Skip("return variables aren't marked on 1.9 or earlier") } - withTestProcess("stepoutret", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stepoutret", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stepout") - assertNoError(p.Continue(), t, "Continue") - assertNoError(p.StepOut(), t, "StepOut") + assertNoError(grp.Continue(), t, "Continue") + assertNoError(grp.StepOut(), t, "StepOut") ret := p.CurrentThread().Common().ReturnValues(normalLoadConfig) if len(ret) != 2 { t.Fatalf("wrong number of return values %v", ret) @@ -4153,7 +4148,7 @@ func TestStepOutReturn(t *testing.T) { } func TestOptimizationCheck(t *testing.T) { - withTestProcess("continuetestprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("continuetestprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fn := p.BinInfo().LookupFunc["main.main"] if fn.Optimized() { t.Fatalf("main.main is optimized") @@ -4161,7 +4156,7 @@ func TestOptimizationCheck(t *testing.T) { }) if goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { - withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("continuetestprog", t, ".", []string{}, protest.EnableOptimization|protest.EnableInlining, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { fn := p.BinInfo().LookupFunc["main.main"] if !fn.Optimized() { t.Fatalf("main.main is not optimized") @@ -4173,17 +4168,17 @@ func TestOptimizationCheck(t *testing.T) { func TestIssue1264(t *testing.T) { // It should be possible to set a breakpoint condition that consists only // of evaluating a single boolean variable. - withTestProcess("issue1264", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1264", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 8) bp.UserBreaklet().Cond = &ast.Ident{Name: "equalsTwo"} - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 8, "after continue") }) } func TestReadDefer(t *testing.T) { - withTestProcess("deferstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") frames, err := p.SelectedGoroutine().Stacktrace(10, proc.StacktraceReadDefers) assertNoError(err, t, "Stacktrace") @@ -4245,10 +4240,10 @@ func TestNextUnknownInstr(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 10) { t.Skip("versions of Go before 1.10 can't assemble the instruction VPUNPCKLWD") } - withTestProcess("nodisasm/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nodisasm/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.asmFunc") - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Next(), t, "Next()") }) } @@ -4274,8 +4269,8 @@ func TestReadDeferArgs(t *testing.T) { {2, 2, 1, -1}, } - withTestProcess("deferstack", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("deferstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, test := range tests { scope, err := proc.ConvertEvalScope(p, -1, test.frame, test.deferCall) @@ -4315,8 +4310,7 @@ func TestReadDeferArgs(t *testing.T) { func TestIssue1374(t *testing.T) { // Continue did not work when stopped at a breakpoint immediately after calling CallFunction. protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("issue1374", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue1374", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 7) assertNoError(grp.Continue(), t, "First Continue") assertLineNumber(p, t, 7, "Did not continue to correct location (first continue),") @@ -4335,8 +4329,8 @@ func TestIssue1432(t *testing.T) { // the struct's type and then accessing a member field will still: // - perform auto-dereferencing on struct member access // - yield a Variable that's ultimately assignable (i.e. has an address) - withTestProcess("issue1432", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue1432", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") svar := evalVariable(p, t, "s") t.Logf("%#x", svar.Addr) @@ -4349,9 +4343,9 @@ func TestIssue1432(t *testing.T) { } func TestGoroutinesInfoLimit(t *testing.T) { - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 37) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gcount := 0 nextg := 0 @@ -4378,9 +4372,9 @@ func TestGoroutinesInfoLimit(t *testing.T) { } func TestIssue1469(t *testing.T) { - withTestProcess("issue1469", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1469", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 13) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gid2thread := make(map[int64][]proc.Thread) for _, thread := range p.ThreadList() { @@ -4415,8 +4409,8 @@ func TestDeadlockBreakpoint(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { deadlockBp = proc.UnrecoveredPanic } - withTestProcess("testdeadlock", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testdeadlock", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp.Breakpoint == nil || bp.Logical.Name != deadlockBp { @@ -4437,12 +4431,12 @@ func findSource(source string, sources []string) bool { func TestListImages(t *testing.T) { pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/") - withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("plugintest", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { if !findSource(fixture.Source, p.BinInfo().Sources) { t.Fatalf("could not find %s in sources: %q\n", fixture.Source, p.BinInfo().Sources) } - assertNoError(p.Continue(), t, "first continue") + assertNoError(grp.Continue(), t, "first continue") f, l := currentLineNumber(p, t) plugin1Found := false t.Logf("Libraries before %s:%d:", f, l) @@ -4459,7 +4453,7 @@ func TestListImages(t *testing.T) { // Source files for the base program must be available even after a plugin is loaded. Issue #2074. t.Fatalf("could not find %s in sources (after loading plugin): %q\n", fixture.Source, p.BinInfo().Sources) } - assertNoError(p.Continue(), t, "second continue") + assertNoError(grp.Continue(), t, "second continue") f, l = currentLineNumber(p, t) plugin1Found, plugin2Found := false, false t.Logf("Libraries after %s:%d:", f, l) @@ -4488,9 +4482,9 @@ func TestAncestors(t *testing.T) { savedGodebug := os.Getenv("GODEBUG") os.Setenv("GODEBUG", "tracebackancestors=100") defer os.Setenv("GODEBUG", savedGodebug) - withTestProcess("testnextprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testnextprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.testgoroutine") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") as, err := proc.Ancestors(p, p.SelectedGoroutine(), 1000) assertNoError(err, t, "Ancestors") t.Logf("ancestors: %#v\n", as) @@ -4546,8 +4540,7 @@ func testCallConcurrentCheckReturns(p *proc.Target, t *testing.T, gid1, gid2 int func TestCallConcurrent(t *testing.T) { protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("teststepconcurrent", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 24) assertNoError(grp.Continue(), t, "Continue()") //_, err := p.ClearBreakpoint(bp.Addr) @@ -4605,8 +4598,8 @@ func TestPluginStepping(t *testing.T) { func TestIssue1601(t *testing.T) { protest.MustHaveCgo(t) // Tests that recursive types involving C qualifiers and typedefs are parsed correctly - withTestProcess("issue1601", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("issue1601", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") evalVariable(p, t, "C.globalq") }) } @@ -4614,7 +4607,7 @@ func TestIssue1601(t *testing.T) { func TestIssue1615(t *testing.T) { // A breakpoint condition that tests for string equality with a constant string shouldn't fail with 'string too long for comparison' error - withTestProcess("issue1615", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1615", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, t, fixture.Source, 19) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, @@ -4622,7 +4615,7 @@ func TestIssue1615(t *testing.T) { Y: &ast.BasicLit{Kind: token.STRING, Value: `"projects/my-gcp-project-id-string/locations/us-central1/queues/my-task-queue-name"`}, } - assertNoError(p.Continue(), t, "Continue") + assertNoError(grp.Continue(), t, "Continue") assertLineNumber(p, t, 19, "") }) } @@ -4634,8 +4627,8 @@ func TestCgoStacktrace2(t *testing.T) { protest.MustHaveCgo(t) // If a panic happens during cgo execution the stacktrace should show the C // function that caused the problem. - withTestProcess("cgosigsegvstack", t, func(p *proc.Target, fixture protest.Fixture) { - p.Continue() + withTestProcess("cgosigsegvstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.Continue() frames, err := proc.ThreadStacktrace(p.CurrentThread(), 100) assertNoError(err, t, "Stacktrace()") logStacktrace(t, p, frames) @@ -4648,14 +4641,14 @@ func TestCgoStacktrace2(t *testing.T) { func TestIssue1656(t *testing.T) { skipUnlessOn(t, "amd64 only", "amd64") - withTestProcess("issue1656/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1656/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.s")), 5) - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("step1\n") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 8, "wrong line number after first step") t.Logf("step2\n") - assertNoError(p.Step(), t, "Step()") + assertNoError(grp.Step(), t, "Step()") assertLineNumber(p, t, 9, "wrong line number after second step") }) } @@ -4666,15 +4659,15 @@ func TestBreakpointConfusionOnResume(t *testing.T) { // stopped at. // This test checks for a regression introduced when fixing Issue #1656 skipUnlessOn(t, "amd64 only", "amd64") - withTestProcess("nopbreakpoint/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("nopbreakpoint/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { maindots := filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.s")) maindotgo := filepath.ToSlash(filepath.Join(fixture.BuildDir, "main.go")) setFileBreakpoint(p, t, maindots, 5) // line immediately after the NOP - assertNoError(p.Continue(), t, "First Continue") + assertNoError(grp.Continue(), t, "First Continue") assertLineNumber(p, t, 5, "not on main.s:5") setFileBreakpoint(p, t, maindots, 4) // sets a breakpoint on the NOP line, which will be one byte before the breakpoint we currently are stopped at. setFileBreakpoint(p, t, maindotgo, 18) // set one extra breakpoint so that we can recover execution and check the global variable g - assertNoError(p.Continue(), t, "Second Continue") + assertNoError(grp.Continue(), t, "Second Continue") gvar := evalVariable(p, t, "g") if n, _ := constant.Int64Val(gvar.Value); n != 1 { t.Fatalf("wrong value of global variable 'g': %v (expected 1)", gvar.Value) @@ -4683,8 +4676,8 @@ func TestBreakpointConfusionOnResume(t *testing.T) { } func TestIssue1736(t *testing.T) { - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") ch1BufVar := evalVariable(p, t, "*(ch1.buf)") q := fmt.Sprintf("*(*%q)(%d)", ch1BufVar.DwarfType.Common().Name, ch1BufVar.Addr) t.Logf("%s", q) @@ -4698,13 +4691,13 @@ func TestIssue1736(t *testing.T) { func TestIssue1817(t *testing.T) { // Setting a breakpoint on a line that doesn't have any PC addresses marked // is_stmt should work. - withTestProcess("issue1817", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1817", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 16) }) } func TestListPackagesBuildInfo(t *testing.T) { - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { pkgs := p.BinInfo().ListPackagesBuildInfo(true) t.Logf("returned %d", len(pkgs)) if len(pkgs) < 10 { @@ -4736,17 +4729,17 @@ func TestIssue1795(t *testing.T) { if !goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) { t.Skip("Test not relevant to Go < 1.13") } - withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 12, "wrong line number after Continue,") - assertNoError(p.Next(), t, "Next()") + assertNoError(grp.Next(), t, "Next()") assertLineNumber(p, t, 13, "wrong line number after Next,") }) - withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("issue1795", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "regexp.(*Regexp).doExecute") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 12, "wrong line number after Continue (1),") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 40) assertNoError(err, t, "ThreadStacktrace()") logStacktrace(t, p, frames) @@ -4767,14 +4760,14 @@ func TestIssue1795(t *testing.T) { func BenchmarkConditionalBreakpoints(b *testing.B) { b.N = 1 - withTestProcess("issue1549", b, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue1549", b, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFileBreakpoint(p, b, fixture.Source, 12) bp.UserBreaklet().Cond = &ast.BinaryExpr{ Op: token.EQL, X: &ast.Ident{Name: "value"}, Y: &ast.BasicLit{Kind: token.INT, Value: "-1"}, } - err := p.Continue() + err := grp.Continue() if _, exited := err.(proc.ErrProcessExited); !exited { b.Fatalf("Unexpected error on Continue(): %v", err) } @@ -4934,8 +4927,7 @@ func TestIssue1925(t *testing.T) { // 'call' procedure should clean the G cache like every other function // altering the state of the target process. protest.MustSupportFunctionCalls(t, testBackend) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { assertNoError(grp.Continue(), t, "Continue()") assertNoError(proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), "afunc(2)", normalLoadConfig, true), t, "Call") t.Logf("%v\n", p.SelectedGoroutine().CurrentLoc) @@ -4998,10 +4990,10 @@ func TestRefreshCurThreadSelGAfterContinueOnceError(t *testing.T) { skipUnlessOn(t, "N/A", "darwin", "lldb") - withTestProcess("issue2078", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue2078", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 4) - assertNoError(p.Continue(), t, "Continue() (first)") - if p.Continue() == nil { + assertNoError(grp.Continue(), t, "Continue() (first)") + if grp.Continue() == nil { t.Fatalf("Second continue did not return an error") } g := p.SelectedGoroutine() @@ -5014,14 +5006,14 @@ func TestRefreshCurThreadSelGAfterContinueOnceError(t *testing.T) { func TestStepoutOneliner(t *testing.T) { // The heuristic detecting autogenerated wrappers when stepping out should // not skip oneliner functions. - withTestProcess("issue2086", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue2086", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") assertLineNumber(p, t, 15, "after first continue") - assertNoError(p.StepOut(), t, "StepOut()") + assertNoError(grp.StepOut(), t, "StepOut()") if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.T.m" { t.Fatalf("wrong function after stepout %#v", fn) } - assertNoError(p.StepOut(), t, "second StepOut()") + assertNoError(grp.StepOut(), t, "second StepOut()") if fn := p.BinInfo().PCToFunc(currentPC(p, t)); fn == nil || fn.Name != "main.main" { t.Fatalf("wrong fnuction after second stepout %#v", fn) } @@ -5030,8 +5022,7 @@ func TestStepoutOneliner(t *testing.T) { func TestRequestManualStopWhileStopped(t *testing.T) { // Requesting a manual stop while stopped shouldn't cause problems (issue #2138). - withTestProcess("issue2138", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue2138", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { resumed := make(chan struct{}) setFileBreakpoint(p, t, fixture.Source, 8) assertNoError(grp.Continue(), t, "Continue() 1") @@ -5064,8 +5055,8 @@ func TestRequestManualStopWhileStopped(t *testing.T) { func TestStepOutPreservesGoroutine(t *testing.T) { // Checks that StepOut preserves the currently selected goroutine. rand.Seed(time.Now().Unix()) - withTestProcess("issue2113", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue2113", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") logState := func() { g := p.SelectedGoroutine() @@ -5115,7 +5106,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) { logState() - err = p.StepOut() + err = grp.StepOut() if err != nil { _, isexited := err.(proc.ErrProcessExited) if !isexited { @@ -5223,7 +5214,7 @@ func TestDump(t *testing.T) { } c, err := core.OpenCore(corePath, exePath, nil) assertNoError(err, t, "OpenCore()") - return c + return c.Selected } testDump := func(p, c *proc.Target) { @@ -5302,8 +5293,8 @@ func TestDump(t *testing.T) { } } - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") corePath := filepath.Join(fixture.BuildDir, "coredump") corePathPlatIndep := filepath.Join(fixture.BuildDir, "coredump-indep") @@ -5329,7 +5320,7 @@ func TestCompositeMemoryWrite(t *testing.T) { t.Skip("only valid on amd64") } skipOn(t, "not implemented", "freebsd") - withTestProcess("fputest/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("fputest/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { getregs := func() (pc, rax, xmm1 uint64) { regs, err := p.CurrentThread().Registers() assertNoError(err, t, "Registers") @@ -5361,7 +5352,7 @@ func TestCompositeMemoryWrite(t *testing.T) { return binary.LittleEndian.Uint64(buf) } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") oldPc, oldRax, oldXmm1 := getregs() t.Logf("PC %#x AX %#x XMM1 %#x", oldPc, oldRax, oldXmm1) @@ -5401,8 +5392,8 @@ func TestVariablesWithExternalLinking(t *testing.T) { // See: // https://github.com/golang/go/issues/25841 // https://github.com/go-delve/delve/issues/2346 - withTestProcessArgs("testvariables2", t, ".", []string{}, protest.BuildModeExternalLinker, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcessArgs("testvariables2", t, ".", []string{}, protest.BuildModeExternalLinker, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") str1Var := evalVariable(p, t, "str1") if str1Var.Unreadable != nil { t.Fatalf("variable str1 is unreadable: %v", str1Var.Unreadable) @@ -5428,11 +5419,11 @@ func TestWatchpointsBasic(t *testing.T) { position5 = 40 } - withTestProcess("databpeasy", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpeasy", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") setFileBreakpoint(p, t, fixture.Source, 21) // Position 2 breakpoint setFileBreakpoint(p, t, fixture.Source, 27) // Position 4 breakpoint - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 13, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5441,7 +5432,7 @@ func TestWatchpointsBasic(t *testing.T) { bp, err := p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only)") - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, position1, "Continue 1") // Position 1 if curbp := p.CurrentThread().Breakpoint().Breakpoint; curbp == nil || (curbp.LogicalID() != bp.LogicalID()) { @@ -5450,25 +5441,25 @@ func TestWatchpointsBasic(t *testing.T) { assertNoError(p.ClearBreakpoint(bp.Addr), t, "ClearBreakpoint") - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") assertLineNumber(p, t, 21, "Continue 2") // Position 2 _, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite|proc.WatchRead, nil) assertNoError(err, t, "SetDataBreakpoint(read-write)") - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") assertLineNumber(p, t, 22, "Continue 3") // Position 3 p.ClearBreakpoint(bp.Addr) - assertNoError(p.Continue(), t, "Continue 4") + assertNoError(grp.Continue(), t, "Continue 4") assertLineNumber(p, t, 27, "Continue 4") // Position 4 t.Logf("setting final breakpoint") _, err = p.SetWatchpoint(0, scope, "globalvar1", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only, again)") - assertNoError(p.Continue(), t, "Continue 5") + assertNoError(grp.Continue(), t, "Continue 5") assertLineNumber(p, t, position5, "Continue 5") // Position 5 }) } @@ -5479,9 +5470,9 @@ func TestWatchpointCounts(t *testing.T) { skipOn(t, "see https://github.com/go-delve/delve/issues/2768", "windows") protest.AllowRecording(t) - withTestProcess("databpcountstest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpcountstest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") @@ -5490,7 +5481,7 @@ func TestWatchpointCounts(t *testing.T) { assertNoError(err, t, "SetWatchpoint(write-only)") for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } @@ -5517,8 +5508,7 @@ func TestWatchpointCounts(t *testing.T) { func TestManualStopWhileStopped(t *testing.T) { // Checks that RequestManualStop sent to a stopped thread does not cause the target process to die. - withTestProcess("loopprog", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("loopprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { asyncCont := func(done chan struct{}) { defer close(done) err := grp.Continue() @@ -5566,9 +5556,9 @@ func TestManualStopWhileStopped(t *testing.T) { func TestDwrapStartLocation(t *testing.T) { // Tests that the start location of a goroutine is unwrapped in Go 1.17 and later. - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.stacktraceme") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") found := false @@ -5601,11 +5591,11 @@ func TestWatchpointStack(t *testing.T) { position1 = 16 } - withTestProcess("databpstack", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint clearlen := len(p.Breakpoints().M) - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 11, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5615,7 +5605,7 @@ func TestWatchpointStack(t *testing.T) { assertNoError(err, t, "SetDataBreakpoint(write-only)") watchbpnum := 3 - if recorded, _ := p.Recorded(); recorded { + if recorded, _ := grp.Recorded(); recorded { watchbpnum = 4 } @@ -5647,10 +5637,10 @@ func TestWatchpointStack(t *testing.T) { t.Errorf("wrong number of breakpoints after setting watchpoint: %d", len(p.Breakpoints().M)-clearlen) } - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, position1, "Continue 1") // Position 1 - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 24, "Continue 2") // Position 2 (watchpoint gone out of scope) @@ -5677,11 +5667,11 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { skipUnlessOn(t, "only for recorded targets", "rr") protest.AllowRecording(t) - withTestProcess("databpstack", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("databpstack", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 11) // Position 0 breakpoint clearlen := len(p.Breakpoints().M) - assertNoError(p.Continue(), t, "Continue 0") + assertNoError(grp.Continue(), t, "Continue 0") assertLineNumber(p, t, 11, "Continue 0") // Position 0 scope, err := proc.GoroutineScope(p, p.CurrentThread()) @@ -5690,20 +5680,20 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { _, err = p.SetWatchpoint(0, scope, "w", proc.WatchWrite, nil) assertNoError(err, t, "SetDataBreakpoint(write-only)") - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") assertLineNumber(p, t, 17, "Continue 1") // Position 1 - p.ChangeDirection(proc.Backward) + grp.ChangeDirection(proc.Backward) - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 16, "Continue 2") // Position 1 again (because of inverted movement) - assertNoError(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 11, "Continue 3") // Position 0 (breakpoint 1 hit) - assertNoError(p.Continue(), t, "Continue 4") + assertNoError(grp.Continue(), t, "Continue 4") t.Logf("%#v", p.CurrentThread().Breakpoint().Breakpoint) assertLineNumber(p, t, 23, "Continue 4") // Position 2 (watchpoint gone out of scope) @@ -5725,9 +5715,9 @@ func TestWatchpointStackBackwardsOutOfScope(t *testing.T) { func TestSetOnFunctions(t *testing.T) { // The set command between function variables should fail with an error // Issue #2691 - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, "GoroutineScope") err = scope.SetVariable("main.func1", "main.func2") @@ -5742,9 +5732,9 @@ func TestSetYMMRegister(t *testing.T) { // Checks that setting a XMM register works. This checks that the // workaround for a bug in debugserver works. // See issue #2767. - withTestProcess("setymmreg/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("setymmreg/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.asmFunc") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") getReg := func(pos string) *op.DwarfRegister { regs := getRegisters(p, t) @@ -5792,14 +5782,14 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { t.Fatalf("assembly file for %s not provided", runtime.GOARCH) } - withTestProcess("asmnilptr/", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("asmnilptr/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { f := filepath.Join(fixture.BuildDir, asmfile) f = strings.ReplaceAll(f, "\\", "/") setFileBreakpoint(p, t, f, 5) t.Logf("first continue") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") t.Logf("second continue") - err := p.Continue() + err := grp.Continue() if runtime.GOOS == "darwin" && err != nil && err.Error() == "bad access" { // this is also ok return @@ -5819,10 +5809,10 @@ func TestNilPtrDerefInBreakInstr(t *testing.T) { func TestStepIntoAutogeneratedSkip(t *testing.T) { // Tests that autogenerated functions are skipped with the new naming // scheme for autogenerated functions (issue #2948). - withTestProcess("stepintobug", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("stepintobug", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 9) - assertNoError(p.Continue(), t, "Continue()") - assertNoError(p.Step(), t, "Step") + assertNoError(grp.Continue(), t, "Continue()") + assertNoError(grp.Step(), t, "Step") assertLineNumber(p, t, 12, "After step") }) } @@ -5836,11 +5826,9 @@ func TestCallInjectionFlagCorruption(t *testing.T) { skipUnlessOn(t, "not relevant", "amd64") protest.MustSupportFunctionCalls(t, testBackend) - withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("badflags", t, ".", []string{"0"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { mainfn := p.BinInfo().LookupFunc["main.main"] - grp := proc.NewGroup(p) - // Find JNZ instruction on line :14 var addr uint64 text, err := proc.Disassemble(p.Memory(), nil, p.Breakpoints(), p.BinInfo(), mainfn.Entry, mainfn.End) @@ -5935,9 +5923,9 @@ func TestStacktraceExtlinkMac(t *testing.T) { // Tests stacktrace for programs built using external linker. // See issue #3194 skipUnlessOn(t, "darwin only", "darwin") - withTestProcess("issue3194", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("issue3194", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") - assertNoError(p.Continue(), t, "First Continue()") + assertNoError(grp.Continue(), t, "First Continue()") frames, err := proc.ThreadStacktrace(p.CurrentThread(), 10) assertNoError(err, t, "ThreadStacktrace") logStacktrace(t, p, frames) @@ -5946,3 +5934,109 @@ func TestStacktraceExtlinkMac(t *testing.T) { } }) } + +func TestFollowExec(t *testing.T) { + skipUnlessOn(t, "follow exec only supported on linux", "linux") + withTestProcessArgs("spawn", t, ".", []string{"spawn", "3"}, 0, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + grp.LogicalBreakpoints[1] = &proc.LogicalBreakpoint{LogicalID: 1, Set: proc.SetBreakpoint{FunctionName: "main.traceme1"}, HitCount: make(map[int64]uint64)} + grp.LogicalBreakpoints[2] = &proc.LogicalBreakpoint{LogicalID: 2, Set: proc.SetBreakpoint{FunctionName: "main.traceme2"}, HitCount: make(map[int64]uint64)} + grp.LogicalBreakpoints[3] = &proc.LogicalBreakpoint{LogicalID: 3, Set: proc.SetBreakpoint{FunctionName: "main.traceme3"}, HitCount: make(map[int64]uint64)} + + assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[1]), t, "EnableBreakpoint(main.traceme1)") + assertNoError(grp.EnableBreakpoint(grp.LogicalBreakpoints[3]), t, "EnableBreakpoint(main.traceme3)") + + assertNoError(grp.FollowExec(true, ""), t, "FollowExec") + + first := true + finished := false + pids := map[int]int{} + ns := map[string]int{} + + for { + t.Log("Continuing") + err := grp.Continue() + if err != nil { + _, isexited := err.(proc.ErrProcessExited) + if isexited { + break + } + assertNoError(err, t, "Continue") + } + + if first { + first = false + if grp.Selected != p { + t.Fatalf("first breakpoint hit was not on the parent process") + } + if grp.Selected.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 1 { + t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint) + } + loc, err := grp.Selected.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme1" { + t.Fatalf("wrong stop location %#v", loc) + } + } else if grp.Selected == p { + if finished { + t.Fatalf("breakpoint hit after the last one in the parent process") + } + if p.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 3 { + t.Fatalf("wrong breakpoint %#v", p.CurrentThread().Breakpoint().Breakpoint) + } + loc, err := p.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme3" { + t.Fatalf("wrong stop location %#v", loc) + } + finished = true + } else { + if finished { + t.Fatalf("breakpoint hit after the last one in a child process") + } + it := proc.ValidTargets{Group: grp} + for it.Next() { + tgt := it.Target + if !tgt.CurrentThread().Breakpoint().Active { + continue + } + if tgt.CurrentThread().Breakpoint().Breakpoint.LogicalID() != 2 { + t.Fatalf("wrong breakpoint %#v", grp.Selected.CurrentThread().Breakpoint().Breakpoint) + } + pids[tgt.Pid()]++ + loc, err := tgt.CurrentThread().Location() + assertNoError(err, t, "Location") + if loc.Fn.Name != "main.traceme2" { + t.Fatalf("wrong stop location %#v", loc) + } + nvar := evalVariable(tgt, t, "n") + if nvar.Unreadable != nil { + t.Fatalf("unreadable variable 'n' on target %d: %v", tgt.Pid(), nvar.Unreadable) + } + t.Logf("variable 'n' on target %d: %#v (%v)", tgt.Pid(), nvar, nvar.Value) + ns[constant.StringVal(nvar.Value)]++ + } + } + } + + if len(ns) != 3 { + t.Errorf("bad contents of ns: %#v", ns) + } + for _, v := range ns { + if v != 1 { + t.Errorf("bad contents of ns: %#v", ns) + } + } + if ns["C0"] != 1 || ns["C1"] != 1 || ns["C2"] != 1 { + t.Errorf("bad contents of ns: %#v", ns) + } + + if len(pids) != 3 { + t.Errorf("bad contents of pids: %#v", pids) + } + for _, v := range pids { + if v != 1 { + t.Errorf("bad contents of pids: %#v", pids) + } + } + }) +} diff --git a/pkg/proc/proc_unix_test.go b/pkg/proc/proc_unix_test.go index fa9ac0ae00..0404995bec 100644 --- a/pkg/proc/proc_unix_test.go +++ b/pkg/proc/proc_unix_test.go @@ -36,8 +36,7 @@ func TestIssue419(t *testing.T) { errChan := make(chan error, 2) // SIGINT directed at the inferior should be passed along not swallowed by delve - withTestProcess("issue419", t, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) + withTestProcess("issue419", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "main.main") assertNoError(grp.Continue(), t, "Continue()") resumeChan := make(chan struct{}, 1) diff --git a/pkg/proc/scope_test.go b/pkg/proc/scope_test.go index 800316906d..8ca4665f05 100644 --- a/pkg/proc/scope_test.go +++ b/pkg/proc/scope_test.go @@ -23,8 +23,8 @@ func TestScopeWithEscapedVariable(t *testing.T) { return } - withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("scopeescapevareval", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") // On the breakpoint there are two 'a' variables in scope, the one that // isn't shadowed is a variable that escapes to the heap and figures in @@ -72,7 +72,7 @@ func TestScope(t *testing.T) { scopeChecks := getScopeChecks(scopetestPath, t) - withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("scopetest", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for i := range scopeChecks { setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line) } @@ -80,7 +80,7 @@ func TestScope(t *testing.T) { t.Logf("%d breakpoints set", len(scopeChecks)) for { - if err := p.Continue(); err != nil { + if err := grp.Continue(); err != nil { if _, exited := err.(proc.ErrProcessExited); exited { break } diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 5646f01338..90403449eb 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -38,9 +38,8 @@ const ( type Target struct { Process - proc ProcessInternal - recman RecordingManipulationInternal - continueOnce ContinueOnceFunc + proc ProcessInternal + recman RecordingManipulationInternal pid int @@ -49,9 +48,6 @@ type Target struct { // case only one will be reported. StopReason StopReason - // CanDump is true if core dumping is supported. - CanDump bool - // currentThread is the thread that will be used by next/step/stepout and to evaluate variables if no goroutine is selected. currentThread Thread @@ -148,18 +144,6 @@ const ( StopWatchpoint // The target process hit one or more watchpoints ) -type ContinueOnceFunc func([]ProcessInternal, *ContinueOnceContext) (trapthread Thread, stopReason StopReason, err error) - -// NewTargetConfig contains the configuration for a new Target object, -type NewTargetConfig struct { - Path string // path of the main executable - DebugInfoDirs []string // Directories to search for split debug info - DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled - StopReason StopReason // Initial stop reason - CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap) - ContinueOnce ContinueOnceFunc -} - // DisableAsyncPreemptEnv returns a process environment (like os.Environ) // where asyncpreemptoff is set to 1. func DisableAsyncPreemptEnv() []string { @@ -174,15 +158,15 @@ func DisableAsyncPreemptEnv() []string { return env } -// NewTarget returns an initialized Target object. +// newTarget returns an initialized Target object. // The p argument can optionally implement the RecordingManipulation interface. -func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetConfig) (*Target, error) { +func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thread, path string) (*Target, error) { entryPoint, err := p.EntryPoint() if err != nil { return nil, err } - err = p.BinInfo().LoadBinaryInfo(cfg.Path, entryPoint, cfg.DebugInfoDirs) + err = p.BinInfo().LoadBinaryInfo(path, entryPoint, grp.cfg.DebugInfoDirs) if err != nil { return nil, err } @@ -196,11 +180,8 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo Process: p, proc: p, fncallForG: make(map[int64]*callInjection), - StopReason: cfg.StopReason, currentThread: currentThread, - CanDump: cfg.CanDump, pid: pid, - continueOnce: cfg.ContinueOnce, } if recman, ok := p.(RecordingManipulationInternal); ok { @@ -219,7 +200,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo t.gcache.init(p.BinInfo()) t.fakeMemoryRegistryMap = make(map[string]*compositeMemory) - if cfg.DisableAsyncPreempt { + if grp.cfg.DisableAsyncPreempt { setAsyncPreemptOff(t, 1) } diff --git a/pkg/proc/target_exec.go b/pkg/proc/target_exec.go index 0e5b68fbb3..6f45d588dd 100644 --- a/pkg/proc/target_exec.go +++ b/pkg/proc/target_exec.go @@ -48,48 +48,55 @@ func (grp *TargetGroup) Next() (err error) { // processes. It will continue until it hits a breakpoint // or is otherwise stopped. func (grp *TargetGroup) Continue() error { - if len(grp.targets) != 1 { - panic("multiple targets not implemented") - } - dbp := grp.Selected - if _, err := dbp.Valid(); err != nil { + if grp.numValid() == 0 { + _, err := grp.targets[0].Valid() return err } - for _, thread := range dbp.ThreadList() { - thread.Common().CallReturn = false - thread.Common().returnValues = nil + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + for _, thread := range dbp.ThreadList() { + thread.Common().CallReturn = false + thread.Common().returnValues = nil + } + dbp.Breakpoints().WatchOutOfScope = nil + dbp.clearHardcodedBreakpoints() } - dbp.Breakpoints().WatchOutOfScope = nil - dbp.clearHardcodedBreakpoints() grp.cctx.CheckAndClearManualStopRequest() defer func() { // Make sure we clear internal breakpoints if we simultaneously receive a // manual stop request and hit a breakpoint. if grp.cctx.CheckAndClearManualStopRequest() { - dbp.StopReason = StopManual - dbp.clearHardcodedBreakpoints() - if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { - dbp.ClearSteppingBreakpoints() - } + grp.finishManualStop() } }() for { if grp.cctx.CheckAndClearManualStopRequest() { - dbp.StopReason = StopManual - dbp.clearHardcodedBreakpoints() - if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { - dbp.ClearSteppingBreakpoints() - } + grp.finishManualStop() return nil } - dbp.ClearCaches() - trapthread, stopReason, contOnceErr := grp.continueOnce([]ProcessInternal{grp.targets[0].proc}, grp.cctx) - dbp.StopReason = stopReason + for _, dbp := range grp.targets { + dbp.ClearCaches() + } + trapthread, stopReason, contOnceErr := grp.procgrp.ContinueOnce(grp.cctx) + var traptgt *Target + if trapthread != nil { + traptgt = grp.TargetForThread(trapthread.ThreadID()) + if traptgt == nil { + return fmt.Errorf("could not find target for thread %d", trapthread.ThreadID()) + } + } else { + traptgt = grp.targets[0] + } + traptgt.StopReason = stopReason - threads := dbp.ThreadList() - for _, thread := range threads { - if thread.Breakpoint().Breakpoint != nil { - thread.Breakpoint().Breakpoint.checkCondition(dbp, thread, thread.Breakpoint()) + it := ValidTargets{Group: grp} + for it.Next() { + for _, thread := range it.ThreadList() { + if thread.Breakpoint().Breakpoint != nil { + thread.Breakpoint().Breakpoint.checkCondition(it.Target, thread, thread.Breakpoint()) + } } } @@ -98,31 +105,56 @@ func (grp *TargetGroup) Continue() error { // Issue #2078. // Errors are ignored because depending on why ContinueOnce failed this // might very well not work. - if valid, _ := dbp.Valid(); valid { - if trapthread != nil { - _ = dbp.SwitchThread(trapthread.ThreadID()) - } else if curth := dbp.CurrentThread(); curth != nil { - dbp.selectedGoroutine, _ = GetG(curth) - } - } + _ = grp.setCurrentThreads(traptgt, trapthread) if pe, ok := contOnceErr.(ErrProcessExited); ok { - dbp.exitStatus = pe.Status + traptgt.exitStatus = pe.Status } return contOnceErr } - if dbp.StopReason == StopLaunched { - dbp.ClearSteppingBreakpoints() + if stopReason == StopLaunched { + it.Reset() + for it.Next() { + it.Target.ClearSteppingBreakpoints() + } } - callInjectionDone, callErr := callInjectionProtocol(dbp, threads) - hcbpErr := dbp.handleHardcodedBreakpoints(trapthread, threads) + var callInjectionDone bool + var callErr error + var hcbpErr error + it.Reset() + for it.Next() { + dbp := it.Target + threads := dbp.ThreadList() + callInjectionDoneThis, callErrThis := callInjectionProtocol(dbp, threads) + callInjectionDone = callInjectionDone || callInjectionDoneThis + if callInjectionDoneThis { + dbp.StopReason = StopCallReturned + } + if callErrThis != nil && callErr == nil { + callErr = callErrThis + } + hcbpErrThis := dbp.handleHardcodedBreakpoints(trapthread, threads) + if hcbpErrThis != nil && hcbpErr == nil { + hcbpErr = hcbpErrThis + } + } // callErr and hcbpErr check delayed until after pickCurrentThread, which // must always happen, otherwise the debugger could be left in an // inconsistent state. - if err := pickCurrentThread(dbp, trapthread, threads); err != nil { - return err + it = ValidTargets{Group: grp} + for it.Next() { + var th Thread = nil + if it.Target == traptgt { + th = trapthread + } + err := pickCurrentThread(it.Target, th) + if err != nil { + return err + } } + grp.pickCurrentTarget(traptgt) + dbp := grp.Selected if callErr != nil { return callErr @@ -138,7 +170,7 @@ func (grp *TargetGroup) Continue() error { case curbp.Active && curbp.Stepping: if curbp.SteppingInto { // See description of proc.(*Process).next for the meaning of StepBreakpoints - if err := conditionErrors(threads); err != nil { + if err := conditionErrors(grp); err != nil { return err } if grp.GetDirection() == Forward { @@ -168,7 +200,7 @@ func (grp *TargetGroup) Continue() error { return err } dbp.StopReason = StopNextFinished - return conditionErrors(threads) + return conditionErrors(grp) } case curbp.Active: onNextGoroutine, err := onNextGoroutine(dbp, curthread, dbp.Breakpoints()) @@ -191,17 +223,56 @@ func (grp *TargetGroup) Continue() error { if curbp.Breakpoint.WatchType != 0 { dbp.StopReason = StopWatchpoint } - return conditionErrors(threads) + return conditionErrors(grp) default: // not a manual stop, not on runtime.Breakpoint, not on a breakpoint, just repeat } if callInjectionDone { // a call injection was finished, don't let a breakpoint with a failed // condition or a step breakpoint shadow this. - dbp.StopReason = StopCallReturned - return conditionErrors(threads) + return conditionErrors(grp) + } + } +} + +func (grp *TargetGroup) finishManualStop() { + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + dbp.StopReason = StopManual + dbp.clearHardcodedBreakpoints() + if grp.KeepSteppingBreakpoints&HaltKeepsSteppingBreakpoints == 0 { + dbp.ClearSteppingBreakpoints() + } + } +} + +// setCurrentThreads switches traptgt to trapthread, then for each target in +// the group if its current thread exists it refreshes the current +// goroutine, otherwise it switches it to a randomly selected thread. +func (grp *TargetGroup) setCurrentThreads(traptgt *Target, trapthread Thread) error { + var err error + if traptgt != nil && trapthread != nil { + err = traptgt.SwitchThread(trapthread.ThreadID()) + } + for _, tgt := range grp.targets { + if isvalid, _ := tgt.Valid(); !isvalid { + continue + } + if _, ok := tgt.FindThread(tgt.currentThread.ThreadID()); ok { + tgt.selectedGoroutine, _ = GetG(tgt.currentThread) + } else { + threads := tgt.ThreadList() + if len(threads) > 0 { + err1 := tgt.SwitchThread(threads[0].ThreadID()) + if err1 != nil && err == nil { + err = err1 + } + } } } + return err } func isTraceOrTraceReturn(bp *Breakpoint) bool { @@ -211,14 +282,19 @@ func isTraceOrTraceReturn(bp *Breakpoint) bool { return bp.Logical.Tracepoint || bp.Logical.TraceReturn } -func conditionErrors(threads []Thread) error { +func conditionErrors(grp *TargetGroup) error { var condErr error - for _, th := range threads { - if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil { - if condErr == nil { - condErr = bp.CondError - } else { - return fmt.Errorf("multiple errors evaluating conditions") + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + for _, th := range dbp.ThreadList() { + if bp := th.Breakpoint(); bp.Breakpoint != nil && bp.CondError != nil { + if condErr == nil { + condErr = bp.CondError + } else { + return fmt.Errorf("multiple errors evaluating conditions") + } } } } @@ -226,24 +302,88 @@ func conditionErrors(threads []Thread) error { } // pick a new dbp.currentThread, with the following priority: +// // - a thread with an active stepping breakpoint // - a thread with an active breakpoint, prioritizing trapthread -// - trapthread -func pickCurrentThread(dbp *Target, trapthread Thread, threads []Thread) error { +// - trapthread if it is not nil +// - the previous current thread if it still exists +// - a randomly selected thread +func pickCurrentThread(dbp *Target, trapthread Thread) error { + threads := dbp.ThreadList() for _, th := range threads { if bp := th.Breakpoint(); bp.Active && bp.Stepping { return dbp.SwitchThread(th.ThreadID()) } } - if bp := trapthread.Breakpoint(); bp.Active { - return dbp.SwitchThread(trapthread.ThreadID()) + if trapthread != nil { + if bp := trapthread.Breakpoint(); bp.Active { + return dbp.SwitchThread(trapthread.ThreadID()) + } } for _, th := range threads { if bp := th.Breakpoint(); bp.Active { return dbp.SwitchThread(th.ThreadID()) } } - return dbp.SwitchThread(trapthread.ThreadID()) + if trapthread != nil { + return dbp.SwitchThread(trapthread.ThreadID()) + } + if _, ok := dbp.FindThread(dbp.currentThread.ThreadID()); ok { + dbp.selectedGoroutine, _ = GetG(dbp.currentThread) + return nil + } + if len(threads) > 0 { + return dbp.SwitchThread(threads[0].ThreadID()) + } + return nil +} + +// pickCurrentTarget picks a new current target, with the following property: +// +// - a target with an active stepping breakpoint +// - a target with StopReason == StopCallReturned +// - a target with an active breakpoint, prioritizing traptgt +// - traptgt +func (grp *TargetGroup) pickCurrentTarget(traptgt *Target) { + if len(grp.targets) == 1 { + grp.Selected = grp.targets[0] + return + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + bp := dbp.currentThread.Breakpoint() + if bp.Active && bp.Stepping { + grp.Selected = dbp + return + } + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + if dbp.StopReason == StopCallReturned { + grp.Selected = dbp + return + } + } + + if traptgt.currentThread.Breakpoint().Active { + grp.Selected = traptgt + return + } + for _, dbp := range grp.targets { + if isvalid, _ := dbp.Valid(); !isvalid { + continue + } + bp := dbp.currentThread.Breakpoint() + if bp.Active { + grp.Selected = dbp + return + } + } + grp.Selected = traptgt } func disassembleCurrentInstruction(p Process, thread Thread, off int64) ([]AsmInstruction, error) { diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go index a1f6acb219..5385b93da5 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -2,8 +2,11 @@ package proc import ( "bytes" + "errors" "fmt" "strings" + + "github.com/go-delve/delve/pkg/logflags" ) // TargetGroup represents a group of target processes being debugged that @@ -12,66 +15,114 @@ import ( // enabled and the backend supports it, otherwise the group will always // contain a single target process. type TargetGroup struct { - targets []*Target - Selected *Target + procgrp ProcessGroup + + targets []*Target + Selected *Target + followExecEnabled bool RecordingManipulation recman RecordingManipulationInternal + // StopReason describes the reason why the selected target process is stopped. + // A process could be stopped for multiple simultaneous reasons, in which + // case only one will be reported. + StopReason StopReason + // KeepSteppingBreakpoints determines whether certain stop reasons (e.g. manual halts) // will keep the stepping breakpoints instead of clearing them. KeepSteppingBreakpoints KeepSteppingBreakpoints LogicalBreakpoints map[int]*LogicalBreakpoint - continueOnce ContinueOnceFunc - cctx *ContinueOnceContext + cctx *ContinueOnceContext + cfg NewTargetGroupConfig + CanDump bool } +// NewTargetGroupConfig contains the configuration for a new TargetGroup object, +type NewTargetGroupConfig struct { + DebugInfoDirs []string // Directories to search for split debug info + DisableAsyncPreempt bool // Go 1.14 asynchronous preemption should be disabled + StopReason StopReason // Initial stop reason + CanDump bool // Can create core dumps (must implement ProcessInternal.MemoryMap) +} + +type AddTargetFunc func(ProcessInternal, int, Thread, string, StopReason) (*Target, error) + // NewGroup creates a TargetGroup containing the specified Target. -func NewGroup(t *Target) *TargetGroup { - if t.partOfGroup { - panic("internal error: target is already part of a group") - } - t.partOfGroup = true - if t.Breakpoints().Logical == nil { - t.Breakpoints().Logical = make(map[int]*LogicalBreakpoint) - } - return &TargetGroup{ - RecordingManipulation: t.recman, - targets: []*Target{t}, - Selected: t, - cctx: &ContinueOnceContext{}, - recman: t.recman, - LogicalBreakpoints: t.Breakpoints().Logical, - continueOnce: t.continueOnce, +func NewGroup(procgrp ProcessGroup, cfg NewTargetGroupConfig) (*TargetGroup, AddTargetFunc) { + grp := &TargetGroup{ + procgrp: procgrp, + cctx: &ContinueOnceContext{}, + LogicalBreakpoints: make(map[int]*LogicalBreakpoint), + StopReason: cfg.StopReason, + cfg: cfg, + CanDump: cfg.CanDump, } + return grp, grp.addTarget } -// NewGroupRestart creates a new group of targets containing t and -// sets breakpoints and other attributes from oldgrp. +// Restart copies breakpoints and follow exec status from oldgrp into grp. // Breakpoints that can not be set will be discarded, if discard is not nil // it will be called for each discarded breakpoint. -func NewGroupRestart(t *Target, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) *TargetGroup { - grp := NewGroup(t) - grp.LogicalBreakpoints = oldgrp.LogicalBreakpoints - t.Breakpoints().Logical = grp.LogicalBreakpoints - for _, bp := range grp.LogicalBreakpoints { - if bp.LogicalID < 0 || !bp.Enabled { +func Restart(grp, oldgrp *TargetGroup, discard func(*LogicalBreakpoint, error)) { + for _, bp := range oldgrp.LogicalBreakpoints { + if _, ok := grp.LogicalBreakpoints[bp.LogicalID]; ok { continue } + grp.LogicalBreakpoints[bp.LogicalID] = bp bp.TotalHitCount = 0 bp.HitCount = make(map[int64]uint64) bp.Set.PidAddrs = nil // breakpoints set through a list of addresses can not be restored after a restart - err := grp.EnableBreakpoint(bp) - if err != nil { - if discard != nil { - discard(bp, err) + if bp.Enabled { + err := grp.EnableBreakpoint(bp) + if err != nil { + if discard != nil { + discard(bp, err) + } + delete(grp.LogicalBreakpoints, bp.LogicalID) } - delete(grp.LogicalBreakpoints, bp.LogicalID) } } - return grp + if oldgrp.followExecEnabled { + grp.FollowExec(true, "") + } +} + +func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thread, path string, stopReason StopReason) (*Target, error) { + t, err := grp.newTarget(p, pid, currentThread, path) + if err != nil { + return nil, err + } + t.StopReason = stopReason + //TODO(aarzilli): check if the target's command line matches the regex + if t.partOfGroup { + panic("internal error: target is already part of group") + } + t.partOfGroup = true + if grp.RecordingManipulation == nil { + grp.RecordingManipulation = t.recman + grp.recman = t.recman + } + if grp.Selected == nil { + grp.Selected = t + } + t.Breakpoints().Logical = grp.LogicalBreakpoints + logger := logflags.DebuggerLogger() + for _, lbp := range grp.LogicalBreakpoints { + if lbp.LogicalID < 0 { + continue + } + err := enableBreakpointOnTarget(t, lbp) + if err != nil { + logger.Debugf("could not enable breakpoint %d on new target %d: %v", lbp.LogicalID, t.Pid(), err) + } else { + logger.Debugf("breakpoint %d enabled on new target %d: %v", lbp.LogicalID, t.Pid(), err) + } + } + grp.targets = append(grp.targets, t) + return t, nil } // Targets returns a slice of all targets in the group, including the @@ -95,10 +146,22 @@ func (grp *TargetGroup) Valid() (bool, error) { return false, err0 } +func (grp *TargetGroup) numValid() int { + r := 0 + for _, t := range grp.targets { + ok, _ := t.Valid() + if ok { + r++ + } + } + return r +} + // Detach detaches all targets in the group. func (grp *TargetGroup) Detach(kill bool) error { var errs []string - for _, t := range grp.targets { + for i := len(grp.targets) - 1; i >= 0; i-- { + t := grp.targets[i] isvalid, _ := t.Valid() if !isvalid { continue @@ -144,12 +207,10 @@ func (grp *TargetGroup) ThreadList() []Thread { } // TargetForThread returns the target containing the given thread. -func (grp *TargetGroup) TargetForThread(thread Thread) *Target { +func (grp *TargetGroup) TargetForThread(tid int) *Target { for _, t := range grp.targets { - for _, th := range t.ThreadList() { - if th == thread { - return t - } + if _, ok := t.FindThread(tid); ok { + return t } } return nil @@ -274,6 +335,26 @@ func (grp *TargetGroup) DisableBreakpoint(lbp *LogicalBreakpoint) error { return nil } +// FollowExec enables or disables follow exec mode. When follow exec mode is +// enabled new processes spawned by the target process are automatically +// added to the target group. +// If regex is not the empty string only processes whose command line +// matches regex will be added to the target group. +func (grp *TargetGroup) FollowExec(v bool, regex string) error { + if regex != "" { + return errors.New("regex not implemented") + } + it := ValidTargets{Group: grp} + for it.Next() { + err := it.proc.FollowExec(v) + if err != nil { + return err + } + } + grp.followExecEnabled = v + return nil +} + // ValidTargets iterates through all valid targets in Group. type ValidTargets struct { *Target @@ -295,3 +376,9 @@ func (it *ValidTargets) Next() bool { it.Target = nil return false } + +// Reset returns the iterator to the start of the group. +func (it *ValidTargets) Reset() { + it.Target = nil + it.start = 0 +} diff --git a/pkg/proc/variable_test.go b/pkg/proc/variable_test.go index 848cb51a43..747616c3b7 100644 --- a/pkg/proc/variable_test.go +++ b/pkg/proc/variable_test.go @@ -10,9 +10,9 @@ import ( func TestGoroutineCreationLocation(t *testing.T) { protest.AllowRecording(t) - withTestProcess("goroutinestackprog", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("goroutinestackprog", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { bp := setFunctionBreakpoint(p, t, "main.agoroutine") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") gs, _, err := proc.GoroutinesInfo(p, 0, 0) assertNoError(err, t, "GoroutinesInfo") @@ -39,6 +39,6 @@ func TestGoroutineCreationLocation(t *testing.T) { } p.ClearBreakpoint(bp.Addr) - p.Continue() + grp.Continue() }) } diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 53d3fdb28d..9cd993a164 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -140,8 +140,8 @@ func TestVariableEvaluation2(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -198,8 +198,8 @@ func TestSetVariable(t *testing.T) { {"s3", "[]int", "[]int len: 3, cap: 3, [3,4,5]", "arr1[:]", "[]int len: 4, cap: 4, [0,1,2,3]"}, } - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { if tc.name == "iface1" && tc.expr == "parr" { @@ -267,8 +267,8 @@ func TestVariableEvaluationShort(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -323,8 +323,8 @@ func TestMultilineVariableEvaluation(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -399,8 +399,8 @@ func TestLocalVariables(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") for _, tc := range testcases { @@ -436,7 +436,7 @@ func TestLocalVariables(t *testing.T) { func TestEmbeddedStruct(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { testcases := []varTest{ {"b.val", true, "-314", "-314", "int", nil}, {"b.A.val", true, "-314", "-314", "int", nil}, @@ -460,7 +460,7 @@ func TestEmbeddedStruct(t *testing.T) { {"w4.F", false, ``, ``, "", errors.New("w4 has no member F")}, {"w5.F", false, ``, ``, "", errors.New("w5 has no member F")}, } - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") ver, _ := goversion.Parse(runtime.Version()) if ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { @@ -491,8 +491,8 @@ func TestEmbeddedStruct(t *testing.T) { } func TestComplexSetting(t *testing.T) { - withTestProcess("testvariables", t, func(p *proc.Target, fixture protest.Fixture) { - err := p.Continue() + withTestProcess("testvariables", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + err := grp.Continue() assertNoError(err, t, "Continue() returned an error") h := func(setExpr, value string) { @@ -842,8 +842,8 @@ func TestEvalExpression(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for i, tc := range testcases { t.Run(strconv.Itoa(i), func(t *testing.T) { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) @@ -872,8 +872,8 @@ func TestEvalExpression(t *testing.T) { func TestEvalAddrAndCast(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") c1addr, err := evalVariableWithCfg(p, "&c1", pnormalLoadConfig) assertNoError(err, t, "EvalExpression(&c1)") c1addrstr := api.ConvertVar(c1addr).SinglelineString() @@ -899,8 +899,8 @@ func TestEvalAddrAndCast(t *testing.T) { func TestMapEvaluation(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") m1v, err := evalVariableWithCfg(p, "m1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable()") m1 := api.ConvertVar(m1v) @@ -941,8 +941,8 @@ func TestMapEvaluation(t *testing.T) { func TestUnsafePointer(t *testing.T) { protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") up1v, err := evalVariableWithCfg(p, "up1", pnormalLoadConfig) assertNoError(err, t, "EvalVariable(up1)") up1 := api.ConvertVar(up1v) @@ -979,8 +979,8 @@ func TestIssue426(t *testing.T) { // Serialization of type expressions (go/ast.Expr) containing anonymous structs or interfaces // differs from the serialization used by the linker to produce DWARF type information protest.AllowRecording(t) - withTestProcess("testvariables2", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, testcase := range testcases { v, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) @@ -1065,8 +1065,8 @@ func TestPackageRenames(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("pkgrenames", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("pkgrenames", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") testPackageRenamesHelper(t, p, testcases) testPackageRenamesHelper(t, p, testcases1_9) @@ -1101,8 +1101,8 @@ func TestConstants(t *testing.T) { // Not supported on 1.9 or earlier t.Skip("constants added in go 1.10") } - withTestProcess("consts", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue") + withTestProcess("consts", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") for _, testcase := range testcases { variable, err := evalVariableWithCfg(p, testcase.name, pnormalLoadConfig) assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", testcase.name)) @@ -1112,9 +1112,9 @@ func TestConstants(t *testing.T) { } func TestIssue1075(t *testing.T) { - withTestProcess("clientdo", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("clientdo", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFunctionBreakpoint(p, t, "net/http.(*Client).Do") - assertNoError(p.Continue(), t, "Continue()") + assertNoError(grp.Continue(), t, "Continue()") for i := 0; i < 10; i++ { scope, err := proc.GoroutineScope(p, p.CurrentThread()) assertNoError(err, t, fmt.Sprintf("GoroutineScope (%d)", i)) @@ -1260,9 +1260,8 @@ func TestCallFunction(t *testing.T) { {`floatsum(1, 2)`, []string{":float64:3"}, nil}, } - withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { - grp := proc.NewGroup(p) - testCallFunctionSetBreakpoint(t, p, fixture) + withTestProcessArgs("fncall", t, ".", nil, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + testCallFunctionSetBreakpoint(t, p, grp, fixture) assertNoError(grp.Continue(), t, "Continue()") for _, tc := range testcases { @@ -1303,7 +1302,7 @@ func TestCallFunction(t *testing.T) { }) } -func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, fixture protest.Fixture) { +func testCallFunctionSetBreakpoint(t *testing.T, p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { buf, err := ioutil.ReadFile(fixture.Source) assertNoError(err, t, "ReadFile") for i, line := range strings.Split(string(buf), "\n") { @@ -1390,8 +1389,8 @@ func testCallFunction(t *testing.T, grp *proc.TargetGroup, p *proc.Target, tc te func TestIssue1531(t *testing.T) { // Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0. - withTestProcess("issue1531", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("issue1531", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") hasKeys := func(mv *proc.Variable, keys ...string) { n := 0 @@ -1453,9 +1452,9 @@ func assertCurrentLocationFunction(p *proc.Target, t *testing.T, fnname string) func TestPluginVariables(t *testing.T) { pluginFixtures := protest.WithPlugins(t, protest.AllNonOptimized, "plugin1/", "plugin2/") - withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, fixture protest.Fixture) { + withTestProcessArgs("plugintest2", t, ".", []string{pluginFixtures[0].Path, pluginFixtures[1].Path}, protest.AllNonOptimized, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { setFileBreakpoint(p, t, fixture.Source, 41) - assertNoError(p.Continue(), t, "Continue 1") + assertNoError(grp.Continue(), t, "Continue 1") bp := setFunctionBreakpoint(p, t, "github.com/go-delve/delve/_fixtures/plugin2.TypesTest") t.Logf("bp.Addr = %#x", bp.Addr) @@ -1465,7 +1464,7 @@ func TestPluginVariables(t *testing.T) { t.Logf("%#x %s\n", image.StaticBase, image.Path) } - assertNoError(p.Continue(), t, "Continue 2") + assertNoError(grp.Continue(), t, "Continue 2") // test that PackageVariables returns variables from the executable and plugins scope, err := evalScope(p) @@ -1495,15 +1494,15 @@ func TestPluginVariables(t *testing.T) { // 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(p.Continue(), t, "Continue 3") + assertNoError(grp.Continue(), t, "Continue 3") assertCurrentLocationFunction(p, t, "github.com/go-delve/delve/_fixtures/plugin2.aIsNotNil") vstr, err := evalVariableWithCfg(p, "str", pnormalLoadConfig) assertNoError(err, t, "Eval(str)") assertVariable(t, vstr, varTest{"str", true, `"success"`, ``, `string`, nil}) - assertNoError(p.StepOut(), t, "StepOut") - assertNoError(p.StepOut(), t, "StepOut") - assertNoError(p.Next(), t, "Next") + assertNoError(grp.StepOut(), t, "StepOut") + assertNoError(grp.StepOut(), t, "StepOut") + assertNoError(grp.Next(), t, "Next") // read interface variable, inside executable code, with a concrete type defined in a plugin vb, err := evalVariableWithCfg(p, "b", pnormalLoadConfig) @@ -1534,8 +1533,8 @@ func TestCgoEval(t *testing.T) { } protest.AllowRecording(t) - withTestProcess("testvariablescgo/", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue() returned an error") + withTestProcess("testvariablescgo/", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue() returned an error") for _, tc := range testcases { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) if err != nil && err.Error() == "evaluating methods not supported on this version of Go" { @@ -1581,9 +1580,9 @@ func TestEvalExpressionGenerics(t *testing.T) { }, } - withTestProcess("testvariables_generic", t, func(p *proc.Target, fixture protest.Fixture) { + withTestProcess("testvariables_generic", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { for i, tcs := range testcases { - assertNoError(p.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i)) + assertNoError(grp.Continue(), t, fmt.Sprintf("Continue() returned an error (%d)", i)) for _, tc := range tcs { variable, err := evalVariableWithCfg(p, tc.name, pnormalLoadConfig) if tc.err == nil { @@ -1604,8 +1603,8 @@ func TestEvalExpressionGenerics(t *testing.T) { // Test the behavior when reading dangling pointers produced by unsafe code. func TestBadUnsafePtr(t *testing.T) { - withTestProcess("testunsafepointers", t, func(p *proc.Target, fixture protest.Fixture) { - assertNoError(p.Continue(), t, "Continue()") + withTestProcess("testunsafepointers", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") // danglingPtrPtr is a pointer with value 0x42, which is an unreadable // address. diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index e6b3c2cc23..d7209576c5 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -79,6 +79,7 @@ func (ft *FakeTerminal) ExecStarlark(starlarkProgram string) (outstr string, err } func (ft *FakeTerminal) MustExec(cmdstr string) string { + ft.t.Helper() outstr, err := ft.Exec(cmdstr) if err != nil { ft.t.Errorf("output of %q: %q", cmdstr, outstr) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 66dbf2850b..64b1556479 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -161,30 +161,28 @@ func New(config *Config, processArgs []string) (*Debugger, error) { if len(d.processArgs) > 0 { path = d.processArgs[0] } - p, err := d.Attach(d.config.AttachPid, path) + var err error + d.target, err = d.Attach(d.config.AttachPid, path) if err != nil { err = go11DecodeErrorCheck(err) err = noDebugErrorWarning(err) return nil, attachErrorMessage(d.config.AttachPid, err) } - d.target = proc.NewGroup(p) case d.config.CoreFile != "": - var p *proc.Target var err error switch d.config.Backend { case "rr": d.log.Infof("opening trace %s", d.config.CoreFile) - p, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories) + d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories) default: d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0]) - p, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories) + d.target, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories) } if err != nil { err = go11DecodeErrorCheck(err) return nil, err } - d.target = proc.NewGroup(p) if err := d.checkGoVersion(); err != nil { d.target.Detach(true) return nil, err @@ -192,7 +190,8 @@ func New(config *Config, processArgs []string) (*Debugger, error) { default: d.log.Infof("launching process with args: %v", d.processArgs) - p, err := d.Launch(d.processArgs, d.config.WorkingDir) + var err error + d.target, err = d.Launch(d.processArgs, d.config.WorkingDir) if err != nil { if _, ok := err.(*proc.ErrUnsupportedArch); !ok { err = go11DecodeErrorCheck(err) @@ -201,10 +200,6 @@ func New(config *Config, processArgs []string) (*Debugger, error) { } return nil, err } - if p != nil { - // if p == nil and err == nil then we are doing a recording, don't touch d.target - d.target = proc.NewGroup(p) - } if err := d.checkGoVersion(); err != nil { d.target.Detach(true) return nil, err @@ -245,7 +240,7 @@ func (d *Debugger) TargetGoVersion() string { } // Launch will start a process with the given args and working directory. -func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) { +func (d *Debugger) Launch(processArgs []string, wd string) (*proc.TargetGroup, error) { fullpath, err := verifyBinaryFormat(processArgs[0]) if err != nil { return nil, err @@ -285,7 +280,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) go func() { defer d.targetMutex.Unlock() - p, err := d.recordingRun(run) + grp, err := d.recordingRun(run) if err != nil { d.log.Errorf("could not record target: %v", err) // this is ugly but we can't respond to any client requests at this @@ -293,7 +288,7 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) os.Exit(1) } d.recordingDone() - d.target = proc.NewGroup(p) + d.target = grp if err := d.checkGoVersion(); err != nil { d.log.Error(err) err := d.target.Detach(true) @@ -332,7 +327,7 @@ func (d *Debugger) isRecording() bool { return d.stopRecording != nil } -func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error) { +func (d *Debugger) recordingRun(run func() (string, error)) (*proc.TargetGroup, error) { tracedir, err := run() if err != nil && tracedir == "" { return nil, err @@ -342,7 +337,7 @@ func (d *Debugger) recordingRun(run func() (string, error)) (*proc.Target, error } // Attach will attach to the process specified by 'pid'. -func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) { +func (d *Debugger) Attach(pid int, path string) (*proc.TargetGroup, error) { switch d.config.Backend { case "native": return native.Attach(pid, d.config.DebugInfoDirectories) @@ -360,7 +355,7 @@ func (d *Debugger) Attach(pid int, path string) (*proc.Target, error) { var errMacOSBackendUnavailable = errors.New("debugserver or lldb-server not found: install Xcode's command line tools or lldb-server") -func betterGdbserialLaunchError(p *proc.Target, err error) (*proc.Target, error) { +func betterGdbserialLaunchError(p *proc.TargetGroup, err error) (*proc.TargetGroup, error) { if runtime.GOOS != "darwin" { return p, err } @@ -482,7 +477,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] d.processArgs = append([]string{d.processArgs[0]}, newArgs...) d.config.Redirects = newRedirects } - var p *proc.Target + var grp *proc.TargetGroup var err error if rebuild { @@ -510,19 +505,20 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } d.recordingStart(stop) - p, err = d.recordingRun(run) + grp, err = d.recordingRun(run) d.recordingDone() } else { - p, err = d.Launch(d.processArgs, d.config.WorkingDir) + grp, err = d.Launch(d.processArgs, d.config.WorkingDir) } if err != nil { return nil, fmt.Errorf("could not launch process: %s", err) } discarded := []api.DiscardedBreakpoint{} - d.target = proc.NewGroupRestart(p, d.target, func(oldBp *proc.LogicalBreakpoint, err error) { + proc.Restart(grp, d.target, func(oldBp *proc.LogicalBreakpoint, err error) { discarded = append(discarded, api.DiscardedBreakpoint{Breakpoint: api.ConvertLogicalBreakpoint(oldBp), Reason: err.Error()}) }) + d.target = grp return discarded, nil } @@ -1321,7 +1317,7 @@ func (d *Debugger) collectBreakpointInformation(apiThread *api.Thread, thread pr bpi := &api.BreakpointInfo{} apiThread.BreakpointInfo = bpi - tgt := d.target.TargetForThread(thread) + tgt := d.target.TargetForThread(thread.ThreadID()) if bp.Goroutine { g, err := proc.GetG(thread) @@ -2141,7 +2137,7 @@ func (d *Debugger) DumpStart(dest string) error { //TODO(aarzilli): what do we do if the user switches to a different target after starting a dump but before it's finished? - if !d.target.Selected.CanDump { + if !d.target.CanDump { d.targetMutex.Unlock() return ErrCoreDumpNotSupported } From d71b3aa6a74cbd420df350aa84def79651d03c51 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Sat, 25 Feb 2023 15:12:12 +0200 Subject: [PATCH 63/72] dwarf/op: change generated comment header (#3288) Change generated comment header for opcodes.go to match Go regexp: ``` ^// Code generated .* DO NOT EDIT\.$ ``` Fix panic in gen-opcodes.go if no args provided. --- _scripts/gen-opcodes.go | 18 +++++++++++++++--- pkg/dwarf/op/opcodes.go | 3 ++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/_scripts/gen-opcodes.go b/_scripts/gen-opcodes.go index 9fa1f0c2b2..e4fa53b52d 100644 --- a/_scripts/gen-opcodes.go +++ b/_scripts/gen-opcodes.go @@ -1,4 +1,5 @@ -// This script generates pkg/dwarf/op/opcodes.go from pkg/dwarf/op/opcodes.txt +//go:build ignore +// +build ignore package main @@ -19,7 +20,17 @@ type Opcode struct { Func string } +func usage() { + os.Stderr.WriteString("gen-opcodes \n\n") + os.Stderr.WriteString("Generates Go file from the table file .\n\n") + os.Exit(1) +} + func main() { + if len(os.Args) != 3 { + usage() + } + fh, err := os.Open(os.Args[1]) if err != nil { log.Fatal(err) @@ -52,8 +63,9 @@ func main() { var buf bytes.Buffer - fmt.Fprintf(&buf, `// THIS FILE IS AUTOGENERATED, EDIT opcodes.table INSTEAD - + fmt.Fprintf(&buf, `// Code generated by gen-opcodes. DO NOT EDIT. +// Edit opcodes.table instead. + package op `) diff --git a/pkg/dwarf/op/opcodes.go b/pkg/dwarf/op/opcodes.go index e53088c029..5a5643a10d 100644 --- a/pkg/dwarf/op/opcodes.go +++ b/pkg/dwarf/op/opcodes.go @@ -1,4 +1,5 @@ -// THIS FILE IS AUTOGENERATED, EDIT opcodes.table INSTEAD +// Code generated by gen-opcodes. DO NOT EDIT. +// Edit opcodes.table instead. package op From 0a7b051fedb68c92ea4e7838c0410c50016e4f36 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Sat, 25 Feb 2023 15:12:46 +0200 Subject: [PATCH 64/72] dwarf/line: enable TestGrafana on Windows (#3289) --- pkg/dwarf/line/state_machine_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index a9ff2b3dfa..0effee88d7 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -10,7 +10,6 @@ import ( "io" "io/ioutil" "os" - "runtime" "testing" pdwarf "github.com/go-delve/delve/pkg/dwarf" @@ -36,9 +35,6 @@ func TestGrafana(t *testing.T) { // of grafana to the output generated using debug/dwarf.LineReader on the // same section. - if runtime.GOOS == "windows" { - t.Skip("filepath.Join ruins this test on windows") - } debugBytes, err := slurpGzip("_testdata/debug.grafana.debug.gz") if err != nil { t.Fatal(err) From 372552bf1fe314e896aad3022bc6dda2e9f8ed37 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Tue, 28 Feb 2023 15:52:52 +0200 Subject: [PATCH 65/72] Documentation: fix typo and grammar issues (#3291) --- Documentation/api/ClientHowto.md | 4 ++-- Documentation/api/dap/README.md | 6 +++--- Documentation/cli/README.md | 2 +- Documentation/cli/expr.md | 2 +- Documentation/cli/starlark.md | 2 +- Documentation/usage/dlv_test.md | 2 +- cmd/dlv/cmds/commands.go | 2 +- pkg/terminal/command.go | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/api/ClientHowto.md b/Documentation/api/ClientHowto.md index 34807c13d5..3bc85dad3c 100644 --- a/Documentation/api/ClientHowto.md +++ b/Documentation/api/ClientHowto.md @@ -122,7 +122,7 @@ breakpoints: "next" will continue until the next line of the program, unexported runtime functions). All of "next", "step" and "stepout" operate on the selected goroutine. The -selected gorutine is described by the `SelectedGoroutine` field of +selected goroutine is described by the `SelectedGoroutine` field of `DebuggerState`. Every time `Command` returns the selected goroutine will be reset to the goroutine that triggered the breakpoint. @@ -203,7 +203,7 @@ There are several API entry points to evaluate variables in Delve: * RPCServer.ListPackageVars returns all global variables in all packages * PRCServer.ListLocalVars returns all local variables of a stack frame * RPCServer.ListFunctionArgs returns all function arguments of a stack frame -* RPCServer.Eval evaluets an expression on a given stack frame +* RPCServer.Eval evaluates an expression on a given stack frame All those API calls take a LoadConfig argument. The LoadConfig specifies how much of the variable's value should actually be loaded. Because of diff --git a/Documentation/api/dap/README.md b/Documentation/api/dap/README.md index a67b1d5287..74565d07bc 100644 --- a/Documentation/api/dap/README.md +++ b/Documentation/api/dap/README.md @@ -4,7 +4,7 @@ Delve exposes a [DAP](https://microsoft.github.io/debug-adapter-protocol/overvie This interface is served over a streaming TCP socket using `dlv` server in one of the two headless modes: 1. [`dlv dap`](../../usage/dlv_dap.md) - starts a single-use DAP-only server that waits for a client to specify launch/attach configuration for starting the debug session. -2. `dlv --headless ` - starts a general server, enters a debug session for the specified debuggee and waits for a [JSON-RPC](../json-rpc/README.md) or a [DAP](https://microsoft.github.io/debug-adapter-protocol/overview) remote-attach client to begin interactive debugging. Can be used in multi-client mode with the following options: +2. `dlv --headless ` - starts a general server, enters a debug session for the specified debuggee and waits for a [JSON-RPC](../json-rpc/README.md) or a [DAP](https://microsoft.github.io/debug-adapter-protocol/overview) remote-attach client to begin interactive debugging. Can be used in multi-client mode with the following options: * `--accept-multiclient` - use to support connections from multiple clients * `--continue` - use to resume debuggee execution as soon as server session starts @@ -73,7 +73,7 @@ Not all of the configurations are supported by each of the two available DAP ser ### Single-Client Mode -When used with `dlv dap` or `dlv --headless --accept-multiclient=false` (default), the DAP server will shut itself down at the end of the debug session, when the client sends a [disconnect request](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect). If the debuggee was launched, it will be taken down as well. If the debugee was attached to, `terminateDebuggee` option will be respected. +When used with `dlv dap` or `dlv --headless --accept-multiclient=false` (default), the DAP server will shut itself down at the end of the debug session, when the client sends a [disconnect request](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect). If the debuggee was launched, it will be taken down as well. If the debuggee was attached to, `terminateDebuggee` option will be respected. When the program terminates, we send a [terminated event](https://microsoft.github.io/debug-adapter-protocol/specification#Events_Terminated), which is expected to trigger a [disconnect request](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect) from the client for a session and a server shutdown. The [restart request](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Restart) is not yet supported. @@ -83,7 +83,7 @@ Pressing Ctrl-C on the terminal where a headless server is running sends SIGINT ### Multi-Client Mode -When used with `dlv --headless --accept-multiclient=true`, the DAP server will honor the multi-client mode when a client [disconnects](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect)) or client connection fails. The server will remain running and ready for a new client connection, and the debuggee will remain in whatever state it was at the time of disconnect - running or halted. Once [`suspendDebuggee`](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect) option is supported by frontends like VS Code ([vscode/issues/134412](https://github.com/microsoft/vscode/issues/134412)), we will update the server to offer this as a way to specify debuggee state on disconnect. +When used with `dlv --headless --accept-multiclient=true`, the DAP server will honor the multi-client mode when a client [disconnects](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect) or client connection fails. The server will remain running and ready for a new client connection, and the debuggee will remain in whatever state it was at the time of disconnect - running or halted. Once [`suspendDebuggee`](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect) option is supported by frontends like VS Code ([vscode/issues/134412](https://github.com/microsoft/vscode/issues/134412)), we will update the server to offer this as a way to specify debuggee state on disconnect. The client may request full shutdown of the server and the debuggee with [`terminateDebuggee`](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect) option. diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 8d49e9dfa8..ec14bea199 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -199,7 +199,7 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n. -With the -clear option a condtion on the breakpoint can removed. +With the -clear option a condition on the breakpoint can removed. The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n. diff --git a/Documentation/cli/expr.md b/Documentation/cli/expr.md index 3cb67d7ad2..cf40df260d 100644 --- a/Documentation/cli/expr.md +++ b/Documentation/cli/expr.md @@ -133,7 +133,7 @@ Because many architectures have SIMD registers that can be used by the applicati * `REGNAME.intN` returns the register REGNAME as an array of intN elements. * `REGNAME.uintN` returns the register REGNAME as an array of uintN elements. -* `REGNAME.floatN` returns the register REGNAME as an array fo floatN elements. +* `REGNAME.floatN` returns the register REGNAME as an array of floatN elements. In all cases N must be a power of 2. diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index 7942ef1d87..4f79900217 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -178,7 +178,7 @@ def command_echo_expr(a, b, c): print("a", a, "b", b, "c", c) ``` -The first commnad, `echo`, takes its arguments as a single string, while for `echo_expr` it will be possible to pass starlark expression as arguments: +The first command, `echo`, takes its arguments as a single string, while for `echo_expr` it will be possible to pass starlark expression as arguments: ``` (dlv) echo 2+2, 2-1, 2*3 diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index cdeeac4179..d7817541a2 100644 --- a/Documentation/usage/dlv_test.md +++ b/Documentation/usage/dlv_test.md @@ -11,7 +11,7 @@ unit tests. By default Delve will debug the tests in the current directory. Alternatively you can specify a package name, and Delve will debug the tests in that package instead. Double-dashes `--` can be used to pass arguments to the test program: -dlv test [package] -- -test.run TestSometing -test.v -other-argument +dlv test [package] -- -test.run TestSomething -test.v -other-argument See also: 'go help testflag'. diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index 39c0b3ea5a..e27653fd4a 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -278,7 +278,7 @@ unit tests. By default Delve will debug the tests in the current directory. Alternatively you can specify a package name, and Delve will debug the tests in that package instead. Double-dashes ` + "`--`" + ` can be used to pass arguments to the test program: -dlv test [package] -- -test.run TestSometing -test.v -other-argument +dlv test [package] -- -test.run TestSomething -test.v -other-argument See also: 'go help testflag'.`, Run: testCmd, diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 3553e78eb5..5d92d9f9f3 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -476,7 +476,7 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n. -With the -clear option a condtion on the breakpoint can removed. +With the -clear option a condition on the breakpoint can removed. The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n. From 212c2002bb28e2b6f77cff645ae81b29dfaced41 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Wed, 1 Mar 2023 20:27:06 +0100 Subject: [PATCH 66/72] proc: do not try to load a non-empty slice if the base address is 0 (#3295) --- _fixtures/testvariables2.go | 7 ++++++- pkg/proc/variables.go | 4 ++++ pkg/proc/variables_test.go | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 5ee588e24b..26439c9dfb 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -4,6 +4,7 @@ import ( "fmt" "go/constant" "math" + "reflect" "runtime" "time" "unsafe" @@ -371,6 +372,10 @@ func main() { namedA1 := astructName1{12, 45} namedA2 := astructName2{13, 46} + badslice := []int{1, 2, 3} + h := (*reflect.SliceHeader)(unsafe.Pointer(&badslice)) + h.Data = 0 + var amb1 = 1 runtime.Breakpoint() for amb1 := 0; amb1 < 10; amb1++ { @@ -381,5 +386,5 @@ func main() { longslice := make([]int, 100, 100) runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2)) + fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice) } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index e24c8240b7..b1dc27e5e6 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -1656,6 +1656,10 @@ func (v *Variable) loadArrayValues(recurseLevel int, cfg LoadConfig) { v.Unreadable = errors.New("Negative array length") return } + if v.Base == 0 && v.Len > 0 { + v.Unreadable = errors.New("non-zero length array with nil base") + return + } count := v.Len // Cap number of elements diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 9cd993a164..dab345b476 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -829,6 +829,9 @@ func TestEvalExpression(t *testing.T) { // Conversions to ptr-to-ptr types {`**(**runtime.hmap)(uintptr(&m1))`, false, `…`, `…`, "runtime.hmap", nil}, + + // Malformed values + {`badslice`, false, `(unreadable non-zero length array with nil base)`, `(unreadable non-zero length array with nil base)`, "[]int", nil}, } ver, _ := goversion.Parse(runtime.Version()) From c0b0148525472f79a136ee5572332ad932facb57 Mon Sep 17 00:00:00 2001 From: Joseph Callen Date: Wed, 1 Mar 2023 14:28:32 -0500 Subject: [PATCH 67/72] cmd/dlv: Add flag to replay for rr onprocess pid (#3281) Add `-p` or `rr-onprocess-pid` to replay command to pass `-p` or `--onprocess` pid to `rr`. --- Documentation/usage/dlv_replay.md | 3 ++- cmd/dlv/cmds/commands.go | 7 +++++++ pkg/proc/gdbserial/rr.go | 15 ++++++++++++--- service/debugger/debugger.go | 6 ++++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Documentation/usage/dlv_replay.md b/Documentation/usage/dlv_replay.md index fcc995b86d..8cec2ef924 100644 --- a/Documentation/usage/dlv_replay.md +++ b/Documentation/usage/dlv_replay.md @@ -17,7 +17,8 @@ dlv replay [trace directory] [flags] ### Options ``` - -h, --help help for replay + -h, --help help for replay + -p, --onprocess int Pass onprocess pid to rr. ``` ### Options inherited from parent commands diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index e27653fd4a..c98c047b51 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -91,6 +91,8 @@ var ( conf *config.Config loadConfErr error + + rrOnProcessPid int ) const dlvCommandLongDesc = `Delve is a source level debugger for Go programs. @@ -368,6 +370,10 @@ https://github.com/mozilla/rr os.Exit(execute(0, []string{}, conf, args[0], debugger.ExecutingOther, args, buildFlags)) }, } + + replayCommand.Flags().IntVarP(&rrOnProcessPid, "onprocess", "p", 0, + "Pass onprocess pid to rr.") + rootCommand.AddCommand(replayCommand) } @@ -981,6 +987,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile TTY: tty, Redirects: redirects, DisableASLR: disableASLR, + RrOnProcessPid: rrOnProcessPid, }, }) default: diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index 52a32080c5..ca70850b62 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -124,12 +124,21 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir // Replay starts an instance of rr in replay mode, with the specified trace // directory, and connects to it. -func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.TargetGroup, error) { +func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string, rrOnProcessPid int) (*proc.TargetGroup, error) { if err := checkRRAvailable(); err != nil { return nil, err } - rrcmd := exec.Command("rr", "replay", "--dbgport=0", tracedir) + args := []string{ + "replay", + "--dbgport=0", + } + if rrOnProcessPid != 0 { + args = append(args, fmt.Sprintf("--onprocess=%d", rrOnProcessPid)) + } + args = append(args, tracedir) + + rrcmd := exec.Command("rr", args...) rrcmd.Stdout = os.Stdout stderr, err := rrcmd.StderrPipe() if err != nil { @@ -284,7 +293,7 @@ func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string if tracedir == "" { return nil, "", err } - t, err := Replay(tracedir, quiet, true, debugInfoDirs) + t, err := Replay(tracedir, quiet, true, debugInfoDirs, 0) return t, tracedir, err } diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 64b1556479..c58eeb2b91 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -141,6 +141,8 @@ type Config struct { // DisableASLR disables ASLR DisableASLR bool + + RrOnProcessPid int } // New creates a new Debugger. ProcessArgs specify the commandline arguments for the @@ -174,7 +176,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) { switch d.config.Backend { case "rr": d.log.Infof("opening trace %s", d.config.CoreFile) - d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories) + d.target, err = gdbserial.Replay(d.config.CoreFile, false, false, d.config.DebugInfoDirectories, d.config.RrOnProcessPid) default: d.log.Infof("opening core file %s (executable %s)", d.config.CoreFile, d.processArgs[0]) d.target, err = core.OpenCore(d.config.CoreFile, d.processArgs[0], d.config.DebugInfoDirectories) @@ -333,7 +335,7 @@ func (d *Debugger) recordingRun(run func() (string, error)) (*proc.TargetGroup, return nil, err } - return gdbserial.Replay(tracedir, false, true, d.config.DebugInfoDirectories) + return gdbserial.Replay(tracedir, false, true, d.config.DebugInfoDirectories, 0) } // Attach will attach to the process specified by 'pid'. From 7a05a4326f75ad478ad72904d8d1aaf06d5ab57a Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Mon, 6 Mar 2023 08:58:07 +0200 Subject: [PATCH 68/72] proc,terminal: remove unused unexported functions (#3299) --- pkg/proc/proc_test.go | 9 --------- pkg/terminal/command_test.go | 13 ------------- 2 files changed, 22 deletions(-) diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 013d1c83b7..61df0f78f3 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -2832,15 +2832,6 @@ func TestNextInDeferReturn(t *testing.T) { }) } -func getg(goid int64, gs []*proc.G) *proc.G { - for _, g := range gs { - if g.ID == goid { - return g - } - } - return nil -} - func TestAttachDetach(t *testing.T) { if testBackend == "lldb" && runtime.GOOS == "linux" { bs, _ := ioutil.ReadFile("/proc/sys/kernel/yama/ptrace_scope") diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index d7209576c5..ee9c605154 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -559,19 +559,6 @@ func TestOnPrefixLocals(t *testing.T) { }) } -func countOccurrences(s, needle string) int { - count := 0 - for { - idx := strings.Index(s, needle) - if idx < 0 { - break - } - count++ - s = s[idx+len(needle):] - } - return count -} - func listIsAt(t *testing.T, term *FakeTerminal, listcmd string, cur, start, end int) { t.Helper() outstr := term.MustExec(listcmd) From a9d699b581aa81fc8680f8996b53a81997794abc Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Tue, 14 Mar 2023 22:23:25 +0100 Subject: [PATCH 69/72] proc: fix automatic breakpoints visibility (#3300) Unrecovered-panic and fatal-throw were no longer part of the breakpoint list because starting in 37e44bf they were created before the logical breakpoints map was switched to the logical breakpoints map of the target group. --- pkg/proc/target.go | 1 + pkg/proc/target_group.go | 1 - service/test/integration2_test.go | 21 +++++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/proc/target.go b/pkg/proc/target.go index 90403449eb..e1fafda0cd 100644 --- a/pkg/proc/target.go +++ b/pkg/proc/target.go @@ -193,6 +193,7 @@ func (grp *TargetGroup) newTarget(p ProcessInternal, pid int, currentThread Thre g, _ := GetG(currentThread) t.selectedGoroutine = g + t.Breakpoints().Logical = grp.LogicalBreakpoints t.createUnrecoveredPanicBreakpoint() t.createFatalThrowBreakpoint() t.createPluginOpenBreakpoint() diff --git a/pkg/proc/target_group.go b/pkg/proc/target_group.go index 5385b93da5..66ed9e735c 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -108,7 +108,6 @@ func (grp *TargetGroup) addTarget(p ProcessInternal, pid int, currentThread Thre if grp.Selected == nil { grp.Selected = t } - t.Breakpoints().Logical = grp.LogicalBreakpoints logger := logflags.DebuggerLogger() for _, lbp := range grp.LogicalBreakpoints { if lbp.LogicalID < 0 { diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index a5478131fe..068ff1050e 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -23,6 +23,7 @@ import ( "github.com/go-delve/delve/pkg/goversion" "github.com/go-delve/delve/pkg/logflags" + "github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/service" "github.com/go-delve/delve/service/api" "github.com/go-delve/delve/service/rpc2" @@ -2970,3 +2971,23 @@ func TestClientServer_createBreakpointWithID(t *testing.T) { } }) } + +func TestClientServer_autoBreakpoints(t *testing.T) { + // Check that unrecoverd-panic and fatal-throw breakpoints are visible in + // the breakpoint list. + protest.AllowRecording(t) + withTestClient2("math", t, func(c service.Client) { + bps, err := c.ListBreakpoints(false) + assertNoError(err, t, "ListBreakpoints") + n := 0 + for _, bp := range bps { + t.Log(bp) + if bp.Name == proc.UnrecoveredPanic || bp.Name == proc.FatalThrow { + n++ + } + } + if n != 2 { + t.Error("automatic breakpoints not found") + } + }) +} From 5b8296782b35983ff672851c52f4797e3c9d869f Mon Sep 17 00:00:00 2001 From: Daniel Frederick Crisman Date: Thu, 16 Mar 2023 15:11:06 -0400 Subject: [PATCH 70/72] Documentation: expand project layout to match usage (#3304) The tree view of the project layout seemed to show that "github.com", "cmd", and "pkg" were all in the same directory. The usage later in the document (and normal go layout) would have them as subdirectories. The new tree output was generated with the below: (cd `mktemp -d` mkdir -p github.com/me/foo/{cmd/foo,pkg/baz} touch github.com/me/foo/{cmd/foo/main,pkg/baz/bar{_test,}}.go tree -n --noreport github.com/me/foo ) --- Documentation/cli/getting_started.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Documentation/cli/getting_started.md b/Documentation/cli/getting_started.md index 504a287e70..039239dc89 100644 --- a/Documentation/cli/getting_started.md +++ b/Documentation/cli/getting_started.md @@ -14,15 +14,14 @@ otherwise it optionally accepts a package path. For example given this project layout: ``` -. -├── github.com/me/foo +github.com/me/foo ├── cmd -│ └── foo -│ └── main.go -├── pkg -│ └── baz -│ ├── bar.go -│ └── bar_test.go +│   └── foo +│   └── main.go +└── pkg + └── baz + ├── bar.go + └── bar_test.go ``` If you are in the directory `github.com/me/foo/cmd/foo` you can simply run `dlv debug` From 9faf66b7a19d2260b3c173af88977e384931e093 Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 16 Mar 2023 20:12:20 +0100 Subject: [PATCH 71/72] proc: limit maximum time.Time we try to format (#3294) The loop adding maxAddSeconds to format a time.Time can take multiple seconds to complete if the time is very far into the future. To avoid this loop slowing down debugging too much limit it to an arbitrary maximum. The chosen maximum is 1000 times the maximum expressible time.Duration, which is 262 years. This means that we will not format dates beyond year 262000 AD. --- _fixtures/testvariables2.go | 5 ++++- pkg/proc/variables.go | 4 ++++ pkg/proc/variables_test.go | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 26439c9dfb..ce9a7c1975 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -5,6 +5,7 @@ import ( "go/constant" "math" "reflect" + "os" "runtime" "time" "unsafe" @@ -365,6 +366,7 @@ func main() { w5 := &W5{nil} w5.W5 = w5 + os.Setenv("TZ", "UTC") tim1 := time.Unix(233431200, 0) loc, _ := time.LoadLocation("Mexico/BajaSur") tim2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2022-06-07 02:03:04", loc) @@ -375,6 +377,7 @@ func main() { badslice := []int{1, 2, 3} h := (*reflect.SliceHeader)(unsafe.Pointer(&badslice)) h.Data = 0 + tim3 := time.Date(300000, 1, 1, 0, 0, 0, 0, time.Local) var amb1 = 1 runtime.Breakpoint() @@ -386,5 +389,5 @@ func main() { longslice := make([]int, 100, 100) runtime.Breakpoint() - fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice) + fmt.Println(i1, i2, i3, p1, pp1, amb1, s1, s3, a0, a1, p2, p3, s2, as1, str1, f1, fn1, fn2, nilslice, nilptr, ch1, chnil, m1, mnil, m2, m3, m4, m5, upnil, up1, i4, i5, i6, err1, err2, errnil, iface1, iface2, ifacenil, arr1, parr, cpx1, const1, iface3, iface4, recursive1, recursive1.x, iface5, iface2fn1, iface2fn2, bencharr, benchparr, mapinf, mainMenu, b, b2, sd, anonstruct1, anonstruct2, anoniface1, anonfunc, mapanonstruct1, ifacearr, efacearr, ni8, ni16, ni32, ni64, pinf, ninf, nan, zsvmap, zsslice, zsvar, tm, rettm, errtypednil, emptyslice, emptymap, byteslice, bytestypeslice, runeslice, bytearray, bytetypearray, runearray, longstr, nilstruct, as2, as2.NonPointerRecieverMethod, s4, iface2map, issue1578, ll, unread, w2, w3, w4, w5, longarr, longslice, val, m6, m7, cl, tim1, tim2, typedstringvar, namedA1, namedA2, astructName1(namedA2), badslice, tim3) } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index b1dc27e5e6..a41dac86af 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -2595,6 +2595,10 @@ func (v *Variable) formatTime() { } else { // the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext var t time.Time + if ext > int64(maxAddSeconds/time.Second)*1000 { + // avoid doing the add loop below if it will take too much time + return + } for ext > int64(maxAddSeconds/time.Second) { t = t.Add(maxAddSeconds) ext -= int64(maxAddSeconds / time.Second) diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index dab345b476..2b7095a91a 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -795,6 +795,7 @@ func TestEvalExpression(t *testing.T) { // pretty printing special types {"tim1", false, `time.Time(1977-05-25T18:00:00Z)…`, `time.Time(1977-05-25T18:00:00Z)…`, "time.Time", nil}, {"tim2", false, `time.Time(2022-06-07T02:03:04-06:00)…`, `time.Time(2022-06-07T02:03:04-06:00)…`, "time.Time", nil}, + {"tim3", false, `time.Time {…`, `time.Time {…`, "time.Time", nil}, // issue #3034 - map access with long string key {`m6["very long string 0123456789a0123456789b0123456789c0123456789d0123456789e0123456789f0123456789g012345678h90123456789i0123456789j0123456789"]`, false, `123`, `123`, "int", nil}, From 15223823366b15db1e649a0e232ef0900ce0baad Mon Sep 17 00:00:00 2001 From: Alessandro Arzilli Date: Thu, 16 Mar 2023 20:13:10 +0100 Subject: [PATCH 72/72] proc: fuzzing expression evaluator and variable loader (#3293) Add code to fuzz the expression evaluator and variable loader, see comment to FuzzEvalExpression for how to use this. --- pkg/proc/core/core.go | 10 +- pkg/proc/core/core_test.go | 4 +- pkg/proc/core/linux_core.go | 2 +- pkg/proc/core/windows_amd64_minidump.go | 2 +- pkg/proc/variables_fuzz_test.go | 245 ++++++++++++++++++++++++ pkg/proc/variables_test.go | 9 +- 6 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 pkg/proc/variables_fuzz_test.go diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 1407627c08..734dbd4159 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -14,7 +14,7 @@ import ( // ErrNoThreads core file did not contain any threads. var ErrNoThreads = errors.New("no threads found in core file") -// A splicedMemory represents a memory space formed from multiple regions, +// A SplicedMemory represents a memory space formed from multiple regions, // each of which may override previously regions. For example, in the following // core, the program text was loaded at 0x400000: // Start End Page Offset @@ -29,7 +29,7 @@ var ErrNoThreads = errors.New("no threads found in core file") // // This can be represented in a SplicedMemory by adding the original region, // then putting the RW mapping on top of it. -type splicedMemory struct { +type SplicedMemory struct { readers []readerEntry } @@ -40,7 +40,7 @@ type readerEntry struct { } // Add adds a new region to the SplicedMemory, which may override existing regions. -func (r *splicedMemory) Add(reader proc.MemoryReader, off, length uint64) { +func (r *SplicedMemory) Add(reader proc.MemoryReader, off, length uint64) { if length == 0 { return } @@ -100,7 +100,7 @@ func (r *splicedMemory) Add(reader proc.MemoryReader, off, length uint64) { } // ReadMemory implements MemoryReader.ReadMemory. -func (r *splicedMemory) ReadMemory(buf []byte, addr uint64) (n int, err error) { +func (r *SplicedMemory) ReadMemory(buf []byte, addr uint64) (n int, err error) { started := false for _, entry := range r.readers { if entry.offset+entry.length <= addr { @@ -201,7 +201,7 @@ var openFns = []openFn{readLinuxOrPlatformIndependentCore, readAMD64Minidump} // any of the supported formats. var ErrUnrecognizedFormat = errors.New("unrecognized core format") -// OpenCore will open the core file and return a Process struct. +// OpenCore will open the core file and return a *proc.TargetGroup. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. func OpenCore(corePath, exePath string, debugInfoDirs []string) (*proc.TargetGroup, error) { diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index 4daabd9cbf..3444fdc348 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -134,7 +134,7 @@ func TestSplicedReader(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - mem := &splicedMemory{} + mem := &SplicedMemory{} for _, region := range test.regions { r := bytes.NewReader(region.data) mem.Add(&offsetReaderAt{r, 0}, region.off, region.length) @@ -149,7 +149,7 @@ func TestSplicedReader(t *testing.T) { // Test some ReadMemory errors - mem := &splicedMemory{} + mem := &SplicedMemory{} for _, region := range []region{ {[]byte{0xa1, 0xa2, 0xa3, 0xa4}, 0x1000, 4}, {[]byte{0xb1, 0xb2, 0xb3, 0xb4}, 0x1004, 4}, diff --git a/pkg/proc/core/linux_core.go b/pkg/proc/core/linux_core.go index ab53291dc8..4dd94f648b 100644 --- a/pkg/proc/core/linux_core.go +++ b/pkg/proc/core/linux_core.go @@ -354,7 +354,7 @@ func skipPadding(r io.ReadSeeker, pad int64) error { } func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*note) proc.MemoryReader { - memory := &splicedMemory{} + memory := &SplicedMemory{} // For now, assume all file mappings are to the exe. for _, note := range notes { diff --git a/pkg/proc/core/windows_amd64_minidump.go b/pkg/proc/core/windows_amd64_minidump.go index aeadd11aab..0bcb5e4ecd 100644 --- a/pkg/proc/core/windows_amd64_minidump.go +++ b/pkg/proc/core/windows_amd64_minidump.go @@ -21,7 +21,7 @@ func readAMD64Minidump(minidumpPath, exePath string) (*process, proc.Thread, err return nil, nil, err } - memory := &splicedMemory{} + memory := &SplicedMemory{} for i := range mdmp.MemoryRanges { m := &mdmp.MemoryRanges[i] diff --git a/pkg/proc/variables_fuzz_test.go b/pkg/proc/variables_fuzz_test.go new file mode 100644 index 0000000000..07e28d3736 --- /dev/null +++ b/pkg/proc/variables_fuzz_test.go @@ -0,0 +1,245 @@ +package proc_test + +import ( + "encoding/binary" + "encoding/gob" + "flag" + "os" + "os/exec" + "sort" + "strings" + "testing" + + "github.com/go-delve/delve/pkg/dwarf/op" + "github.com/go-delve/delve/pkg/proc" + "github.com/go-delve/delve/pkg/proc/core" + + protest "github.com/go-delve/delve/pkg/proc/test" +) + +var fuzzEvalExpressionSetup = flag.Bool("fuzzevalexpressionsetup", false, "Performs setup for FuzzEvalExpression") + +const ( + fuzzExecutable = "testdata/fuzzexe" + fuzzCoredump = "testdata/fuzzcoredump" + fuzzInfoPath = "testdata/fuzzinfo" +) + +type fuzzInfo struct { + Loc *proc.Location + Memchunks []memchunk + Regs op.DwarfRegisters + Fuzzbuf []byte +} + +// FuzzEvalExpression fuzzes the variables loader and expression evaluator of Delve. +// To run it, execute the setup first: +// +// go test -run FuzzEvalExpression -fuzzevalexpressionsetup +// +// this will create some required files in testdata, the fuzzer can then be run with: +// +// go test -run NONE -fuzz FuzzEvalExpression -v -fuzzminimizetime=0 +func FuzzEvalExpression(f *testing.F) { + if *fuzzEvalExpressionSetup { + doFuzzEvalExpressionSetup(f) + } + _, err := os.Stat(fuzzExecutable) + if os.IsNotExist(err) { + f.Skip("not setup") + } + bi := proc.NewBinaryInfo("linux", "amd64") + assertNoError(bi.LoadBinaryInfo(fuzzExecutable, 0, nil), f, "LoadBinaryInfo") + fh, err := os.Open(fuzzInfoPath) + assertNoError(err, f, "Open fuzzInfoPath") + defer fh.Close() + var fi fuzzInfo + gob.NewDecoder(fh).Decode(&fi) + fi.Regs.ByteOrder = binary.LittleEndian + fns, err := bi.FindFunction("main.main") + assertNoError(err, f, "FindFunction main.main") + fi.Loc.Fn = fns[0] + f.Add(fi.Fuzzbuf) + f.Fuzz(func(t *testing.T, fuzzbuf []byte) { + t.Log("fuzzbuf len", len(fuzzbuf)) + mem := &core.SplicedMemory{} + + // can't work with shrunk input fuzzbufs provided by the fuzzer, resize it + // so it is always at least the size we want. + lastMemchunk := fi.Memchunks[len(fi.Memchunks)-1] + fuzzbufsz := lastMemchunk.Idx + int(lastMemchunk.Sz) + if fuzzbufsz > len(fuzzbuf) { + newfuzzbuf := make([]byte, fuzzbufsz) + copy(newfuzzbuf, fuzzbuf) + fuzzbuf = newfuzzbuf + } + + end := uint64(0) + + for _, memchunk := range fi.Memchunks { + if end != memchunk.Addr { + mem.Add(&zeroReader{}, end, memchunk.Addr-end) + } + mem.Add(&offsetReader{fuzzbuf[memchunk.Idx:][:memchunk.Sz], memchunk.Addr}, memchunk.Addr, memchunk.Sz) + end = memchunk.Addr + memchunk.Sz + } + + scope := &proc.EvalScope{Location: *fi.Loc, Regs: fi.Regs, Mem: memoryReaderWithFailingWrites{mem}, BinInfo: bi} + for _, tc := range getEvalExpressionTestCases() { + scope.EvalExpression(tc.name, pnormalLoadConfig) + } + }) +} + +func doFuzzEvalExpressionSetup(f *testing.F) { + os.Mkdir("testdata", 0700) + withTestProcess("testvariables2", f, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + exePath := fixture.Path + assertNoError(grp.Continue(), f, "Continue") + fh, err := os.Create(fuzzCoredump) + assertNoError(err, f, "Creating coredump") + var state proc.DumpState + p.Dump(fh, 0, &state) + assertNoError(state.Err, f, "Dump()") + out, err := exec.Command("cp", exePath, fuzzExecutable).CombinedOutput() + f.Log(string(out)) + assertNoError(err, f, "cp") + }) + + // 2. Open the core file and search for the correct goroutine + + cgrp, err := core.OpenCore(fuzzCoredump, fuzzExecutable, nil) + c := cgrp.Selected + assertNoError(err, f, "OpenCore") + gs, _, err := proc.GoroutinesInfo(c, 0, 0) + assertNoError(err, f, "GoroutinesInfo") + found := false + for _, g := range gs { + if strings.Contains(g.UserCurrent().File, "testvariables2") { + c.SwitchGoroutine(g) + found = true + break + } + } + if !found { + f.Errorf("could not find testvariables2 goroutine") + } + + // 3. Run all the test cases on the core file, register which memory addresses are read + + frames, err := c.SelectedGoroutine().Stacktrace(2, 0) + assertNoError(err, f, "Stacktrace") + + mem := c.Memory() + loc, _ := c.CurrentThread().Location() + tmem := &tracingMem{make(map[uint64]int), mem} + + scope := &proc.EvalScope{Location: *loc, Regs: frames[0].Regs, Mem: tmem, BinInfo: c.BinInfo()} + + for _, tc := range getEvalExpressionTestCases() { + scope.EvalExpression(tc.name, pnormalLoadConfig) + } + + // 4. Copy all the memory we read into a buffer to use as fuzz example (if + // we try to use the whole core dump as fuzz example the Go fuzzer crashes) + + memchunks, fuzzbuf := tmem.memoryReadsCondensed() + + fh, err := os.Create(fuzzInfoPath) + assertNoError(err, f, "os.Create") + frames[0].Regs.ByteOrder = nil + loc.Fn = nil + assertNoError(gob.NewEncoder(fh).Encode(fuzzInfo{ + Loc: loc, + Memchunks: memchunks, + Regs: frames[0].Regs, + Fuzzbuf: fuzzbuf, + }), f, "Encode") + assertNoError(fh.Close(), f, "Close") +} + +type tracingMem struct { + read map[uint64]int + mem proc.MemoryReadWriter +} + +func (tmem *tracingMem) ReadMemory(b []byte, n uint64) (int, error) { + if len(b) > tmem.read[n] { + tmem.read[n] = len(b) + } + return tmem.mem.ReadMemory(b, n) +} + +func (tmem *tracingMem) WriteMemory(uint64, []byte) (int, error) { + panic("should not be called") +} + +type memchunk struct { + Addr, Sz uint64 + Idx int +} + +func (tmem *tracingMem) memoryReadsCondensed() ([]memchunk, []byte) { + memoryReads := make([]memchunk, 0, len(tmem.read)) + for addr, sz := range tmem.read { + memoryReads = append(memoryReads, memchunk{addr, uint64(sz), 0}) + } + sort.Slice(memoryReads, func(i, j int) bool { return memoryReads[i].Addr < memoryReads[j].Addr }) + + memoryReadsCondensed := make([]memchunk, 0, len(memoryReads)) + for _, v := range memoryReads { + if len(memoryReadsCondensed) != 0 { + last := &memoryReadsCondensed[len(memoryReadsCondensed)-1] + if last.Addr+last.Sz >= v.Addr { + end := v.Addr + v.Sz + sz2 := end - last.Addr + if sz2 > last.Sz { + last.Sz = sz2 + } + continue + } + } + memoryReadsCondensed = append(memoryReadsCondensed, v) + } + + fuzzbuf := []byte{} + for i := range memoryReadsCondensed { + buf := make([]byte, memoryReadsCondensed[i].Sz) + tmem.mem.ReadMemory(buf, memoryReadsCondensed[i].Addr) + memoryReadsCondensed[i].Idx = len(fuzzbuf) + fuzzbuf = append(fuzzbuf, buf...) + } + + return memoryReadsCondensed, fuzzbuf +} + +type offsetReader struct { + buf []byte + addr uint64 +} + +func (or *offsetReader) ReadMemory(buf []byte, addr uint64) (int, error) { + copy(buf, or.buf[addr-or.addr:][:len(buf)]) + return len(buf), nil +} + +type memoryReaderWithFailingWrites struct { + proc.MemoryReader +} + +func (w memoryReaderWithFailingWrites) WriteMemory(uint64, []byte) (int, error) { + panic("should not be called") +} + +type zeroReader struct{} + +func (*zeroReader) ReadMemory(b []byte, addr uint64) (int, error) { + for i := range b { + b[i] = 0 + } + return len(b), nil +} + +func (*zeroReader) WriteMemory(b []byte, addr uint64) (int, error) { + panic("should not be called") +} diff --git a/pkg/proc/variables_test.go b/pkg/proc/variables_test.go index 2b7095a91a..77b8677a53 100644 --- a/pkg/proc/variables_test.go +++ b/pkg/proc/variables_test.go @@ -50,7 +50,7 @@ func matchStringOrPrefix(output, target string) bool { } } -func assertVariable(t *testing.T, variable *proc.Variable, expected varTest) { +func assertVariable(t testing.TB, variable *proc.Variable, expected varTest) { if expected.preserveName { if variable.Name != expected.name { t.Fatalf("Expected %s got %s\n", expected.name, variable.Name) @@ -511,7 +511,7 @@ func TestComplexSetting(t *testing.T) { }) } -func TestEvalExpression(t *testing.T) { +func getEvalExpressionTestCases() []varTest { testcases := []varTest{ // slice/array/string subscript {"s1[0]", false, "\"one\"", "\"one\"", "string", nil}, @@ -845,6 +845,11 @@ func TestEvalExpression(t *testing.T) { } } + return testcases +} + +func TestEvalExpression(t *testing.T) { + testcases := getEvalExpressionTestCases() protest.AllowRecording(t) withTestProcess("testvariables2", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { assertNoError(grp.Continue(), t, "Continue() returned an error")