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:
aarzilli 2020-08-21 16:14:02 +02:00 committed by Alessandro Arzilli
parent f90134eb4d
commit 7555d1c063
43 changed files with 766 additions and 271 deletions

@ -412,17 +412,23 @@ Restart process.
For recorded targets the command takes the following forms: For recorded targets the command takes the following forms:
restart resets ot the start of the recording restart resets ot the start of the recording
restart [checkpoint] resets the recording to the given checkpoint 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: 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 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. 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 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) 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) 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) 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) 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) 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) state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State)

@ -19,19 +19,21 @@ Pass flags to the program you are debugging using `--`, for example:
### Options ### Options
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -25,19 +25,21 @@ dlv attach pid [executable]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -18,19 +18,21 @@ are:
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -14,19 +14,21 @@ dlv connect addr
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -20,19 +20,21 @@ dlv core <executable> <core>
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -21,19 +21,21 @@ dlv dap
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -27,19 +27,21 @@ dlv debug [package]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -27,19 +27,21 @@ dlv exec <path/to/binary>
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -33,19 +33,21 @@ and dap modes.
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -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.

@ -18,19 +18,21 @@ dlv replay [trace directory]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -14,19 +14,21 @@ dlv run
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -25,19 +25,21 @@ dlv test [package]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -32,19 +32,21 @@ dlv trace [package] regexp
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -14,19 +14,21 @@ dlv version
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--accept-multiclient Allows a headless server to accept multiple client connections. --accept-multiclient Allows a headless server to accept multiple client connections.
--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) --allow-non-terminal-interactive Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr
--backend string Backend selection (see 'dlv help backend'). (default "default") --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)
--build-flags string Build flags, to be passed to the compiler. --backend string Backend selection (see 'dlv help backend'). (default "default")
--check-go-version Checks that the version of Go in use is compatible with Delve. (default true) --build-flags string Build flags, to be passed to the compiler.
--headless Run debug server only, in headless mode. --check-go-version Checks that the version of Go in use is compatible with Delve. (default true)
--init string Init file, executed by the terminal client. --headless Run debug server only, in headless mode.
-l, --listen string Debugging server listen address. (default "127.0.0.1:0") --init string Init file, executed by the terminal client.
--log Enable debugging server logging. -l, --listen string Debugging server listen address. (default "127.0.0.1:0")
--log-dest string Writes logs to the specified file or file descriptor (see 'dlv help log'). --log Enable debugging server logging.
--log-output string Comma separated list of components that should produce debug output (see 'dlv help log') --log-dest string Writes logs to the specified file or file descriptor (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) --log-output string Comma separated list of components that should produce debug output (see 'dlv help log')
--wd string Working directory for running the program. --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 ### SEE ALSO

@ -0,0 +1 @@
Redirect test

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

@ -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/debugger"
"github.com/go-delve/delve/service/rpc2" "github.com/go-delve/delve/service/rpc2"
"github.com/go-delve/delve/service/rpccommon" "github.com/go-delve/delve/service/rpccommon"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -74,6 +75,11 @@ var (
traceTestBinary bool traceTestBinary bool
traceStackDepth int traceStackDepth int
// redirect specifications for target process
redirects []string
allowNonTerminalInteractive bool
conf *config.Config 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(&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().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().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. // 'attach' subcommand.
attachCommand := &cobra.Command{ attachCommand := &cobra.Command{
@ -358,6 +366,23 @@ otherwise as a file path.
This option will also redirect the "server listening at" message in headless This option will also redirect the "server listening at" message in headless
and dap modes. 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 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 listener net.Listener
var clientConn net.Conn var clientConn net.Conn
var err error
// Make a TCP listener // Make a TCP listener
if headless { if headless {
@ -791,6 +841,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
DebugInfoDirectories: conf.DebugInfoDirectories, DebugInfoDirectories: conf.DebugInfoDirectories,
CheckGoVersion: checkGoVersion, CheckGoVersion: checkGoVersion,
TTY: tty, TTY: tty,
Redirects: redirects,
}, },
}) })
default: default:
@ -832,3 +883,24 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
return connect(listener.Addr().String(), clientConn, conf, kind) 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 var stdoutBuf, stderrBuf bytes.Buffer
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") 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") debugbin := filepath.Join(buildtestdir, "__debug_bin")
if output != "" { if output != "" {
c = append(c, "--output", output) c = append(c, "--output", output)
@ -734,7 +734,7 @@ func TestDlvTestChdir(t *testing.T) {
defer os.RemoveAll(tmpdir) defer os.RemoveAll(tmpdir)
fixtures := protest.FindFixturesDir() 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") cmd.Stdin = strings.NewReader("continue\nexit\n")
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {

@ -342,7 +342,7 @@ func getLdEnvVars() []string {
// LLDBLaunch starts an instance of lldb-server and connects to it, asking // LLDBLaunch starts an instance of lldb-server and connects to it, asking
// it to launch the specified target program with the specified arguments // it to launch the specified target program with the specified arguments
// (cmd) on the specified directory wd. // (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" { if runtime.GOOS == "windows" {
return nil, ErrUnsupportedOS return nil, ErrUnsupportedOS
} }
@ -371,12 +371,32 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string
ldEnvVars := getLdEnvVars() ldEnvVars := getLdEnvVars()
args := make([]string, 0, len(cmd)+4+len(ldEnvVars)) args := make([]string, 0, len(cmd)+4+len(ldEnvVars))
args = append(args, ldEnvVars...) args = append(args, ldEnvVars...)
if foreground {
args = append(args, "--stdio-path", "/dev/tty")
}
if tty != "" { if tty != "" {
args = append(args, "--stdio-path", 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() { if logflags.LLDBServerOutput() {
args = append(args, "-g", "-l", "stdout") args = append(args, "-g", "-l", "stdout")
} }

@ -21,7 +21,7 @@ import (
// program. Returns a run function which will actually record the program, a // program. Returns a run function which will actually record the program, a
// stop function which will prematurely terminate the recording of the // stop function which will prematurely terminate the recording of the
// program. // 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 { if err := checkRRAvailabe(); err != nil {
return nil, nil, err 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, "record", "--print-trace-dir=3")
args = append(args, cmd...) args = append(args, cmd...)
rrcmd := exec.Command("rr", args...) rrcmd := exec.Command("rr", args...)
rrcmd.Stdin = os.Stdin var closefn func()
if !quiet { rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(redirects, quiet)
rrcmd.Stdout = os.Stdout if err != nil {
rrcmd.Stderr = os.Stderr return nil, nil, err
} }
rrcmd.ExtraFiles = []*os.File{wfd} rrcmd.ExtraFiles = []*os.File{wfd}
rrcmd.Dir = wd rrcmd.Dir = wd
@ -51,6 +51,7 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error
run = func() (string, error) { run = func() (string, error) {
err := rrcmd.Run() err := rrcmd.Run()
closefn()
_ = wfd.Close() _ = wfd.Close()
tracedir := <-tracedirChan tracedir := <-tracedirChan
return tracedir, err return tracedir, err
@ -63,10 +64,57 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error
return run, stop, nil 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 // Record uses rr to record the execution of the specified program and
// returns the trace directory's path. // returns the trace directory's path.
func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) { func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir string, err error) {
run, _, err := RecordAsync(cmd, wd, quiet) run, _, err := RecordAsync(cmd, wd, quiet, redirects)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -286,8 +334,8 @@ func splitQuotedFields(in string) []string {
} }
// RecordAndReplay acts like calling Record and then Replay. // RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (*proc.Target, string, error) { func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) {
tracedir, err := Record(cmd, wd, quiet) tracedir, err := Record(cmd, wd, quiet, redirects)
if tracedir == "" { if tracedir == "" {
return nil, "", err 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.Skip("test skipped, rr not found")
} }
t.Log("recording") 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 { if err != nil {
t.Fatal("Launch():", err) t.Fatal("Launch():", err)
} }

@ -12,7 +12,7 @@ import (
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")
// Launch returns ErrNativeBackendDisabled. // 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 return nil, ErrNativeBackendDisabled
} }

@ -320,3 +320,47 @@ func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uin
_, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction()) _, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction())
return err 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 // custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into // PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions. // 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]) argv0Go, err := filepath.Abs(cmd[0])
if err != nil { if err != nil {
return nil, err return nil, err

@ -6,7 +6,6 @@ package native
import "C" import "C"
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"os/signal" "os/signal"
"strings" "strings"
@ -43,13 +42,18 @@ type osProcessDetails struct {
// to be supplied to that process. `wd` is working directory of the program. // 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 // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // 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 ( var (
process *exec.Cmd process *exec.Cmd
err error 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 // exec.(*Process).Start will fail if we try to send a process to
// foreground but we are not attached to a terminal. // foreground but we are not attached to a terminal.
foreground = false foreground = false
@ -64,13 +68,13 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0]) process = exec.Command(cmd[0])
process.Args = cmd process.Args = cmd
process.Stdout = os.Stdout process.Stdin = stdin
process.Stderr = os.Stderr process.Stdout = stdout
process.Stderr = stderr
process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground} process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground}
process.Env = proc.DisableAsyncPreemptEnv() process.Env = proc.DisableAsyncPreemptEnv()
if foreground { if foreground {
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
process.Stdin = os.Stdin
} }
if tty != "" { if tty != "" {
dbp.ctty, err = attachProcessToTTY(process, 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() err = process.Start()
}) })
closefn()
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -49,13 +49,18 @@ type osProcessDetails struct {
// to be supplied to that process. `wd` is working directory of the program. // 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 // If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in. // 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 ( var (
process *exec.Cmd process *exec.Cmd
err error 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 // exec.(*Process).Start will fail if we try to send a process to
// foreground but we are not attached to a terminal. // foreground but we are not attached to a terminal.
foreground = false foreground = false
@ -70,8 +75,9 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
process = exec.Command(cmd[0]) process = exec.Command(cmd[0])
process.Args = cmd process.Args = cmd
process.Stdout = os.Stdout process.Stdin = stdin
process.Stderr = os.Stderr process.Stdout = stdout
process.Stderr = stderr
process.SysProcAttr = &syscall.SysProcAttr{ process.SysProcAttr = &syscall.SysProcAttr{
Ptrace: true, Ptrace: true,
Setpgid: true, Setpgid: true,
@ -79,7 +85,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt
} }
if foreground { if foreground {
signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN)
process.Stdin = os.Stdin
} }
if tty != "" { if tty != "" {
dbp.ctty, err = attachProcessToTTY(process, 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() err = process.Start()
}) })
closefn()
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -21,7 +21,7 @@ type osProcessDetails struct {
} }
// Launch creates and begins debugging a new process. // 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]) argv0Go, err := filepath.Abs(cmd[0])
if err != nil { if err != nil {
return nil, err return nil, err
@ -29,12 +29,17 @@ func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*pr
env := proc.DisableAsyncPreemptEnv() env := proc.DisableAsyncPreemptEnv()
stdin, stdout, stderr, closefn, err := openRedirects(redirects, true)
if err != nil {
return nil, err
}
var p *os.Process var p *os.Process
dbp := newProcess(0) dbp := newProcess(0)
dbp.execPtraceFunc(func() { dbp.execPtraceFunc(func() {
attr := &os.ProcAttr{ attr := &os.ProcAttr{
Dir: wd, Dir: wd,
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, Files: []*os.File{stdin, stdout, stderr},
Sys: &syscall.SysProcAttr{ Sys: &syscall.SysProcAttr{
CreationFlags: _DEBUG_ONLY_THIS_PROCESS, 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) p, err = os.StartProcess(argv0Go, cmd, attr)
}) })
closefn()
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -14,7 +14,7 @@ func TestLoadingExternalDebugInfo(t *testing.T) {
fixture := protest.BuildFixture("locationsprog", 0) fixture := protest.BuildFixture("locationsprog", 0)
defer os.Remove(fixture.Path) defer os.Remove(fixture.Path)
stripAndCopyDebugInfo(fixture, t) 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

@ -69,13 +69,13 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu
switch testBackend { switch testBackend {
case "native": 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": 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": case "rr":
protest.MustHaveRecordingAllowed(t) protest.MustHaveRecordingAllowed(t)
t.Log("recording") 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) t.Logf("replaying %q", tracedir)
default: default:
t.Fatal("unknown backend") t.Fatal("unknown backend")
@ -2102,9 +2102,9 @@ func TestUnsupportedArch(t *testing.T) {
switch testBackend { switch testBackend {
case "native": case "native":
p, err = native.Launch([]string{outfile}, ".", false, []string{}, "") p, err = native.Launch([]string{outfile}, ".", false, []string{}, "", [3]string{})
case "lldb": case "lldb":
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "") p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "", [3]string{})
default: default:
t.Skip("test not valid for this backend") t.Skip("test not valid for this backend")
} }

@ -132,16 +132,22 @@ See also: "help on", "help cond" and "help clear"`},
For recorded targets the command takes the following forms: For recorded targets the command takes the following forms:
restart resets ot the start of the recording restart resets ot the start of the recording
restart [checkpoint] resets the recording to the given checkpoint 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: 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 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. 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{"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."}, {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 rerecord := false
resetArgs := false resetArgs := false
newArgv := []string{} newArgv := []string{}
newRedirects := [3]string{}
restartPos := "" restartPos := ""
if len(v) > 0 { if len(v) > 0 {
@ -978,7 +985,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error {
rerecord = true rerecord = true
if len(v) == 2 { if len(v) == 2 {
var err error var err error
resetArgs, newArgv, err = parseNewArgv(v[1]) resetArgs, newArgv, newRedirects, err = parseNewArgv(v[1])
if err != nil { if err != nil {
return err 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 return err
} }
@ -1015,12 +1022,12 @@ func parseOptionalCount(arg string) (int64, error) {
} }
func restartLive(t *Term, ctx callContext, args string) error { func restartLive(t *Term, ctx callContext, args string) error {
resetArgs, newArgv, err := parseNewArgv(args) resetArgs, newArgv, newRedirects, err := parseNewArgv(args)
if err != nil { if err != nil {
return err return err
} }
if err := restartIntl(t, false, "", resetArgs, newArgv); err != nil { if err := restartIntl(t, false, "", resetArgs, newArgv, newRedirects); err != nil {
return err return err
} }
@ -1028,8 +1035,8 @@ func restartLive(t *Term, ctx callContext, args string) error {
return nil return nil
} }
func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error { 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, false) discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, newRedirects, false)
if err != nil { if err != nil {
return err return err
} }
@ -1039,9 +1046,9 @@ func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newA
return nil 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 == "" { if args == "" {
return false, nil, nil return false, nil, [3]string{}, nil
} }
v, err := argv.Argv(args, v, err := argv.Argv(args,
func(s string) (string, error) { func(s string) (string, error) {
@ -1049,22 +1056,58 @@ func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) {
}, },
nil) nil)
if err != nil { if err != nil {
return false, nil, err return false, nil, [3]string{}, err
} }
if len(v) != 1 { 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] w := v[0]
if len(w) == 0 { if len(w) == 0 {
return false, nil, nil return false, nil, [3]string{}, nil
} }
if w[0] == "-noargs" { if w[0] == "-noargs" {
if len(w) > 1 { 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) { func printcontextNoState(t *Term) {

@ -1138,3 +1138,50 @@ func TestPrintCastToInterface(t *testing.T) {
t.Logf("%q", out) 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) 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 { for _, kv := range kwargs {
var err error var err error
switch kv[0].(starlark.String) { switch kv[0].(starlark.String) {
@ -1110,6 +1116,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord")
case "Rebuild": case "Rebuild":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild") err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild")
case "NewRedirects":
err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewRedirects, "NewRedirects")
default: default:
err = fmt.Errorf("unknown argument %q", kv[0]) 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. // Restarts program. Set true if you want to rebuild the process we are debugging.
Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error)
// Restarts program from the specified position. // 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 returns the current debugger state.
GetState() (*api.DebuggerState, error) GetState() (*api.DebuggerState, error)

@ -117,6 +117,9 @@ type Config struct {
// ExecuteKind contains the kind of the executed program. // ExecuteKind contains the kind of the executed program.
ExecuteKind ExecuteKind 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 // 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 { switch d.config.Backend {
case "native": 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": 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": case "rr":
if d.target != nil { if d.target != nil {
// restart should not call us if the backend is 'rr' // restart should not call us if the backend is 'rr'
panic("internal error: call to Launch with rr backend and target already exists") 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 { if err != nil {
return nil, err return nil, err
} }
@ -265,9 +268,9 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error)
case "default": case "default":
if runtime.GOOS == "darwin" { 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: default:
return nil, fmt.Errorf("unknown backend %q", d.config.Backend) 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 // 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 // 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. // 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() d.targetMutex.Lock()
defer d.targetMutex.Unlock() defer d.targetMutex.Unlock()
@ -437,6 +440,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
if resetArgs { if resetArgs {
d.processArgs = append([]string{d.processArgs[0]}, newArgs...) d.processArgs = append([]string{d.processArgs[0]}, newArgs...)
d.config.Redirects = newRedirects
} }
var p *proc.Target var p *proc.Target
var err error var err error
@ -460,7 +464,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []
} }
if recorded { 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 { if err2 != nil {
return nil, err2 return nil, err2
} }

@ -46,7 +46,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error {
if s.config.Debugger.AttachPid != 0 { if s.config.Debugger.AttachPid != 0 {
return errors.New("cannot restart process Delve did not create") 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 return err
} }

@ -62,13 +62,13 @@ func (c *RPCClient) Detach(kill bool) error {
func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) { func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) {
out := new(RestartOut) 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 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) 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 return out.DiscardedBreakpoints, err
} }

@ -68,7 +68,7 @@ type RestartIn struct {
// otherwise it's an event number. Only valid for recorded targets. // otherwise it's an event number. Only valid for recorded targets.
Position string Position string
// ResetArgs tell whether NewArgs should take effect. // ResetArgs tell whether NewArgs and NewRedirects should take effect.
ResetArgs bool ResetArgs bool
// NewArgs are arguments to launch a new process. They replace only the // NewArgs are arguments to launch a new process. They replace only the
// argv[1] and later. Argv[0] cannot be changed. // 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 // When Rebuild is set the process will be build again
Rebuild bool Rebuild bool
NewRedirects [3]string
} }
type RestartOut struct { type RestartOut struct {
@ -93,7 +95,7 @@ func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) {
} }
var out RestartOut var out RestartOut
var err error 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) 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)) { 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) 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" { if testBackend == "rr" {
protest.MustHaveRecordingAllowed(t) protest.MustHaveRecordingAllowed(t)
} }
@ -69,6 +69,11 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
buildFlags |= protest.BuildModePIE buildFlags |= protest.BuildModePIE
} }
fixture = protest.BuildFixture(name, buildFlags) 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{ server := rpccommon.NewServer(&service.Config{
Listener: listener, Listener: listener,
ProcessArgs: []string{fixture.Path}, ProcessArgs: []string{fixture.Path},
@ -78,6 +83,7 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
Packages: []string{fixture.Source}, 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. BuildFlags: "", // build flags can be an empty string here because the only test that uses it, does not set special flags.
ExecuteKind: debugger.ExecutingGeneratedFile, ExecuteKind: debugger.ExecutingGeneratedFile,
Redirects: redirects,
}, },
}) })
if err := server.Run(); err != nil { if err := server.Run(); err != nil {
@ -86,8 +92,8 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie
return clientConn, fixture return clientConn, fixture
} }
func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, fn func(c service.Client, fixture protest.Fixture)) { 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) clientConn, fixture := startServer(name, buildFlags, t, redirects)
client := rpc2.NewClientFromConn(clientConn) client := rpc2.NewClientFromConn(clientConn)
defer func() { defer func() {
client.Detach(true) 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. // In the original fixture file the env var tested for is SOMEVAR.
os.Setenv("SOMEVAR", "bah") 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() <-c.Continue()
var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig) 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) 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 // Upper case
findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) 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) { func TestUnknownMethodCall(t *testing.T) {
clientConn, _ := startServer("continuetestprog", 0, t) clientConn, _ := startServer("continuetestprog", 0, t, [3]string{})
client := &brokenRPCClient{jsonrpc.NewClient(clientConn)} client := &brokenRPCClient{jsonrpc.NewClient(clientConn)}
client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{}) client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{})
defer client.Detach(true) defer client.Detach(true)
@ -1950,7 +1956,7 @@ func TestRerecord(t *testing.T) {
t0 := gett() t0 := gett()
_, err = c.RestartFrom(false, "", false, nil, false) _, err = c.RestartFrom(false, "", false, nil, [3]string{}, false)
assertNoError(err, t, "First restart") assertNoError(err, t, "First restart")
t1 := gett() 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 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") assertNoError(err, t, "Second restart")
t2 := gett() t2 := gett()
@ -2025,7 +2031,7 @@ func TestStopRecording(t *testing.T) {
// try rerecording // try rerecording
go func() { 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... 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 // Clearing a logical breakpoint should clear all associated physical
// breakpoints. // breakpoints.
// Issue #1955. // 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"}) bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.inlineThis"})
assertNoError(err, t, "CreateBreakpoint()") assertNoError(err, t, "CreateBreakpoint()")
t.Logf("breakpoint set at %#v", bp.Addrs) 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 var tracedir string
switch testBackend { switch testBackend {
case "native": 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": 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": case "rr":
protest.MustHaveRecordingAllowed(t) protest.MustHaveRecordingAllowed(t)
t.Log("recording") 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) t.Logf("replaying %q", tracedir)
default: default:
t.Fatalf("unknown backend %q", testBackend) t.Fatalf("unknown backend %q", testBackend)