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/.teamcity/settings.kts b/.teamcity/settings.kts index 1901c601ca..c57106a48c 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -35,23 +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", - "mac/amd64/1.19", + "windows/arm64/1.20", + "windows/arm64/tip", + + "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..76f5daeeaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,37 @@ 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 + +- 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/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/backend_test_health.md b/Documentation/backend_test_health.md index cca2178257..4f16ef93bb 100644 --- a/Documentation/backend_test_health.md +++ b/Documentation/backend_test_health.md @@ -11,16 +11,16 @@ 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 -* linux/arm64 skipped = 1 - * 1 broken - cgo stacktraces * pie skipped = 2 * 2 upstream issue - https://github.com/golang/go/issues/29322 * windows skipped = 5 * 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/Documentation/cli/README.md b/Documentation/cli/README.md index bb3224d502..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. @@ -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/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/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` 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/faq.md b/Documentation/faq.md index 97a67a293d..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: @@ -103,8 +112,20 @@ 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`. + +### 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/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/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index 9da841160c..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.v -other-argument +dlv test [package] -- -test.run TestSomething -test.v -other-argument See also: 'go help testflag'. diff --git a/README.md b/README.md index 88d8eff5dc..05459b4167 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/_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/_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/_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/_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/_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/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/_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/_fixtures/testvariables2.go b/_fixtures/testvariables2.go index 5ee588e24b..ce9a7c1975 100644 --- a/_fixtures/testvariables2.go +++ b/_fixtures/testvariables2.go @@ -4,6 +4,8 @@ import ( "fmt" "go/constant" "math" + "reflect" + "os" "runtime" "time" "unsafe" @@ -364,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) @@ -371,6 +374,11 @@ func main() { namedA1 := astructName1{12, 45} namedA2 := astructName2{13, 46} + 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() for amb1 := 0; amb1 < 10; amb1++ { @@ -381,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)) + 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/_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/_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/_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/_scripts/make.go b/_scripts/make.go index 0f4a96981a..3b2fc299bf 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,14 +280,31 @@ 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 { 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 } @@ -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_linux.sh b/_scripts/test_linux.sh index 0ae64d6d79..44ca5d7b44 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 @@ -41,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/_scripts/test_windows.ps1 b/_scripts/test_windows.ps1 index 782542173e..6c2b595c0e 100644 --- a/_scripts/test_windows.ps1 +++ b/_scripts/test_windows.ps1 @@ -1,50 +1,72 @@ 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 ($binDir -eq "") { + $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" -ErrorAction Stop + Expand-Archive -Force -LiteralPath "$env:TEMP\$name.zip" -DestinationPath "$binDir\llvm-mingw\" + } + $env:PATH = "$binDir\llvm-mingw\$name\bin;$env:PATH" +} else { + Throw "Unsupported architecture: $arch" +} # 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" -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 } } 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 = "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 @@ -52,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 @@ -64,25 +86,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/cmds/commands.go b/cmd/dlv/cmds/commands.go index c52c7784bb..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. @@ -278,7 +280,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 TestSomething -test.v -other-argument See also: 'go help testflag'.`, Run: testCmd, @@ -327,6 +329,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. @@ -364,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) } @@ -728,7 +738,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 }() @@ -750,11 +762,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) @@ -762,8 +770,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 "." } @@ -977,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/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 562ade5e7f..e1c3006528 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -204,26 +204,25 @@ 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 // 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) { +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") @@ -232,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!" @@ -256,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) @@ -297,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) @@ -341,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) @@ -424,6 +419,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) @@ -431,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") @@ -457,9 +454,10 @@ 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", "-")) + 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") @@ -467,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") @@ -668,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() @@ -701,8 +697,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() @@ -711,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() @@ -777,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) @@ -831,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) @@ -899,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) @@ -961,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{} @@ -996,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() @@ -1020,14 +1017,32 @@ func TestTrace(t *testing.T) { cmd.Wait() } -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") +func TestTrace2(t *testing.T) { + 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(t.TempDir(), "__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)) } + assertNoError(cmd.Wait(), t, "cmd.Wait()") +} - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) +func TestTraceMultipleGoroutines(t *testing.T) { + 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 @@ -1036,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() @@ -1066,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") @@ -1100,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() @@ -1127,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() @@ -1167,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() @@ -1207,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) @@ -1234,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() @@ -1251,28 +1261,39 @@ func TestTraceEBPF2(t *testing.T) { } func TestDlvTestChdir(t *testing.T) { - dlvbin, tmpdir := getDlvBin(t) - defer os.RemoveAll(tmpdir) + dlvbin := getDlvBin(t) 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) { - 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/go.mod b/go.mod index 922b3bc882..2be8c4d5b5 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ 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/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 @@ -19,7 +19,6 @@ 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/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + golang.org/x/tools v0.1.12 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index a4d4497902..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,8 @@ 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= 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= @@ -90,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= @@ -218,12 +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/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= @@ -272,7 +269,7 @@ 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= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -281,7 +278,7 @@ 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= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -296,26 +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-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +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/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= @@ -338,8 +330,8 @@ 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= 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= @@ -375,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/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/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/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/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 -) diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go index 147ca34f15..e789239c4b 100644 --- a/pkg/dwarf/frame/parser.go +++ b/pkg/dwarf/frame/parser.go @@ -9,7 +9,8 @@ import ( "fmt" "io" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) type parsefunc func(*parseContext) parsefunc @@ -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) } @@ -153,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" { @@ -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 } @@ -240,23 +241,23 @@ func (ctx *parseContext) readEncodedPtr(addr uint64, buf util.ByteReaderWithLen, 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, _ = util.DecodeULEB128(buf) + 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, _ := 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/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 73% rename from pkg/dwarf/util/buf.go rename to pkg/dwarf/godwarf/buf.go index 12899f8aa3..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 } -// Some parts of DWARF 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") @@ -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/dwarf/godwarf/tree.go b/pkg/dwarf/godwarf/tree.go index 12008d9e60..b9be92ba1f 100644 --- a/pkg/dwarf/godwarf/tree.go +++ b/pkg/dwarf/godwarf/tree.go @@ -11,24 +11,34 @@ 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 } -// 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 +53,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 +62,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 +107,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 +250,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) } } 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/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..030689a3a1 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -6,7 +6,8 @@ import ( "path" "strings" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) // DebugLinePrologue prologue of .debug_line data. @@ -151,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) @@ -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() @@ -185,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) @@ -227,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) @@ -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 ( @@ -298,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 867d9b7b3b..b1f22e5452 100644 --- a/pkg/dwarf/line/parse_util.go +++ b/pkg/dwarf/line/parse_util.go @@ -5,7 +5,8 @@ import ( "encoding/binary" "errors" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) const ( @@ -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,13 +151,13 @@ 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) + 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 691b57a96d..6c31fb4610 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -7,7 +7,8 @@ import ( "fmt" "io" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) type Location struct { @@ -107,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, @@ -260,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 } } @@ -271,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 @@ -300,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}) } } @@ -409,7 +411,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 +434,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 +446,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 +475,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) } @@ -502,7 +504,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) } @@ -510,7 +512,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 +530,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..0effee88d7 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -10,10 +10,10 @@ import ( "io" "io/ioutil" "os" - "runtime" "testing" - "github.com/go-delve/delve/pkg/dwarf/util" + pdwarf "github.com/go-delve/delve/pkg/dwarf" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) func slurpGzip(path string) ([]byte, error) { @@ -35,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) @@ -136,9 +133,9 @@ 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) + pdwarf.WriteUint(instr, binary.LittleEndian, ptrSize, addr) } write_DW_LNS_copy := func() { @@ -147,17 +144,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..666f42f5e8 100644 --- a/pkg/dwarf/loclist/dwarf5_loclist.go +++ b/pkg/dwarf/loclist/dwarf5_loclist.go @@ -5,8 +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/util" + "github.com/go-delve/delve/pkg/dwarf/leb128" ) // Dwarf5Reader parses and presents DWARF loclist information for DWARF version 5 and later. @@ -23,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:] @@ -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 @@ -160,19 +161,19 @@ 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) - length, _ := util.DecodeULEB128(it.buf) + 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 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..a3269f2012 100644 --- a/pkg/dwarf/op/op.go +++ b/pkg/dwarf/op/op.go @@ -7,7 +7,8 @@ import ( "fmt" "io" - "github.com/go-delve/delve/pkg/dwarf/util" + "github.com/go-delve/delve/pkg/dwarf/leb128" + "github.com/go-delve/delve/pkg/dwarf" ) // Opcode represent a DWARF stack program instruction. @@ -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 @@ -212,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 } @@ -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, @@ -296,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") } @@ -322,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") } @@ -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 { @@ -560,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/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 diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/parseutil.go similarity index 59% rename from pkg/dwarf/util/util.go rename to pkg/dwarf/parseutil.go index 4e199cb26d..d8ed2b0e78 100644 --- a/pkg/dwarf/util/util.go +++ b/pkg/dwarf/parseutil.go @@ -1,4 +1,4 @@ -package util +package dwarf import ( "bytes" @@ -8,127 +8,8 @@ import ( "io" ) -// ByteReaderWithLen is a io.ByteReader with a Len method. This interface is -// satisified by both bytes.Buffer and bytes.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. -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 -} - -// DecodeSLEB128 decodes a signed Little Endian Base 128 -// represented number. -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 -} - -// EncodeULEB128 encodes x to the unsigned Little Endian Base 128 format -// into out. -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 - } - } -} - -// EncodeSLEB128 encodes x to the signed Little Endian Base 128 format -// into out. -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 - } - } -} - -// 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/parseutil_test.go b/pkg/dwarf/parseutil_test.go new file mode 100644 index 0000000000..c8ea66ca99 --- /dev/null +++ b/pkg/dwarf/parseutil_test.go @@ -0,0 +1,17 @@ +package dwarf_test + +import ( + "bytes" + "testing" + + "github.com/go-delve/delve/pkg/dwarf" +) + +func TestReadString(t *testing.T) { + bstr := bytes.NewBuffer([]byte{'h', 'i', 0x0, 0xFF, 0xCC}) + str, _ := dwarf.ReadString(bstr) + + if str != "hi" { + t.Fatalf("String was not parsed correctly %#v", str) + } +} 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 diff --git a/pkg/dwarf/util/util_test.go b/pkg/dwarf/util/util_test.go deleted file mode 100644 index 63bb4190bf..0000000000 --- a/pkg/dwarf/util/util_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package util - -import ( - "bytes" - "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) - - if str != "hi" { - t.Fatalf("String was not parsed correctly %#v", str) - } -} 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/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/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/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/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/arm64_arch.go b/pkg/proc/arm64_arch.go index ddd9797eef..01272e6954 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/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/bininfo.go b/pkg/proc/bininfo.go index 894e57c45a..950bdda1f8 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -23,18 +23,17 @@ 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" "github.com/hashicorp/golang-lru/simplelru" - "github.com/sirupsen/logrus" ) const ( @@ -94,7 +93,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 @@ -110,7 +110,7 @@ type BinaryInfo struct { // Go 1.17 register ABI is enabled. regabi bool - logger *logrus.Entry + logger logflags.Logger } var ( @@ -700,7 +700,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 @@ -943,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() @@ -1055,7 +1055,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) @@ -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 != "" { @@ -1308,7 +1311,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:])) @@ -1516,7 +1519,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)) @@ -1534,7 +1542,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 { @@ -1577,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) @@ -1642,38 +1650,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 } } @@ -1786,8 +1808,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 { @@ -1800,9 +1821,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) @@ -1858,8 +1898,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 @@ -1988,7 +2028,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() @@ -2028,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()) } @@ -2055,7 +2091,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 { @@ -2112,7 +2148,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 @@ -2176,7 +2212,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 { @@ -2474,7 +2510,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. @@ -2525,7 +2561,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/core/core.go b/pkg/proc/core/core.go index 561b0096f7..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,10 +201,10 @@ 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.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 @@ -396,7 +400,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 @@ -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 29a40108bb..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}, @@ -198,14 +198,10 @@ 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, 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 @@ -255,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 { @@ -328,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 { @@ -411,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") @@ -456,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/core/linux_core.go b/pkg/proc/core/linux_core.go index 25ebbbfa89..4dd94f648b 100644 --- a/pkg/proc/core/linux_core.go +++ b/pkg/proc/core/linux_core.go @@ -340,21 +340,21 @@ 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 } 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/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/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/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/eval.go b/pkg/proc/eval.go index f01c4d2ca7..1afa229488 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)) } @@ -1556,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") } @@ -2084,7 +2106,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/fncall.go b/pkg/proc/fncall.go index 8f00ab6bf7..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 { @@ -615,7 +619,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 @@ -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. @@ -1248,9 +1245,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 +1278,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{ diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index f898eb4e73..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 } @@ -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 !strings.HasPrefix(v, "DYLD_INSERT_LIBRARIES") { + env = append(env, v) + } + } + process.Env = env } if err = process.Start(); err != nil { @@ -556,13 +567,20 @@ 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. + err := tcsetpgrp(os.Stdin.Fd(), p.conn.pid) + if err != nil { + 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 @@ -570,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 } @@ -615,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 @@ -655,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 @@ -706,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) { @@ -803,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} } @@ -997,7 +1010,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 } @@ -1289,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 @@ -1873,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 { @@ -1949,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..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") @@ -81,7 +80,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/gdbserial/gdbserver_unix.go b/pkg/proc/gdbserial/gdbserver_unix.go index c5151bf442..4ba87ebf87 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,11 @@ func sysProcAttr(foreground bool) *syscall.SysProcAttr { func foregroundSignalsIgnore() { signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) } + +func tcsetpgrp(fd uintptr, pid int) error { + pgid, _ := syscall.Getpgid(pid) + if pid == pgid { + return unix.IoctlSetPointerInt(int(fd), unix.TIOCSPGRP, pid) + } + return nil +} 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 +} diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index e133b12bd2..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.Target, 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 { @@ -279,12 +288,12 @@ 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 } - t, err := Replay(tracedir, quiet, true, debugInfoDirs) + t, err := Replay(tracedir, quiet, true, debugInfoDirs, 0) return t, tracedir, 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/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/interface.go b/pkg/proc/interface.go index 2f845211bf..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. @@ -64,7 +72,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/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 2a0bd5c8a4..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,49 +285,53 @@ 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: // - 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"), - 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. @@ -281,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() @@ -351,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 f4fb6334c8..40581e5ae9 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() {} @@ -51,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 @@ -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) } @@ -118,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 @@ -143,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 } @@ -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) } } @@ -219,14 +227,30 @@ func findExecutable(path string, pid int) string { return path } -func (dbp *nativeProcess) trapWait(pid int) (*nativeThread, error) { - return dbp.trapWaitInternal(pid, false) +func trapWait(procgrp *processGroup, pid int) (*nativeThread, error) { + return procgrp.procs[0].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,31 +373,110 @@ 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 } // 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} } - // 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 + } + + 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 trapthread, nil + + 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 +499,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/proc_linux.go b/pkg/proc/native/proc_linux.go index cfcc2878b1..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) } @@ -459,7 +529,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 @@ -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 5c5ffcf25f..705fe6e500 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" @@ -24,11 +23,8 @@ 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) { - argv0Go, err := filepath.Abs(cmd[0]) - if err != nil { - return nil, err - } +func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.TargetGroup, error) { + argv0Go := cmd[0] env := proc.DisableAsyncPreemptEnv() @@ -144,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 @@ -431,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() { @@ -478,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/ptrace_freebsd.go b/pkg/proc/native/ptrace_freebsd.go index fe7c5c371a..6ccffe6b84 100644 --- a/pkg/proc/native/ptrace_freebsd.go +++ b/pkg/proc/native/ptrace_freebsd.go @@ -9,6 +9,8 @@ package native import "C" import ( + "runtime" + "syscall" "unsafe" sys "golang.org/x/sys/unix" @@ -59,10 +61,44 @@ 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)) } + +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/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 } 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..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 } @@ -120,3 +123,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..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 { @@ -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_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 21d241e4ee..61df0f78f3 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" @@ -87,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") @@ -118,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 { @@ -173,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) @@ -190,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) @@ -271,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) @@ -293,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() @@ -312,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") @@ -333,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") @@ -350,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") @@ -359,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) @@ -433,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) @@ -455,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") @@ -482,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") } @@ -585,16 +585,15 @@ func TestNextGeneral(t *testing.T) { } func TestNextConcurrent(t *testing.T) { - skipOn(t, "broken", "freebsd") testcases := []nextTest{ {8, 9}, {9, 10}, {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) @@ -621,7 +620,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}, @@ -629,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) @@ -645,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") @@ -662,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") @@ -707,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 { @@ -720,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) @@ -729,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") } @@ -737,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) } @@ -765,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) } @@ -784,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 { @@ -798,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) } @@ -843,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()") }) } @@ -854,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) @@ -882,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()") @@ -907,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()") @@ -924,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) { @@ -978,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") @@ -1031,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 { @@ -1046,7 +1044,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())) @@ -1057,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()") @@ -1075,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 @@ -1086,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 } @@ -1131,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) @@ -1141,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()") }) } @@ -1236,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) @@ -1290,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()) @@ -1345,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()") @@ -1373,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 { @@ -1391,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") @@ -1419,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") @@ -1441,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) }) @@ -1451,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") }) } @@ -1460,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,13 +1469,12 @@ 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) { + 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 } @@ -1503,11 +1500,10 @@ func TestBreakpointCounts(t *testing.T) { } func TestHardcodedBreakpointCounts(t *testing.T) { - skipOn(t, "broken", "freebsd") - 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 } @@ -1543,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") @@ -1560,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 } @@ -1616,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") @@ -1630,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") @@ -1640,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() @@ -1654,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") } @@ -1674,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") }) } @@ -1691,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) @@ -1702,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() @@ -1715,9 +1711,8 @@ 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) { + 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, @@ -1725,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") @@ -1737,9 +1732,8 @@ 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) { + 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, @@ -1747,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()") } @@ -1762,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) @@ -1779,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) @@ -1794,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) } @@ -1803,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 @@ -1811,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) @@ -1820,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 @@ -1834,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) @@ -1843,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) } @@ -1853,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) @@ -1863,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 { @@ -1888,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 { @@ -1912,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 { @@ -1940,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()") } @@ -1954,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) @@ -1966,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 { @@ -1990,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()") @@ -2019,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) @@ -2036,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) @@ -2052,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) @@ -2077,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 { @@ -2100,15 +2093,14 @@ 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) { + 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 @@ -2142,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) @@ -2151,16 +2143,15 @@ 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) { + 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 @@ -2190,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) @@ -2222,7 +2213,7 @@ func TestUnsupportedArch(t *testing.T) { } defer os.Remove(outfile) - var p *proc.Target + var p *proc.TargetGroup switch testBackend { case "native": @@ -2250,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 { @@ -2285,19 +2276,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) { @@ -2328,71 +2311,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) { @@ -2417,27 +2355,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") } } @@ -2445,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() @@ -2480,12 +2399,12 @@ 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) { + 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()") @@ -2534,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 { @@ -2544,9 +2463,8 @@ 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) { + 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 { @@ -2560,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 @@ -2588,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 { @@ -2618,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") @@ -2635,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") @@ -2652,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()") @@ -2679,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 { @@ -2711,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") } @@ -2726,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 @@ -2747,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,") @@ -2777,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" { @@ -2798,15 +2715,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) { @@ -2816,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 { @@ -2838,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 { @@ -2855,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 } @@ -2871,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}) @@ -2901,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 @@ -2916,20 +2827,11 @@ 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)) } }) } -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") @@ -2965,7 +2867,7 @@ func TestAttachDetach(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -2988,7 +2890,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") @@ -3009,8 +2911,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" { @@ -3024,8 +2926,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") }) @@ -3034,8 +2936,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) @@ -3061,24 +2963,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 { @@ -3100,8 +3002,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) @@ -3116,8 +3018,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 } @@ -3136,17 +3038,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 @@ -3193,8 +3095,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) @@ -3267,7 +3169,7 @@ func TestAttachStripped(t *testing.T) { } } - var p *proc.Target + var p *proc.TargetGroup var err error switch testBackend { @@ -3297,7 +3199,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{ @@ -3305,13 +3207,15 @@ 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,") }) } 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 +3225,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 +3325,7 @@ func TestCgoStacktrace(t *testing.T) { } skipOn(t, "broken - cgo stacktraces", "386") - skipOn(t, "broken - cgo stacktraces", "linux", "arm64") + skipOn(t, "broken - cgo stacktraces", "windows", "arm64") protest.MustHaveCgo(t) // Tests that: @@ -3427,20 +3339,22 @@ 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 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 { - assertNoError(p.Continue(), t, fmt.Sprintf("Continue at iteration step %d", itidx)) + t.Logf("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)) @@ -3456,7 +3370,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 +3388,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() @@ -3521,7 +3434,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 @@ -3540,10 +3453,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) @@ -3562,9 +3475,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") @@ -3572,7 +3485,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 { @@ -3595,9 +3508,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:]...) @@ -3615,17 +3528,17 @@ 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) 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) } }) } @@ -3660,41 +3573,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") @@ -3719,16 +3632,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") { @@ -3748,8 +3661,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) @@ -3768,8 +3680,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()") @@ -3798,7 +3709,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) @@ -3828,9 +3739,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 } @@ -3840,7 +3750,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) @@ -3866,14 +3776,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, @@ -3887,14 +3797,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, @@ -3904,7 +3814,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 { @@ -3917,7 +3827,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") @@ -3944,7 +3854,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") @@ -3984,6 +3894,9 @@ func TestInlineStep(t *testing.T) { {contContinue, 18}, {contStep, 6}, {contStep, 7}, + {contStep, 24}, + {contStep, 25}, + {contStep, 7}, {contStep, 18}, {contStep, 19}, }) @@ -4029,11 +3942,16 @@ 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") } - withTestProcessArgs("testinline", t, ".", []string{}, protest.EnableInlining|protest.EnableOptimization, func(p *proc.Target, fixture protest.Fixture) { + 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, grp *proc.TargetGroup, fixture protest.Fixture) { var found bool for _, fn := range p.BinInfo().Functions { if strings.Contains(fn.Name, "inlineThis") { @@ -4053,7 +3971,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) @@ -4081,7 +3999,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) @@ -4100,8 +4018,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) @@ -4133,9 +4051,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") @@ -4145,9 +4063,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) @@ -4173,10 +4091,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) @@ -4221,7 +4139,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") @@ -4229,7 +4147,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") @@ -4241,17 +4159,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") @@ -4313,10 +4231,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()") }) } @@ -4342,8 +4260,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) @@ -4383,8 +4301,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),") @@ -4403,8 +4320,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) @@ -4417,9 +4334,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 @@ -4446,9 +4363,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() { @@ -4483,8 +4400,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 { @@ -4505,12 +4422,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) @@ -4527,7 +4444,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) @@ -4556,9 +4473,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) @@ -4613,10 +4530,8 @@ 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) + 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) @@ -4673,9 +4588,9 @@ 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") + // Tests that recursive types involving C qualifiers and typedefs are parsed correctly + withTestProcess("issue1601", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue") evalVariable(p, t, "C.globalq") }) } @@ -4683,7 +4598,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, @@ -4691,7 +4606,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, "") }) } @@ -4703,8 +4618,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) @@ -4717,14 +4632,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") }) } @@ -4735,15 +4650,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) @@ -4752,8 +4667,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) @@ -4767,13 +4682,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 { @@ -4786,7 +4701,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) } } @@ -4805,17 +4720,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) @@ -4836,14 +4751,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) } @@ -5003,8 +4918,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) @@ -5067,10 +4981,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() @@ -5083,14 +4997,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) } @@ -5099,8 +5013,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") @@ -5132,10 +5045,9 @@ 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()") + withTestProcess("issue2113", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.Continue(), t, "Continue()") logState := func() { g := p.SelectedGoroutine() @@ -5185,7 +5097,7 @@ func TestStepOutPreservesGoroutine(t *testing.T) { logState() - err = p.StepOut() + err = grp.StepOut() if err != nil { _, isexited := err.(proc.ErrProcessExited) if !isexited { @@ -5293,7 +5205,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) { @@ -5372,8 +5284,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") @@ -5399,7 +5311,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") @@ -5431,7 +5343,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) @@ -5471,8 +5383,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) @@ -5498,11 +5410,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()) @@ -5511,7 +5423,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()) { @@ -5520,25 +5432,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 }) } @@ -5549,9 +5461,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") @@ -5560,7 +5472,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 } @@ -5587,8 +5499,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() @@ -5636,9 +5547,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 @@ -5671,11 +5582,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()) @@ -5685,7 +5596,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 } @@ -5717,10 +5628,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) @@ -5747,11 +5658,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()) @@ -5760,20 +5671,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) @@ -5795,9 +5706,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") @@ -5812,9 +5723,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) @@ -5862,18 +5773,19 @@ 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.Replace(f, "\\", "/", -1) + 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 } + t.Logf("third continue") assertNoError(err, t, "Continue()") bp := p.CurrentThread().Breakpoint() if bp != nil { @@ -5888,10 +5800,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") }) } @@ -5905,11 +5817,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) @@ -5999,3 +5909,125 @@ 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, grp *proc.TargetGroup, fixture protest.Fixture) { + setFunctionBreakpoint(p, t, "main.main") + assertNoError(grp.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") + } + }) +} + +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 a477281e62..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 } @@ -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/stack.go b/pkg/proc/stack.go index 9d847f4980..3ad7f2c4e9 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, @@ -616,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 f556fee834..e1fafda0cd 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 { @@ -212,6 +193,7 @@ func NewTarget(p ProcessInternal, pid int, currentThread Thread, cfg NewTargetCo g, _ := GetG(currentThread) t.selectedGoroutine = g + t.Breakpoints().Logical = grp.LogicalBreakpoints t.createUnrecoveredPanicBreakpoint() t.createFatalThrowBreakpoint() t.createPluginOpenBreakpoint() @@ -219,7 +201,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) } @@ -268,7 +250,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. @@ -339,7 +321,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_exec.go b/pkg/proc/target_exec.go index 1c894e8c57..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) { @@ -1137,7 +1277,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/target_group.go b/pkg/proc/target_group.go index 07a464625a..66ed9e735c 100644 --- a/pkg/proc/target_group.go +++ b/pkg/proc/target_group.go @@ -2,76 +2,126 @@ package proc import ( "bytes" + "errors" "fmt" "strings" + + "github.com/go-delve/delve/pkg/logflags" ) -// 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 // 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 + } + 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 +145,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 +206,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 @@ -211,14 +271,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) } @@ -274,6 +334,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 +375,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/test/support.go b/pkg/proc/test/support.go index bd5086738f..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) @@ -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/pkg/proc/types.go b/pkg/proc/types.go index e9c72f0c46..1940b26a5a 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 @@ -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/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.go b/pkg/proc/variables.go index 9b6a70d143..a41dac86af 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 @@ -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 @@ -874,8 +877,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 @@ -1185,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) @@ -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) @@ -1392,7 +1428,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 { @@ -1620,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 @@ -2293,7 +2333,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 "" } @@ -2555,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_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 22cc223e50..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) @@ -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) { @@ -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}, @@ -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}, @@ -820,6 +821,18 @@ 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}, + + // 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()) @@ -832,9 +845,14 @@ func TestEvalExpression(t *testing.T) { } } + return testcases +} + +func TestEvalExpression(t *testing.T) { + testcases := getEvalExpressionTestCases() 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) @@ -863,8 +881,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() @@ -890,8 +908,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) @@ -932,8 +950,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) @@ -970,8 +988,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)) @@ -1042,11 +1060,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}, } @@ -1061,15 +1074,11 @@ 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) - 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) @@ -1101,8 +1110,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 +1121,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)) @@ -1206,6 +1215,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{ @@ -1256,9 +1269,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 { @@ -1299,7 +1311,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") { @@ -1386,8 +1398,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 @@ -1449,9 +1461,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) @@ -1461,7 +1473,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) @@ -1491,15 +1503,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) @@ -1530,8 +1542,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" { @@ -1563,23 +1575,23 @@ 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}, }, } - 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 { @@ -1597,3 +1609,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, grp *proc.TargetGroup, fixture protest.Fixture) { + assertNoError(grp.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) + } + }) +} 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/command.go b/pkg/terminal/command.go index 86b8779f55..5d92d9f9f3 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. @@ -475,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. @@ -1753,14 +1754,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]?") + 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 +3225,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/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 1411956da1..ee9c605154 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) @@ -456,9 +457,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 +518,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) { @@ -564,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) @@ -1342,3 +1324,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) + } + }) +} diff --git a/pkg/terminal/config.go b/pkg/terminal/config.go index a005c51aa6..3584f9b0c4 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.updateConfig() } return nil } diff --git a/pkg/terminal/out.go b/pkg/terminal/out.go index b8bb95bb1b..79dd73f9ae 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 @@ -99,6 +100,8 @@ type pagingWriter struct { pager string lastnl bool cancel func() + + lines, columns int } type pagingWriterMode uint8 @@ -107,9 +110,6 @@ const ( pagingWriterNormal pagingWriterMode = iota pagingWriterMaybe pagingWriterPaging - - pagingWriterMaxLines = 30 - pagingWriterColsPerLine = 100 ) func (w *pagingWriter) Write(p []byte) (nn int, err error) { @@ -140,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' @@ -201,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) +} diff --git a/pkg/terminal/terminal.go b/pkg/terminal/terminal.go index 5f7b9701ea..4b62625ed3 100644 --- a/pkg/terminal/terminal.go +++ b/pkg/terminal/terminal.go @@ -108,30 +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 - 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.updateConfig() + if client != nil { lcfg := t.loadConfig() client.SetReturnValuesLoadConfig(&lcfg) @@ -141,6 +121,47 @@ 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 + } + + 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) + t.stdout.colorEscapes[colorize.TabStyle] = wd(conf.SourceListTabColor, ansiBrBlack) + 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) updateTab() { + t.stdout.altTabString = t.conf.Tab +} + func (t *Term) SetTraceNonInteractive() { t.traceNonInteractive = true } @@ -292,7 +313,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.") @@ -411,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 @@ -448,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 } @@ -474,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 } @@ -484,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 } diff --git a/pkg/version/version.go b/pkg/version/version.go index d81b9fd12b..96e65b6c61 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: "1", Metadata: "", Build: "$Id$", } ) diff --git a/service/dap/server.go b/service/dap/server.go index d8b67a2fd8..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{} @@ -405,7 +403,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. @@ -1411,7 +1409,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} } } @@ -1775,7 +1773,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: @@ -2694,7 +2692,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) } @@ -2743,7 +2741,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 5615d42822..8b775487c4 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,11 +3679,8 @@ 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") + 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) @@ -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 @@ -5472,10 +5455,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}) @@ -5706,11 +5693,8 @@ 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") + 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 @@ -6634,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}) @@ -6758,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" diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index cabba03bfb..c58eeb2b91 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" @@ -30,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 ( @@ -69,7 +69,7 @@ type Debugger struct { targetMutex sync.Mutex target *proc.TargetGroup - log *logrus.Entry + log logflags.Logger running bool runningMutex sync.Mutex @@ -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 @@ -161,30 +163,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, d.config.RrOnProcessPid) 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 +192,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 +202,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,10 +242,12 @@ 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 { +func (d *Debugger) Launch(processArgs []string, wd string) (*proc.TargetGroup, error) { + fullpath, err := verifyBinaryFormat(processArgs[0]) + if err != nil { return nil, err } + processArgs[0] = fullpath launchFlags := proc.LaunchFlags(0) if d.config.Foreground { @@ -283,7 +282,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 @@ -291,7 +290,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) @@ -330,17 +329,17 @@ 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 } - 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'. -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) @@ -358,7 +357,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 } @@ -480,7 +479,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 { @@ -508,19 +507,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 } @@ -769,14 +769,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 @@ -784,6 +786,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 { @@ -960,10 +974,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 @@ -1309,7 +1319,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) @@ -1556,8 +1566,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() @@ -2129,7 +2139,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 } @@ -2259,34 +2269,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 +2306,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) + } +} 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() 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 } diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index a362ce5282..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) @@ -985,9 +981,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 61617a8f8e..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" @@ -996,7 +997,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 +1306,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 +1335,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) @@ -1496,9 +1489,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}) @@ -2011,19 +2001,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) } @@ -2150,32 +2127,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) { @@ -2961,3 +2912,82 @@ func TestPluginSuspendedBreakpoint(t *testing.T) { cont("Continue 4", "plugin2.go", 9) }) } + +// 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) { + 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) + } + }) +} + +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") + } + }) +} 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/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/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..ac1d2a3ab4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,13 +15,13 @@ 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 ## 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 @@ -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