cmd,proc,terminal,debugger: Support default file descriptor redirects
Adds features to support default file descriptor redirects for the target process: 1. A new command line flag '--redirect' and '-r' are added to specify file redirects for the target process 2. New syntax is added to the 'restart' command to specify file redirects. 3. Interactive instances will check if stdin/stdout and stderr are terminals and print a helpful error message if they aren't.
This commit is contained in:
parent
f90134eb4d
commit
7555d1c063
@ -414,15 +414,21 @@ For recorded targets the command takes the following forms:
|
||||
|
||||
restart resets ot the start of the recording
|
||||
restart [checkpoint] resets the recording to the given checkpoint
|
||||
restart -r [newargv...] re-records the target process
|
||||
restart -r [newargv...] [redirects...] re-records the target process
|
||||
|
||||
For live targets the command takes the following forms:
|
||||
|
||||
restart [newargv...] restarts the process
|
||||
restart [newargv...] [redirects...] restarts the process
|
||||
|
||||
If newargv is omitted the process is restarted (or re-recorded) with the same argument vector.
|
||||
If -noargs is specified instead, the argument vector is cleared.
|
||||
|
||||
A list of file redirections can be specified after the new argument list to override the redirections defined using the '--redirect' command line option. A syntax similar to Unix shells is used:
|
||||
|
||||
<input.txt redirects the standard input of the target process from input.txt
|
||||
>output.txt redirects the standard output of the target process to output.txt
|
||||
2>error.txt redirects the standard error of the target process to error.txt
|
||||
|
||||
|
||||
Aliases: r
|
||||
|
||||
|
@ -51,7 +51,7 @@ threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go
|
||||
types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes)
|
||||
process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ProcessPid)
|
||||
recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded)
|
||||
restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart)
|
||||
restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild, NewRedirects) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart)
|
||||
set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set)
|
||||
stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace)
|
||||
state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State)
|
||||
|
@ -20,6 +20,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -31,6 +32,7 @@ Pass flags to the program you are debugging using `--`, for example:
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -26,6 +26,7 @@ dlv attach pid [executable]
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -37,6 +38,7 @@ dlv attach pid [executable]
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -19,6 +19,7 @@ are:
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -30,6 +31,7 @@ are:
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -15,6 +15,7 @@ dlv connect addr
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -26,6 +27,7 @@ dlv connect addr
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -21,6 +21,7 @@ dlv core <executable> <core>
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -32,6 +33,7 @@ dlv core <executable> <core>
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -22,6 +22,7 @@ dlv dap
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -33,6 +34,7 @@ dlv dap
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -28,6 +28,7 @@ dlv debug [package]
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -39,6 +40,7 @@ dlv debug [package]
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -28,6 +28,7 @@ dlv exec <path/to/binary>
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -39,6 +40,7 @@ dlv exec <path/to/binary>
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -34,6 +34,7 @@ and dap modes.
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -45,6 +46,7 @@ and dap modes.
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
43
Documentation/usage/dlv_redirect.md
Normal file
43
Documentation/usage/dlv_redirect.md
Normal file
@ -0,0 +1,43 @@
|
||||
## dlv redirect
|
||||
|
||||
Help about file redirection.
|
||||
|
||||
### Synopsis
|
||||
|
||||
|
||||
The standard file descriptors of the target process can be controlled using the '-r' and '--tty' arguments.
|
||||
|
||||
The --tty argument allows redirecting all standard descriptors to a terminal, specified as an argument to --tty.
|
||||
|
||||
The syntax for '-r' argument is:
|
||||
|
||||
-r [source:]destination
|
||||
|
||||
Where source is one of 'stdin', 'stdout' or 'stderr' and destination is the path to a file. If the source is omitted stdin is used implicitly.
|
||||
|
||||
File redirects can also be changed using the 'restart' command.
|
||||
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
|
||||
--headless Run debug server only, in headless mode.
|
||||
--init string Init file, executed by the terminal client.
|
||||
-l, --listen string Debugging server listen address. (default "127.0.0.1:0")
|
||||
--log Enable debugging server logging.
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
* [dlv](dlv.md) - Delve is a debugger for the Go programming language.
|
||||
|
@ -19,6 +19,7 @@ dlv replay [trace directory]
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -30,6 +31,7 @@ dlv replay [trace directory]
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -15,6 +15,7 @@ dlv run
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -26,6 +27,7 @@ dlv run
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -26,6 +26,7 @@ dlv test [package]
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -37,6 +38,7 @@ dlv test [package]
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -33,6 +33,7 @@ dlv trace [package] regexp
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -44,6 +45,7 @@ dlv trace [package] regexp
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
@ -15,6 +15,7 @@ dlv version
|
||||
|
||||
```
|
||||
--accept-multiclient Allows a headless server to accept multiple client connections.
|
||||
--allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
|
||||
--api-version int Selects API version when headless. New clients should use v2. Can be reset via RPCServer.SetApiVersion. See Documentation/api/json-rpc/README.md. (default 1)
|
||||
--backend string Backend selection (see 'dlv help backend'). (default "default")
|
||||
--build-flags string Build flags, to be passed to the compiler.
|
||||
@ -26,6 +27,7 @@ dlv version
|
||||
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log').
|
||||
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
|
||||
--only-same-user Only connections from the same user that started this instance of Delve are allowed to connect. (default true)
|
||||
-r, --redirect stringArray Specifies redirect rules for target process (see 'dlv help redirect')
|
||||
--wd string Working directory for running the program.
|
||||
```
|
||||
|
||||
|
1
_fixtures/redirect-input.txt
Normal file
1
_fixtures/redirect-input.txt
Normal file
@ -0,0 +1 @@
|
||||
Redirect test
|
13
_fixtures/redirect.go
Normal file
13
_fixtures/redirect.go
Normal file
@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
buf, _ := ioutil.ReadAll(os.Stdin)
|
||||
fmt.Fprintf(os.Stdout, "%s %v\n", buf, time.Now())
|
||||
}
|
59
cmd/dlv/cmds/cmds_test.go
Normal file
59
cmd/dlv/cmds/cmds_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package cmds
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseRedirects(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in []string
|
||||
tgt [3]string
|
||||
tgterr string
|
||||
}{
|
||||
{
|
||||
[]string{"one.txt"},
|
||||
[3]string{"one.txt", "", ""},
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]string{"one.txt", "two.txt"},
|
||||
[3]string{},
|
||||
"redirect error: stdin redirected twice",
|
||||
},
|
||||
{
|
||||
[]string{"stdout:one.txt"},
|
||||
[3]string{"", "one.txt", ""},
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]string{"stdout:one.txt", "stderr:two.txt", "stdin:three.txt"},
|
||||
[3]string{"three.txt", "one.txt", "two.txt"},
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]string{"stdout:one.txt", "stderr:two.txt", "three.txt"},
|
||||
[3]string{"three.txt", "one.txt", "two.txt"},
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Logf("input: %q", tc.in)
|
||||
out, err := parseRedirects(tc.in)
|
||||
t.Logf("output: %q error %v", out, err)
|
||||
if tc.tgterr != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error %q, got output %q", tc.tgterr, out)
|
||||
} else if errstr := err.Error(); errstr != tc.tgterr {
|
||||
t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr)
|
||||
}
|
||||
} else {
|
||||
for i := range tc.tgt {
|
||||
if tc.tgt[i] != out[i] {
|
||||
t.Errorf("Expected %q, got %q (mismatch at index %d)", tc.tgt, out, i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ import (
|
||||
"github.com/go-delve/delve/service/debugger"
|
||||
"github.com/go-delve/delve/service/rpc2"
|
||||
"github.com/go-delve/delve/service/rpccommon"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -74,6 +75,11 @@ var (
|
||||
traceTestBinary bool
|
||||
traceStackDepth int
|
||||
|
||||
// redirect specifications for target process
|
||||
redirects []string
|
||||
|
||||
allowNonTerminalInteractive bool
|
||||
|
||||
conf *config.Config
|
||||
)
|
||||
|
||||
@ -123,6 +129,8 @@ func New(docCall bool) *cobra.Command {
|
||||
rootCommand.PersistentFlags().BoolVarP(&checkGoVersion, "check-go-version", "", true, "Checks that the version of Go in use is compatible with Delve.")
|
||||
rootCommand.PersistentFlags().BoolVarP(&checkLocalConnUser, "only-same-user", "", true, "Only connections from the same user that started this instance of Delve are allowed to connect.")
|
||||
rootCommand.PersistentFlags().StringVar(&backend, "backend", "default", `Backend selection (see 'dlv help backend').`)
|
||||
rootCommand.PersistentFlags().StringArrayVarP(&redirects, "redirect", "r", []string{}, "Specifies redirect rules for target process (see 'dlv help redirect')")
|
||||
rootCommand.PersistentFlags().BoolVar(&allowNonTerminalInteractive, "allow-non-terminal-interactive", false, "Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr")
|
||||
|
||||
// 'attach' subcommand.
|
||||
attachCommand := &cobra.Command{
|
||||
@ -358,6 +366,23 @@ otherwise as a file path.
|
||||
This option will also redirect the "server listening at" message in headless
|
||||
and dap modes.
|
||||
|
||||
`,
|
||||
})
|
||||
|
||||
rootCommand.AddCommand(&cobra.Command{
|
||||
Use: "redirect",
|
||||
Short: "Help about file redirection.",
|
||||
Long: `The standard file descriptors of the target process can be controlled using the '-r' and '--tty' arguments.
|
||||
|
||||
The --tty argument allows redirecting all standard descriptors to a terminal, specified as an argument to --tty.
|
||||
|
||||
The syntax for '-r' argument is:
|
||||
|
||||
-r [source:]destination
|
||||
|
||||
Where source is one of 'stdin', 'stdout' or 'stderr' and destination is the path to a file. If the source is omitted stdin is used implicitly.
|
||||
|
||||
File redirects can also be changed using the 'restart' command.
|
||||
`,
|
||||
})
|
||||
|
||||
@ -745,9 +770,34 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
||||
acceptMulti = false
|
||||
}
|
||||
|
||||
if !headless && !allowNonTerminalInteractive {
|
||||
for _, f := range []struct {
|
||||
name string
|
||||
file *os.File
|
||||
}{{"Stdin", os.Stdin}, {"Stdout", os.Stdout}, {"Stderr", os.Stderr}} {
|
||||
if f.file == nil {
|
||||
continue
|
||||
}
|
||||
if !isatty.IsTerminal(f.file.Fd()) {
|
||||
fmt.Fprintf(os.Stderr, "%s is not a terminal, use '-r' to specify redirects for the target process or --allow-non-terminal-interactive=true if you really want to specify a redirect for Delve\n", f.name)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(redirects) > 0 && tty != "" {
|
||||
fmt.Fprintf(os.Stderr, "Can not use -r and --tty together\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
redirects, err := parseRedirects(redirects)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
var listener net.Listener
|
||||
var clientConn net.Conn
|
||||
var err error
|
||||
|
||||
// Make a TCP listener
|
||||
if headless {
|
||||
@ -791,6 +841,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
||||
DebugInfoDirectories: conf.DebugInfoDirectories,
|
||||
CheckGoVersion: checkGoVersion,
|
||||
TTY: tty,
|
||||
Redirects: redirects,
|
||||
},
|
||||
})
|
||||
default:
|
||||
@ -832,3 +883,24 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
|
||||
|
||||
return connect(listener.Addr().String(), clientConn, conf, kind)
|
||||
}
|
||||
|
||||
func parseRedirects(redirects []string) ([3]string, error) {
|
||||
r := [3]string{}
|
||||
names := [3]string{"stdin", "stdout", "stderr"}
|
||||
for _, redirect := range redirects {
|
||||
idx := 0
|
||||
for i, name := range names {
|
||||
pfx := name + ":"
|
||||
if strings.HasPrefix(redirect, pfx) {
|
||||
idx = i
|
||||
redirect = redirect[len(pfx):]
|
||||
break
|
||||
}
|
||||
}
|
||||
if r[idx] != "" {
|
||||
return r, fmt.Errorf("redirect error: %s redirected twice", names[idx])
|
||||
}
|
||||
r[idx] = redirect
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ func testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
|
||||
|
||||
c := []string{dlvbin, "debug"}
|
||||
c := []string{dlvbin, "debug", "--allow-non-terminal-interactive=true"}
|
||||
debugbin := filepath.Join(buildtestdir, "__debug_bin")
|
||||
if output != "" {
|
||||
c = append(c, "--output", output)
|
||||
@ -734,7 +734,7 @@ func TestDlvTestChdir(t *testing.T) {
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
fixtures := protest.FindFixturesDir()
|
||||
cmd := exec.Command(dlvbin, "test", filepath.Join(fixtures, "buildtest"), "--", "-test.v")
|
||||
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 {
|
||||
|
@ -342,7 +342,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, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) {
|
||||
func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil, ErrUnsupportedOS
|
||||
}
|
||||
@ -371,12 +371,32 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
|
||||
ldEnvVars := getLdEnvVars()
|
||||
args := make([]string, 0, len(cmd)+4+len(ldEnvVars))
|
||||
args = append(args, ldEnvVars...)
|
||||
if foreground {
|
||||
args = append(args, "--stdio-path", "/dev/tty")
|
||||
}
|
||||
|
||||
if tty != "" {
|
||||
args = append(args, "--stdio-path", tty)
|
||||
} else {
|
||||
found := [3]bool{}
|
||||
names := [3]string{"stdin", "stdout", "stderr"}
|
||||
for i := range redirects {
|
||||
if redirects[i] != "" {
|
||||
found[i] = true
|
||||
args = append(args, fmt.Sprintf("--%s-path", names[i]), redirects[i])
|
||||
}
|
||||
}
|
||||
|
||||
if foreground {
|
||||
if !found[0] && !found[1] && !found[2] {
|
||||
args = append(args, "--stdio-path", "/dev/tty")
|
||||
} else {
|
||||
for i := range found {
|
||||
if !found[i] {
|
||||
args = append(args, fmt.Sprintf("--%s-path", names[i]), "/dev/tty")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if logflags.LLDBServerOutput() {
|
||||
args = append(args, "-g", "-l", "stdout")
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
// program. Returns a run function which will actually record the program, a
|
||||
// stop function which will prematurely terminate the recording of the
|
||||
// program.
|
||||
func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error), stop func() error, err error) {
|
||||
func RecordAsync(cmd []string, wd string, quiet bool, redirects [3]string) (run func() (string, error), stop func() error, err error) {
|
||||
if err := checkRRAvailabe(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -35,10 +35,10 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error
|
||||
args = append(args, "record", "--print-trace-dir=3")
|
||||
args = append(args, cmd...)
|
||||
rrcmd := exec.Command("rr", args...)
|
||||
rrcmd.Stdin = os.Stdin
|
||||
if !quiet {
|
||||
rrcmd.Stdout = os.Stdout
|
||||
rrcmd.Stderr = os.Stderr
|
||||
var closefn func()
|
||||
rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(redirects, quiet)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rrcmd.ExtraFiles = []*os.File{wfd}
|
||||
rrcmd.Dir = wd
|
||||
@ -51,6 +51,7 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error
|
||||
|
||||
run = func() (string, error) {
|
||||
err := rrcmd.Run()
|
||||
closefn()
|
||||
_ = wfd.Close()
|
||||
tracedir := <-tracedirChan
|
||||
return tracedir, err
|
||||
@ -63,10 +64,57 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error
|
||||
return run, stop, nil
|
||||
}
|
||||
|
||||
func openRedirects(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
|
||||
toclose := []*os.File{}
|
||||
|
||||
if redirects[0] != "" {
|
||||
stdin, err = os.Open(redirects[0])
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
toclose = append(toclose, stdin)
|
||||
} else {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
|
||||
create := func(path string, dflt *os.File) *os.File {
|
||||
if path == "" {
|
||||
if quiet {
|
||||
return nil
|
||||
}
|
||||
return dflt
|
||||
}
|
||||
var f *os.File
|
||||
f, err = os.Create(path)
|
||||
if f != nil {
|
||||
toclose = append(toclose, f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
stdout = create(redirects[1], os.Stdout)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
stderr = create(redirects[2], os.Stderr)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
closefn = func() {
|
||||
for _, f := range toclose {
|
||||
_ = f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return stdin, stdout, stderr, closefn, nil
|
||||
}
|
||||
|
||||
// Record uses rr to record the execution of the specified program and
|
||||
// returns the trace directory's path.
|
||||
func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) {
|
||||
run, _, err := RecordAsync(cmd, wd, quiet)
|
||||
func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir string, err error) {
|
||||
run, _, err := RecordAsync(cmd, wd, quiet, redirects)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -286,8 +334,8 @@ func splitQuotedFields(in string) []string {
|
||||
}
|
||||
|
||||
// RecordAndReplay acts like calling Record and then Replay.
|
||||
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (*proc.Target, string, error) {
|
||||
tracedir, err := Record(cmd, wd, quiet)
|
||||
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) {
|
||||
tracedir, err := Record(cmd, wd, quiet, redirects)
|
||||
if tracedir == "" {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixtur
|
||||
t.Skip("test skipped, rr not found")
|
||||
}
|
||||
t.Log("recording")
|
||||
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{})
|
||||
p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{})
|
||||
if err != nil {
|
||||
t.Fatal("Launch():", err)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
|
||||
|
||||
// Launch returns ErrNativeBackendDisabled.
|
||||
func Launch(_ []string, _ string, _ bool, _ []string, _ string) (*proc.Target, error) {
|
||||
func Launch(_ []string, _ string, _ bool, _ []string, _ string, _ [3]string) (*proc.Target, error) {
|
||||
return nil, ErrNativeBackendDisabled
|
||||
}
|
||||
|
||||
|
@ -320,3 +320,47 @@ func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uin
|
||||
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
|
||||
return err
|
||||
}
|
||||
|
||||
func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
|
||||
toclose := []*os.File{}
|
||||
|
||||
if redirects[0] != "" {
|
||||
stdin, err = os.Open(redirects[0])
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
toclose = append(toclose, stdin)
|
||||
} else if foreground {
|
||||
stdin = os.Stdin
|
||||
}
|
||||
|
||||
create := func(path string, dflt *os.File) *os.File {
|
||||
if path == "" {
|
||||
return dflt
|
||||
}
|
||||
var f *os.File
|
||||
f, err = os.Create(path)
|
||||
if f != nil {
|
||||
toclose = append(toclose, f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
stdout = create(redirects[1], os.Stdout)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
stderr = create(redirects[2], os.Stderr)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
closefn = func() {
|
||||
for _, f := range toclose {
|
||||
_ = f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return stdin, stdout, stderr, closefn, nil
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type osProcessDetails struct {
|
||||
// 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, foreground bool, _ []string, _ string) (*proc.Target, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string, _ string, _ [3]string) (*proc.Target, error) {
|
||||
argv0Go, err := filepath.Abs(cmd[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -6,7 +6,6 @@ package native
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
@ -43,13 +42,18 @@ type osProcessDetails struct {
|
||||
// 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, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
||||
var (
|
||||
process *exec.Cmd
|
||||
err error
|
||||
)
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stdin == nil || !isatty.IsTerminal(stdin.Fd()) {
|
||||
// exec.(*Process).Start will fail if we try to send a process to
|
||||
// foreground but we are not attached to a terminal.
|
||||
foreground = false
|
||||
@ -64,13 +68,13 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
|
||||
dbp.execPtraceFunc(func() {
|
||||
process = exec.Command(cmd[0])
|
||||
process.Args = cmd
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
process.Stdin = stdin
|
||||
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)
|
||||
process.Stdin = os.Stdin
|
||||
}
|
||||
if tty != "" {
|
||||
dbp.ctty, err = attachProcessToTTY(process, tty)
|
||||
@ -83,6 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
|
||||
}
|
||||
err = process.Start()
|
||||
})
|
||||
closefn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -49,13 +49,18 @@ type osProcessDetails struct {
|
||||
// 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, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) {
|
||||
var (
|
||||
process *exec.Cmd
|
||||
err error
|
||||
)
|
||||
|
||||
if !isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stdin == nil || !isatty.IsTerminal(stdin.Fd()) {
|
||||
// exec.(*Process).Start will fail if we try to send a process to
|
||||
// foreground but we are not attached to a terminal.
|
||||
foreground = false
|
||||
@ -70,8 +75,9 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
|
||||
dbp.execPtraceFunc(func() {
|
||||
process = exec.Command(cmd[0])
|
||||
process.Args = cmd
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
process.Stdin = stdin
|
||||
process.Stdout = stdout
|
||||
process.Stderr = stderr
|
||||
process.SysProcAttr = &syscall.SysProcAttr{
|
||||
Ptrace: true,
|
||||
Setpgid: true,
|
||||
@ -79,7 +85,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
|
||||
}
|
||||
if foreground {
|
||||
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
|
||||
process.Stdin = os.Stdin
|
||||
}
|
||||
if tty != "" {
|
||||
dbp.ctty, err = attachProcessToTTY(process, tty)
|
||||
@ -92,6 +97,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
|
||||
}
|
||||
err = process.Start()
|
||||
})
|
||||
closefn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type osProcessDetails struct {
|
||||
}
|
||||
|
||||
// Launch creates and begins debugging a new process.
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*proc.Target, error) {
|
||||
func Launch(cmd []string, wd string, foreground bool, _ []string, _ string, redirects [3]string) (*proc.Target, error) {
|
||||
argv0Go, err := filepath.Abs(cmd[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -29,12 +29,17 @@ func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*pr
|
||||
|
||||
env := proc.DisableAsyncPreemptEnv()
|
||||
|
||||
stdin, stdout, stderr, closefn, err := openRedirects(redirects, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var p *os.Process
|
||||
dbp := newProcess(0)
|
||||
dbp.execPtraceFunc(func() {
|
||||
attr := &os.ProcAttr{
|
||||
Dir: wd,
|
||||
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
|
||||
Files: []*os.File{stdin, stdout, stderr},
|
||||
Sys: &syscall.SysProcAttr{
|
||||
CreationFlags: _DEBUG_ONLY_THIS_PROCESS,
|
||||
},
|
||||
@ -42,6 +47,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*pr
|
||||
}
|
||||
p, err = os.StartProcess(argv0Go, cmd, attr)
|
||||
})
|
||||
closefn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ func TestLoadingExternalDebugInfo(t *testing.T) {
|
||||
fixture := protest.BuildFixture("locationsprog", 0)
|
||||
defer os.Remove(fixture.Path)
|
||||
stripAndCopyDebugInfo(fixture, t)
|
||||
p, err := native.Launch(append([]string{fixture.Path}, ""), "", false, []string{filepath.Dir(fixture.Path)}, "")
|
||||
p, err := native.Launch(append([]string{fixture.Path}, ""), "", false, []string{filepath.Dir(fixture.Path)}, "", [3]string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -69,13 +69,13 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "")
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "")
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{})
|
||||
case "rr":
|
||||
protest.MustHaveRecordingAllowed(t)
|
||||
t.Log("recording")
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{})
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{})
|
||||
t.Logf("replaying %q", tracedir)
|
||||
default:
|
||||
t.Fatal("unknown backend")
|
||||
@ -2102,9 +2102,9 @@ func TestUnsupportedArch(t *testing.T) {
|
||||
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch([]string{outfile}, ".", false, []string{}, "")
|
||||
p, err = native.Launch([]string{outfile}, ".", false, []string{}, "", [3]string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "")
|
||||
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "", [3]string{})
|
||||
default:
|
||||
t.Skip("test not valid for this backend")
|
||||
}
|
||||
|
@ -134,14 +134,20 @@ For recorded targets the command takes the following forms:
|
||||
|
||||
restart resets ot the start of the recording
|
||||
restart [checkpoint] resets the recording to the given checkpoint
|
||||
restart -r [newargv...] re-records the target process
|
||||
restart -r [newargv...] [redirects...] re-records the target process
|
||||
|
||||
For live targets the command takes the following forms:
|
||||
|
||||
restart [newargv...] restarts the process
|
||||
restart [newargv...] [redirects...] restarts the process
|
||||
|
||||
If newargv is omitted the process is restarted (or re-recorded) with the same argument vector.
|
||||
If -noargs is specified instead, the argument vector is cleared.
|
||||
|
||||
A list of file redirections can be specified after the new argument list to override the redirections defined using the '--redirect' command line option. A syntax similar to Unix shells is used:
|
||||
|
||||
<input.txt redirects the standard input of the target process from input.txt
|
||||
>output.txt redirects the standard output of the target process to output.txt
|
||||
2>error.txt redirects the standard error of the target process to error.txt
|
||||
`},
|
||||
{aliases: []string{"rebuild"}, group: runCmds, cmdFn: c.rebuild, allowedPrefixes: revPrefix, helpMsg: "Rebuild the target executable and restarts it. It does not work if the executable was not built by delve."},
|
||||
{aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."},
|
||||
@ -971,6 +977,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error {
|
||||
rerecord := false
|
||||
resetArgs := false
|
||||
newArgv := []string{}
|
||||
newRedirects := [3]string{}
|
||||
restartPos := ""
|
||||
|
||||
if len(v) > 0 {
|
||||
@ -978,7 +985,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error {
|
||||
rerecord = true
|
||||
if len(v) == 2 {
|
||||
var err error
|
||||
resetArgs, newArgv, err = parseNewArgv(v[1])
|
||||
resetArgs, newArgv, newRedirects, err = parseNewArgv(v[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -991,7 +998,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv); err != nil {
|
||||
if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv, newRedirects); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1015,12 +1022,12 @@ func parseOptionalCount(arg string) (int64, error) {
|
||||
}
|
||||
|
||||
func restartLive(t *Term, ctx callContext, args string) error {
|
||||
resetArgs, newArgv, err := parseNewArgv(args)
|
||||
resetArgs, newArgv, newRedirects, err := parseNewArgv(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := restartIntl(t, false, "", resetArgs, newArgv); err != nil {
|
||||
if err := restartIntl(t, false, "", resetArgs, newArgv, newRedirects); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1028,8 +1035,8 @@ func restartLive(t *Term, ctx callContext, args string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error {
|
||||
discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, false)
|
||||
func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string, newRedirects [3]string) error {
|
||||
discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, newRedirects, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1039,9 +1046,9 @@ func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newA
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) {
|
||||
func parseNewArgv(args string) (resetArgs bool, newArgv []string, newRedirects [3]string, err error) {
|
||||
if args == "" {
|
||||
return false, nil, nil
|
||||
return false, nil, [3]string{}, nil
|
||||
}
|
||||
v, err := argv.Argv(args,
|
||||
func(s string) (string, error) {
|
||||
@ -1049,22 +1056,58 @@ func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) {
|
||||
},
|
||||
nil)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, [3]string{}, err
|
||||
}
|
||||
if len(v) != 1 {
|
||||
return false, nil, fmt.Errorf("Illegal commandline '%s'", args)
|
||||
return false, nil, [3]string{}, fmt.Errorf("illegal commandline '%s'", args)
|
||||
}
|
||||
w := v[0]
|
||||
if len(w) == 0 {
|
||||
return false, nil, nil
|
||||
return false, nil, [3]string{}, nil
|
||||
}
|
||||
if w[0] == "-noargs" {
|
||||
if len(w) > 1 {
|
||||
return false, nil, fmt.Errorf("Too many arguments to restart")
|
||||
return false, nil, [3]string{}, fmt.Errorf("too many arguments to restart")
|
||||
}
|
||||
return true, nil, nil
|
||||
return true, nil, [3]string{}, nil
|
||||
}
|
||||
return true, w, nil
|
||||
redirs := [3]string{}
|
||||
for len(w) > 0 {
|
||||
var found bool
|
||||
var err error
|
||||
w, found, err = parseOneRedirect(w, &redirs)
|
||||
if err != nil {
|
||||
return false, nil, [3]string{}, err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
return true, w, redirs, nil
|
||||
}
|
||||
|
||||
func parseOneRedirect(w []string, redirs *[3]string) ([]string, bool, error) {
|
||||
prefixes := []string{"<", ">", "2>"}
|
||||
names := []string{"stdin", "stdout", "stderr"}
|
||||
if len(w) >= 2 {
|
||||
for _, prefix := range prefixes {
|
||||
if w[len(w)-2] == prefix {
|
||||
w[len(w)-2] += w[len(w)-1]
|
||||
w = w[:len(w)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, prefix := range prefixes {
|
||||
if strings.HasPrefix(w[len(w)-1], prefix) {
|
||||
if redirs[i] != "" {
|
||||
return nil, false, fmt.Errorf("redirect error: %s redirected twice", names[i])
|
||||
}
|
||||
redirs[i] = w[len(w)-1][len(prefix):]
|
||||
return w[:len(w)-1], true, nil
|
||||
}
|
||||
}
|
||||
return w, false, nil
|
||||
}
|
||||
|
||||
func printcontextNoState(t *Term) {
|
||||
|
@ -1138,3 +1138,50 @@ func TestPrintCastToInterface(t *testing.T) {
|
||||
t.Logf("%q", out)
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseNewArgv(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in string
|
||||
tgtargs string
|
||||
tgtredir string
|
||||
tgterr string
|
||||
}{
|
||||
{"-noargs", "", " | | ", ""},
|
||||
{"-noargs arg1", "", "", "too many arguments to restart"},
|
||||
{"arg1 arg2", "arg1 | arg2", " | | ", ""},
|
||||
{"arg1 arg2 <input.txt", "arg1 | arg2", "input.txt | | ", ""},
|
||||
{"arg1 arg2 < input.txt", "arg1 | arg2", "input.txt | | ", ""},
|
||||
{"<input.txt", "", "input.txt | | ", ""},
|
||||
{"< input.txt", "", "input.txt | | ", ""},
|
||||
{"arg1 < input.txt > output.txt 2> error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
|
||||
{"< input.txt > output.txt 2> error.txt", "", "input.txt | output.txt | error.txt", ""},
|
||||
{"arg1 <input.txt >output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""},
|
||||
{"<input.txt >output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""},
|
||||
{"<input.txt <input2.txt", "", "", "redirect error: stdin redirected twice"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
resetArgs, newArgv, newRedirects, err := parseNewArgv(tc.in)
|
||||
t.Logf("%q -> %q %q %v\n", tc.in, newArgv, newRedirects, err)
|
||||
if tc.tgterr != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error %q, got no error", tc.tgterr)
|
||||
} else if errstr := err.Error(); errstr != tc.tgterr {
|
||||
t.Errorf("Expected error %q, got error %q", tc.tgterr, errstr)
|
||||
}
|
||||
} else {
|
||||
if !resetArgs {
|
||||
t.Errorf("parse error, resetArgs is false")
|
||||
continue
|
||||
}
|
||||
argvstr := strings.Join(newArgv, " | ")
|
||||
if argvstr != tc.tgtargs {
|
||||
t.Errorf("Expected new arguments %q, got %q", tc.tgtargs, argvstr)
|
||||
}
|
||||
redirstr := strings.Join(newRedirects[:], " | ")
|
||||
if redirstr != tc.tgtredir {
|
||||
t.Errorf("Expected new redirects %q, got %q", tc.tgtredir, redirstr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1097,6 +1097,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
if len(args) > 5 && args[5] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[5], &rpcArgs.NewRedirects, "NewRedirects")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
@ -1110,6 +1116,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord")
|
||||
case "Rebuild":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild")
|
||||
case "NewRedirects":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewRedirects, "NewRedirects")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type Client interface {
|
||||
// Restarts program. Set true if you want to rebuild the process we are debugging.
|
||||
Restart(rebuild bool) ([]api.DiscardedBreakpoint, error)
|
||||
// Restarts program from the specified position.
|
||||
RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error)
|
||||
RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error)
|
||||
|
||||
// GetState returns the current debugger state.
|
||||
GetState() (*api.DebuggerState, error)
|
||||
|
@ -117,6 +117,9 @@ type Config struct {
|
||||
|
||||
// ExecuteKind contains the kind of the executed program.
|
||||
ExecuteKind ExecuteKind
|
||||
|
||||
// Redirects specifies redirect rules for stdin, stdout and stderr
|
||||
Redirects [3]string
|
||||
}
|
||||
|
||||
// New creates a new Debugger. ProcessArgs specify the commandline arguments for the
|
||||
@ -221,16 +224,16 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
|
||||
}
|
||||
switch d.config.Backend {
|
||||
case "native":
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY)
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects)
|
||||
case "lldb":
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects))
|
||||
case "rr":
|
||||
if d.target != nil {
|
||||
// restart should not call us if the backend is 'rr'
|
||||
panic("internal error: call to Launch with rr backend and target already exists")
|
||||
}
|
||||
|
||||
run, stop, err := gdbserial.RecordAsync(processArgs, wd, false)
|
||||
run, stop, err := gdbserial.RecordAsync(processArgs, wd, false, d.config.Redirects)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -265,9 +268,9 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
|
||||
|
||||
case "default":
|
||||
if runtime.GOOS == "darwin" {
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY))
|
||||
return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects))
|
||||
}
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY)
|
||||
return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown backend %q", d.config.Backend)
|
||||
}
|
||||
@ -409,7 +412,7 @@ func (d *Debugger) detach(kill bool) error {
|
||||
// If the target process is a recording it will restart it from the given
|
||||
// position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an
|
||||
// event number. If resetArgs is true, newArgs will replace the process args.
|
||||
func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
|
||||
func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
|
||||
d.targetMutex.Lock()
|
||||
defer d.targetMutex.Unlock()
|
||||
|
||||
@ -437,6 +440,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
}
|
||||
if resetArgs {
|
||||
d.processArgs = append([]string{d.processArgs[0]}, newArgs...)
|
||||
d.config.Redirects = newRedirects
|
||||
}
|
||||
var p *proc.Target
|
||||
var err error
|
||||
@ -460,7 +464,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
|
||||
}
|
||||
|
||||
if recorded {
|
||||
run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false)
|
||||
run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false, d.config.Redirects)
|
||||
if err2 != nil {
|
||||
return nil, err2
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
|
||||
if s.config.Debugger.AttachPid != 0 {
|
||||
return errors.New("cannot restart process Delve did not create")
|
||||
}
|
||||
_, err := s.debugger.Restart(false, "", false, nil, false)
|
||||
_, err := s.debugger.Restart(false, "", false, nil, [3]string{}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -62,13 +62,13 @@ func (c *RPCClient) Detach(kill bool) error {
|
||||
|
||||
func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) {
|
||||
out := new(RestartOut)
|
||||
err := c.call("Restart", RestartIn{"", false, nil, false, rebuild}, out)
|
||||
err := c.call("Restart", RestartIn{"", false, nil, false, rebuild, [3]string{}}, out)
|
||||
return out.DiscardedBreakpoints, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
|
||||
func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) {
|
||||
out := new(RestartOut)
|
||||
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild}, out)
|
||||
err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild, newRedirects}, out)
|
||||
return out.DiscardedBreakpoints, err
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ type RestartIn struct {
|
||||
// otherwise it's an event number. Only valid for recorded targets.
|
||||
Position string
|
||||
|
||||
// ResetArgs tell whether NewArgs should take effect.
|
||||
// ResetArgs tell whether NewArgs and NewRedirects should take effect.
|
||||
ResetArgs bool
|
||||
// NewArgs are arguments to launch a new process. They replace only the
|
||||
// argv[1] and later. Argv[0] cannot be changed.
|
||||
@ -79,6 +79,8 @@ type RestartIn struct {
|
||||
|
||||
// When Rebuild is set the process will be build again
|
||||
Rebuild bool
|
||||
|
||||
NewRedirects [3]string
|
||||
}
|
||||
|
||||
type RestartOut struct {
|
||||
@ -93,7 +95,7 @@ func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) {
|
||||
}
|
||||
var out RestartOut
|
||||
var err error
|
||||
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.Rebuild)
|
||||
out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.NewRedirects, arg.Rebuild)
|
||||
cb.Return(out, err)
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,12 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func withTestClient2(name string, t *testing.T, fn func(c service.Client)) {
|
||||
withTestClient2Extended(name, t, 0, func(c service.Client, fixture protest.Fixture) {
|
||||
withTestClient2Extended(name, t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) {
|
||||
fn(c)
|
||||
})
|
||||
}
|
||||
|
||||
func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clientConn net.Conn, fixture protest.Fixture) {
|
||||
func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redirects [3]string) (clientConn net.Conn, fixture protest.Fixture) {
|
||||
if testBackend == "rr" {
|
||||
protest.MustHaveRecordingAllowed(t)
|
||||
}
|
||||
@ -69,6 +69,11 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
|
||||
buildFlags |= protest.BuildModePIE
|
||||
}
|
||||
fixture = protest.BuildFixture(name, buildFlags)
|
||||
for i := range redirects {
|
||||
if redirects[i] != "" {
|
||||
redirects[i] = filepath.Join(fixture.BuildDir, redirects[i])
|
||||
}
|
||||
}
|
||||
server := rpccommon.NewServer(&service.Config{
|
||||
Listener: listener,
|
||||
ProcessArgs: []string{fixture.Path},
|
||||
@ -78,6 +83,7 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
|
||||
Packages: []string{fixture.Source},
|
||||
BuildFlags: "", // build flags can be an empty string here because the only test that uses it, does not set special flags.
|
||||
ExecuteKind: debugger.ExecutingGeneratedFile,
|
||||
Redirects: redirects,
|
||||
},
|
||||
})
|
||||
if err := server.Run(); err != nil {
|
||||
@ -86,8 +92,8 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
|
||||
return clientConn, fixture
|
||||
}
|
||||
|
||||
func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, fn func(c service.Client, fixture protest.Fixture)) {
|
||||
clientConn, fixture := startServer(name, buildFlags, t)
|
||||
func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, redirects [3]string, fn func(c service.Client, fixture protest.Fixture)) {
|
||||
clientConn, fixture := startServer(name, buildFlags, t, redirects)
|
||||
client := rpc2.NewClientFromConn(clientConn)
|
||||
defer func() {
|
||||
client.Detach(true)
|
||||
@ -223,7 +229,7 @@ func TestRestart_rebuild(t *testing.T) {
|
||||
// In the original fixture file the env var tested for is SOMEVAR.
|
||||
os.Setenv("SOMEVAR", "bah")
|
||||
|
||||
withTestClient2Extended("testenv", t, 0, func(c service.Client, f protest.Fixture) {
|
||||
withTestClient2Extended("testenv", t, 0, [3]string{}, func(c service.Client, f protest.Fixture) {
|
||||
<-c.Continue()
|
||||
|
||||
var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig)
|
||||
@ -772,7 +778,7 @@ func TestClientServer_FindLocations(t *testing.T) {
|
||||
findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr)
|
||||
})
|
||||
|
||||
withTestClient2Extended("locationsUpperCase", t, 0, func(c service.Client, fixture protest.Fixture) {
|
||||
withTestClient2Extended("locationsUpperCase", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) {
|
||||
// Upper case
|
||||
findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0)
|
||||
|
||||
@ -1895,7 +1901,7 @@ func (c *brokenRPCClient) call(method string, args, reply interface{}) error {
|
||||
}
|
||||
|
||||
func TestUnknownMethodCall(t *testing.T) {
|
||||
clientConn, _ := startServer("continuetestprog", 0, t)
|
||||
clientConn, _ := startServer("continuetestprog", 0, t, [3]string{})
|
||||
client := &brokenRPCClient{jsonrpc.NewClient(clientConn)}
|
||||
client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{})
|
||||
defer client.Detach(true)
|
||||
@ -1950,7 +1956,7 @@ func TestRerecord(t *testing.T) {
|
||||
|
||||
t0 := gett()
|
||||
|
||||
_, err = c.RestartFrom(false, "", false, nil, false)
|
||||
_, err = c.RestartFrom(false, "", false, nil, [3]string{}, false)
|
||||
assertNoError(err, t, "First restart")
|
||||
t1 := gett()
|
||||
|
||||
@ -1960,7 +1966,7 @@ func TestRerecord(t *testing.T) {
|
||||
|
||||
time.Sleep(2 * time.Second) // make sure that we're not running inside the same second
|
||||
|
||||
_, err = c.RestartFrom(true, "", false, nil, false)
|
||||
_, err = c.RestartFrom(true, "", false, nil, [3]string{}, false)
|
||||
assertNoError(err, t, "Second restart")
|
||||
t2 := gett()
|
||||
|
||||
@ -2025,7 +2031,7 @@ func TestStopRecording(t *testing.T) {
|
||||
|
||||
// try rerecording
|
||||
go func() {
|
||||
c.RestartFrom(true, "", false, nil, false)
|
||||
c.RestartFrom(true, "", false, nil, [3]string{}, false)
|
||||
}()
|
||||
|
||||
time.Sleep(time.Second) // hopefully the re-recording started...
|
||||
@ -2039,7 +2045,7 @@ func TestClearLogicalBreakpoint(t *testing.T) {
|
||||
// Clearing a logical breakpoint should clear all associated physical
|
||||
// breakpoints.
|
||||
// Issue #1955.
|
||||
withTestClient2Extended("testinline", t, protest.EnableInlining, func(c service.Client, fixture protest.Fixture) {
|
||||
withTestClient2Extended("testinline", t, protest.EnableInlining, [3]string{}, func(c service.Client, fixture protest.Fixture) {
|
||||
bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.inlineThis"})
|
||||
assertNoError(err, t, "CreateBreakpoint()")
|
||||
t.Logf("breakpoint set at %#v", bp.Addrs)
|
||||
@ -2058,3 +2064,37 @@ func TestClearLogicalBreakpoint(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRedirects(t *testing.T) {
|
||||
const (
|
||||
infile = "redirect-input.txt"
|
||||
outfile = "redirect-output.txt"
|
||||
)
|
||||
protest.AllowRecording(t)
|
||||
withTestClient2Extended("redirect", t, 0, [3]string{infile, outfile, ""}, func(c service.Client, fixture protest.Fixture) {
|
||||
outpath := filepath.Join(fixture.BuildDir, outfile)
|
||||
<-c.Continue()
|
||||
buf, err := ioutil.ReadFile(outpath)
|
||||
assertNoError(err, t, "Reading output file")
|
||||
t.Logf("output %q", buf)
|
||||
if !strings.HasPrefix(string(buf), "Redirect test") {
|
||||
t.Fatalf("Wrong output %q", string(buf))
|
||||
}
|
||||
os.Remove(outpath)
|
||||
if testBackend != "rr" {
|
||||
_, err = c.Restart(false)
|
||||
assertNoError(err, t, "Restart")
|
||||
<-c.Continue()
|
||||
buf2, err := ioutil.ReadFile(outpath)
|
||||
t.Logf("output %q", buf2)
|
||||
assertNoError(err, t, "Reading output file (second time)")
|
||||
if !strings.HasPrefix(string(buf2), "Redirect test") {
|
||||
t.Fatalf("Wrong output %q", string(buf2))
|
||||
}
|
||||
if string(buf2) == string(buf) {
|
||||
t.Fatalf("Expected output change got %q and %q", string(buf), string(buf2))
|
||||
}
|
||||
os.Remove(outpath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -131,13 +131,13 @@ func withTestProcessArgs(name string, t *testing.T, wd string, args []string, bu
|
||||
var tracedir string
|
||||
switch testBackend {
|
||||
case "native":
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "")
|
||||
p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{})
|
||||
case "lldb":
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "")
|
||||
p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{})
|
||||
case "rr":
|
||||
protest.MustHaveRecordingAllowed(t)
|
||||
t.Log("recording")
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{})
|
||||
p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{})
|
||||
t.Logf("replaying %q", tracedir)
|
||||
default:
|
||||
t.Fatalf("unknown backend %q", testBackend)
|
||||
|
Loading…
Reference in New Issue
Block a user