diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index ea721fd6..89e5691c 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -412,17 +412,23 @@ Restart process. For recorded targets the command takes the following forms: - restart resets ot the start of the recording - restart [checkpoint] resets the recording to the given checkpoint - restart -r [newargv...] re-records the target process + restart resets ot the start of the recording + restart [checkpoint] resets the recording to the given checkpoint + restart -r [newargv...] [redirects...] re-records the target process For live targets the command takes the following forms: - restart [newargv...] restarts the process + restart [newargv...] [redirects...] restarts the process If newargv is omitted the process is restarted (or re-recorded) with the same argument vector. If -noargs is specified instead, the argument vector is cleared. +A list of file redirections can be specified after the new argument list to override the redirections defined using the '--redirect' command line option. A syntax similar to Unix shells is used: + + 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 diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index ea64a6ce..f4a286e2 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -51,7 +51,7 @@ threads() | Equivalent to API call [ListThreads](https://godoc.org/github.com/go types(Filter) | Equivalent to API call [ListTypes](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTypes) process_pid() | Equivalent to API call [ProcessPid](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ProcessPid) recorded() | Equivalent to API call [Recorded](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Recorded) -restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) +restart(Position, ResetArgs, NewArgs, Rerecord, Rebuild, NewRedirects) | Equivalent to API call [Restart](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Restart) set_expr(Scope, Symbol, Value) | Equivalent to API call [Set](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Set) stacktrace(Id, Depth, Full, Defers, Opts, Cfg) | Equivalent to API call [Stacktrace](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Stacktrace) state(NonBlocking) | Equivalent to API call [State](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.State) diff --git a/Documentation/usage/dlv.md b/Documentation/usage/dlv.md index 1ecac545..f7e72b12 100644 --- a/Documentation/usage/dlv.md +++ b/Documentation/usage/dlv.md @@ -19,19 +19,21 @@ Pass flags to the program you are debugging using `--`, for example: ### Options ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_attach.md b/Documentation/usage/dlv_attach.md index 18c3a0f8..730cc6ef 100644 --- a/Documentation/usage/dlv_attach.md +++ b/Documentation/usage/dlv_attach.md @@ -25,19 +25,21 @@ dlv attach pid [executable] ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_backend.md b/Documentation/usage/dlv_backend.md index 35cebba7..09e99224 100644 --- a/Documentation/usage/dlv_backend.md +++ b/Documentation/usage/dlv_backend.md @@ -18,19 +18,21 @@ are: ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_connect.md b/Documentation/usage/dlv_connect.md index 22854725..9b1cbe99 100644 --- a/Documentation/usage/dlv_connect.md +++ b/Documentation/usage/dlv_connect.md @@ -14,19 +14,21 @@ dlv connect addr ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_core.md b/Documentation/usage/dlv_core.md index 6bf3e5d5..c625bec5 100644 --- a/Documentation/usage/dlv_core.md +++ b/Documentation/usage/dlv_core.md @@ -20,19 +20,21 @@ dlv core ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_dap.md b/Documentation/usage/dlv_dap.md index a3144a69..931d7a27 100644 --- a/Documentation/usage/dlv_dap.md +++ b/Documentation/usage/dlv_dap.md @@ -21,19 +21,21 @@ dlv dap ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_debug.md b/Documentation/usage/dlv_debug.md index f007ff72..c5ea8f71 100644 --- a/Documentation/usage/dlv_debug.md +++ b/Documentation/usage/dlv_debug.md @@ -27,19 +27,21 @@ dlv debug [package] ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_exec.md b/Documentation/usage/dlv_exec.md index aad2582c..350e4183 100644 --- a/Documentation/usage/dlv_exec.md +++ b/Documentation/usage/dlv_exec.md @@ -27,19 +27,21 @@ dlv exec ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_log.md b/Documentation/usage/dlv_log.md index 0b6985e4..503f6ed1 100644 --- a/Documentation/usage/dlv_log.md +++ b/Documentation/usage/dlv_log.md @@ -33,19 +33,21 @@ and dap modes. ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_redirect.md b/Documentation/usage/dlv_redirect.md new file mode 100644 index 00000000..eda39268 --- /dev/null +++ b/Documentation/usage/dlv_redirect.md @@ -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. + diff --git a/Documentation/usage/dlv_replay.md b/Documentation/usage/dlv_replay.md index 40713e83..16b5fc0d 100644 --- a/Documentation/usage/dlv_replay.md +++ b/Documentation/usage/dlv_replay.md @@ -18,19 +18,21 @@ dlv replay [trace directory] ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_run.md b/Documentation/usage/dlv_run.md index e729f099..979c1e9f 100644 --- a/Documentation/usage/dlv_run.md +++ b/Documentation/usage/dlv_run.md @@ -14,19 +14,21 @@ dlv run ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index 931b85bf..87737be8 100644 --- a/Documentation/usage/dlv_test.md +++ b/Documentation/usage/dlv_test.md @@ -25,19 +25,21 @@ dlv test [package] ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_trace.md b/Documentation/usage/dlv_trace.md index ae237c18..8e7cc4d4 100644 --- a/Documentation/usage/dlv_trace.md +++ b/Documentation/usage/dlv_trace.md @@ -32,19 +32,21 @@ dlv trace [package] regexp ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/Documentation/usage/dlv_version.md b/Documentation/usage/dlv_version.md index 1cee5938..c0ab1f4b 100644 --- a/Documentation/usage/dlv_version.md +++ b/Documentation/usage/dlv_version.md @@ -14,19 +14,21 @@ dlv version ### Options inherited from parent commands ``` - --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) - --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) - --wd string Working directory for running the program. + --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 diff --git a/_fixtures/redirect-input.txt b/_fixtures/redirect-input.txt new file mode 100644 index 00000000..c674e138 --- /dev/null +++ b/_fixtures/redirect-input.txt @@ -0,0 +1 @@ +Redirect test \ No newline at end of file diff --git a/_fixtures/redirect.go b/_fixtures/redirect.go new file mode 100644 index 00000000..f25a7e8e --- /dev/null +++ b/_fixtures/redirect.go @@ -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()) +} diff --git a/cmd/dlv/cmds/cmds_test.go b/cmd/dlv/cmds/cmds_test.go new file mode 100644 index 00000000..d2da8e4a --- /dev/null +++ b/cmd/dlv/cmds/cmds_test.go @@ -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 + } + } + } + } +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index e911cf0b..e28a0a16 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -26,6 +26,7 @@ import ( "github.com/go-delve/delve/service/debugger" "github.com/go-delve/delve/service/rpc2" "github.com/go-delve/delve/service/rpccommon" + "github.com/mattn/go-isatty" "github.com/spf13/cobra" ) @@ -74,6 +75,11 @@ var ( traceTestBinary bool traceStackDepth int + // redirect specifications for target process + redirects []string + + allowNonTerminalInteractive bool + conf *config.Config ) @@ -123,6 +129,8 @@ func New(docCall bool) *cobra.Command { rootCommand.PersistentFlags().BoolVarP(&checkGoVersion, "check-go-version", "", true, "Checks that the version of Go in use is compatible with Delve.") rootCommand.PersistentFlags().BoolVarP(&checkLocalConnUser, "only-same-user", "", true, "Only connections from the same user that started this instance of Delve are allowed to connect.") rootCommand.PersistentFlags().StringVar(&backend, "backend", "default", `Backend selection (see 'dlv help backend').`) + rootCommand.PersistentFlags().StringArrayVarP(&redirects, "redirect", "r", []string{}, "Specifies redirect rules for target process (see 'dlv help redirect')") + rootCommand.PersistentFlags().BoolVar(&allowNonTerminalInteractive, "allow-non-terminal-interactive", false, "Allows interactive sessions of Delve that don't have a terminal as stdin, stdout and stderr") // 'attach' subcommand. attachCommand := &cobra.Command{ @@ -358,6 +366,23 @@ otherwise as a file path. This option will also redirect the "server listening at" message in headless and dap modes. +`, + }) + + rootCommand.AddCommand(&cobra.Command{ + Use: "redirect", + Short: "Help about file redirection.", + Long: `The standard file descriptors of the target process can be controlled using the '-r' and '--tty' arguments. + +The --tty argument allows redirecting all standard descriptors to a terminal, specified as an argument to --tty. + +The syntax for '-r' argument is: + + -r [source:]destination + +Where source is one of 'stdin', 'stdout' or 'stderr' and destination is the path to a file. If the source is omitted stdin is used implicitly. + +File redirects can also be changed using the 'restart' command. `, }) @@ -745,9 +770,34 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile acceptMulti = false } + if !headless && !allowNonTerminalInteractive { + for _, f := range []struct { + name string + file *os.File + }{{"Stdin", os.Stdin}, {"Stdout", os.Stdout}, {"Stderr", os.Stderr}} { + if f.file == nil { + continue + } + if !isatty.IsTerminal(f.file.Fd()) { + fmt.Fprintf(os.Stderr, "%s is not a terminal, use '-r' to specify redirects for the target process or --allow-non-terminal-interactive=true if you really want to specify a redirect for Delve\n", f.name) + return 1 + } + } + } + + if len(redirects) > 0 && tty != "" { + fmt.Fprintf(os.Stderr, "Can not use -r and --tty together\n") + return 1 + } + + redirects, err := parseRedirects(redirects) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return 1 + } + var listener net.Listener var clientConn net.Conn - var err error // Make a TCP listener if headless { @@ -791,6 +841,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile DebugInfoDirectories: conf.DebugInfoDirectories, CheckGoVersion: checkGoVersion, TTY: tty, + Redirects: redirects, }, }) default: @@ -832,3 +883,24 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile return connect(listener.Addr().String(), clientConn, conf, kind) } + +func parseRedirects(redirects []string) ([3]string, error) { + r := [3]string{} + names := [3]string{"stdin", "stdout", "stderr"} + for _, redirect := range redirects { + idx := 0 + for i, name := range names { + pfx := name + ":" + if strings.HasPrefix(redirect, pfx) { + idx = i + redirect = redirect[len(pfx):] + break + } + } + if r[idx] != "" { + return r, fmt.Errorf("redirect error: %s redirected twice", names[idx]) + } + r[idx] = redirect + } + return r, nil +} diff --git a/cmd/dlv/dlv_test.go b/cmd/dlv/dlv_test.go index 137e5c1a..c6609f6c 100644 --- a/cmd/dlv/dlv_test.go +++ b/cmd/dlv/dlv_test.go @@ -121,7 +121,7 @@ func testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout var stdoutBuf, stderrBuf bytes.Buffer buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest") - c := []string{dlvbin, "debug"} + c := []string{dlvbin, "debug", "--allow-non-terminal-interactive=true"} debugbin := filepath.Join(buildtestdir, "__debug_bin") if output != "" { c = append(c, "--output", output) @@ -734,7 +734,7 @@ func TestDlvTestChdir(t *testing.T) { defer os.RemoveAll(tmpdir) fixtures := protest.FindFixturesDir() - cmd := exec.Command(dlvbin, "test", filepath.Join(fixtures, "buildtest"), "--", "-test.v") + cmd := exec.Command(dlvbin, "--allow-non-terminal-interactive=true", "test", filepath.Join(fixtures, "buildtest"), "--", "-test.v") cmd.Stdin = strings.NewReader("continue\nexit\n") out, err := cmd.CombinedOutput() if err != nil { diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index cdbf554a..2ff9bedc 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -342,7 +342,7 @@ func getLdEnvVars() []string { // LLDBLaunch starts an instance of lldb-server and connects to it, asking // it to launch the specified target program with the specified arguments // (cmd) on the specified directory wd. -func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) { +func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { if runtime.GOOS == "windows" { return nil, ErrUnsupportedOS } @@ -371,12 +371,32 @@ func LLDBLaunch(cmd []string, wd string, foreground bool, debugInfoDirs []string ldEnvVars := getLdEnvVars() args := make([]string, 0, len(cmd)+4+len(ldEnvVars)) args = append(args, ldEnvVars...) - if foreground { - args = append(args, "--stdio-path", "/dev/tty") - } + if tty != "" { args = append(args, "--stdio-path", tty) + } else { + found := [3]bool{} + names := [3]string{"stdin", "stdout", "stderr"} + for i := range redirects { + if redirects[i] != "" { + found[i] = true + args = append(args, fmt.Sprintf("--%s-path", names[i]), redirects[i]) + } + } + + if foreground { + if !found[0] && !found[1] && !found[2] { + args = append(args, "--stdio-path", "/dev/tty") + } else { + for i := range found { + if !found[i] { + args = append(args, fmt.Sprintf("--%s-path", names[i]), "/dev/tty") + } + } + } + } } + if logflags.LLDBServerOutput() { args = append(args, "-g", "-l", "stdout") } diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go index 7b6198f2..8a47aa00 100644 --- a/pkg/proc/gdbserial/rr.go +++ b/pkg/proc/gdbserial/rr.go @@ -21,7 +21,7 @@ import ( // program. Returns a run function which will actually record the program, a // stop function which will prematurely terminate the recording of the // program. -func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error), stop func() error, err error) { +func RecordAsync(cmd []string, wd string, quiet bool, redirects [3]string) (run func() (string, error), stop func() error, err error) { if err := checkRRAvailabe(); err != nil { return nil, nil, err } @@ -35,10 +35,10 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error args = append(args, "record", "--print-trace-dir=3") args = append(args, cmd...) rrcmd := exec.Command("rr", args...) - rrcmd.Stdin = os.Stdin - if !quiet { - rrcmd.Stdout = os.Stdout - rrcmd.Stderr = os.Stderr + var closefn func() + rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(redirects, quiet) + if err != nil { + return nil, nil, err } rrcmd.ExtraFiles = []*os.File{wfd} rrcmd.Dir = wd @@ -51,6 +51,7 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error run = func() (string, error) { err := rrcmd.Run() + closefn() _ = wfd.Close() tracedir := <-tracedirChan return tracedir, err @@ -63,10 +64,57 @@ func RecordAsync(cmd []string, wd string, quiet bool) (run func() (string, error return run, stop, nil } +func openRedirects(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) { + toclose := []*os.File{} + + if redirects[0] != "" { + stdin, err = os.Open(redirects[0]) + if err != nil { + return nil, nil, nil, nil, err + } + toclose = append(toclose, stdin) + } else { + stdin = os.Stdin + } + + create := func(path string, dflt *os.File) *os.File { + if path == "" { + if quiet { + return nil + } + return dflt + } + var f *os.File + f, err = os.Create(path) + if f != nil { + toclose = append(toclose, f) + } + return f + } + + stdout = create(redirects[1], os.Stdout) + if err != nil { + return nil, nil, nil, nil, err + } + + stderr = create(redirects[2], os.Stderr) + if err != nil { + return nil, nil, nil, nil, err + } + + closefn = func() { + for _, f := range toclose { + _ = f.Close() + } + } + + return stdin, stdout, stderr, closefn, nil +} + // Record uses rr to record the execution of the specified program and // returns the trace directory's path. -func Record(cmd []string, wd string, quiet bool) (tracedir string, err error) { - run, _, err := RecordAsync(cmd, wd, quiet) +func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir string, err error) { + run, _, err := RecordAsync(cmd, wd, quiet, redirects) if err != nil { return "", err } @@ -286,8 +334,8 @@ func splitQuotedFields(in string) []string { } // RecordAndReplay acts like calling Record and then Replay. -func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string) (*proc.Target, string, error) { - tracedir, err := Record(cmd, wd, quiet) +func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) { + tracedir, err := Record(cmd, wd, quiet, redirects) if tracedir == "" { return nil, "", err } diff --git a/pkg/proc/gdbserial/rr_test.go b/pkg/proc/gdbserial/rr_test.go index eed5ec54..3382d40d 100644 --- a/pkg/proc/gdbserial/rr_test.go +++ b/pkg/proc/gdbserial/rr_test.go @@ -30,7 +30,7 @@ func withTestRecording(name string, t testing.TB, fn func(t *proc.Target, fixtur t.Skip("test skipped, rr not found") } t.Log("recording") - p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}) + p, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{}) if err != nil { t.Fatal("Launch():", err) } diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go index ef27a302..30ccb8fa 100644 --- a/pkg/proc/native/nonative_darwin.go +++ b/pkg/proc/native/nonative_darwin.go @@ -12,7 +12,7 @@ import ( var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation") // Launch returns ErrNativeBackendDisabled. -func Launch(_ []string, _ string, _ bool, _ []string, _ string) (*proc.Target, error) { +func Launch(_ []string, _ string, _ bool, _ []string, _ string, _ [3]string) (*proc.Target, error) { return nil, ErrNativeBackendDisabled } diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 9e4aeb61..3a80b86e 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -320,3 +320,47 @@ func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uin _, err := thread.WriteMemory(uintptr(addr), dbp.bi.Arch.BreakpointInstruction()) return err } + +func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) { + toclose := []*os.File{} + + if redirects[0] != "" { + stdin, err = os.Open(redirects[0]) + if err != nil { + return nil, nil, nil, nil, err + } + toclose = append(toclose, stdin) + } else if foreground { + stdin = os.Stdin + } + + create := func(path string, dflt *os.File) *os.File { + if path == "" { + return dflt + } + var f *os.File + f, err = os.Create(path) + if f != nil { + toclose = append(toclose, f) + } + return f + } + + stdout = create(redirects[1], os.Stdout) + if err != nil { + return nil, nil, nil, nil, err + } + + stderr = create(redirects[2], os.Stderr) + if err != nil { + return nil, nil, nil, nil, err + } + + closefn = func() { + for _, f := range toclose { + _ = f.Close() + } + } + + return stdin, stdout, stderr, closefn, nil +} diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index ae7681cf..d8dfb758 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -37,7 +37,7 @@ type osProcessDetails struct { // custom fork/exec process in order to take advantage of // PT_SIGEXC on Darwin which will turn Unix signals into // Mach exceptions. -func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*proc.Target, error) { +func Launch(cmd []string, wd string, foreground bool, _ []string, _ string, _ [3]string) (*proc.Target, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err diff --git a/pkg/proc/native/proc_freebsd.go b/pkg/proc/native/proc_freebsd.go index 6ae123fd..f2fe628a 100644 --- a/pkg/proc/native/proc_freebsd.go +++ b/pkg/proc/native/proc_freebsd.go @@ -6,7 +6,6 @@ package native import "C" import ( "fmt" - "os" "os/exec" "os/signal" "strings" @@ -43,13 +42,18 @@ type osProcessDetails struct { // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) { +func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { var ( process *exec.Cmd err error ) - if !isatty.IsTerminal(os.Stdin.Fd()) { + stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground) + if err != nil { + return nil, err + } + + if stdin == nil || !isatty.IsTerminal(stdin.Fd()) { // exec.(*Process).Start will fail if we try to send a process to // foreground but we are not attached to a terminal. foreground = false @@ -64,13 +68,13 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt dbp.execPtraceFunc(func() { process = exec.Command(cmd[0]) process.Args = cmd - process.Stdout = os.Stdout - process.Stderr = os.Stderr + process.Stdin = stdin + process.Stdout = stdout + process.Stderr = stderr process.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true, Foreground: foreground} process.Env = proc.DisableAsyncPreemptEnv() if foreground { signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) - process.Stdin = os.Stdin } if tty != "" { dbp.ctty, err = attachProcessToTTY(process, tty) @@ -83,6 +87,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt } err = process.Start() }) + closefn() if err != nil { return nil, err } diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 1975753b..605819bc 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -49,13 +49,18 @@ type osProcessDetails struct { // to be supplied to that process. `wd` is working directory of the program. // If the DWARF information cannot be found in the binary, Delve will look // for external debug files in the directories passed in. -func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string) (*proc.Target, error) { +func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tty string, redirects [3]string) (*proc.Target, error) { var ( process *exec.Cmd err error ) - if !isatty.IsTerminal(os.Stdin.Fd()) { + stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground) + if err != nil { + return nil, err + } + + if stdin == nil || !isatty.IsTerminal(stdin.Fd()) { // exec.(*Process).Start will fail if we try to send a process to // foreground but we are not attached to a terminal. foreground = false @@ -70,8 +75,9 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt dbp.execPtraceFunc(func() { process = exec.Command(cmd[0]) process.Args = cmd - process.Stdout = os.Stdout - process.Stderr = os.Stderr + process.Stdin = stdin + process.Stdout = stdout + process.Stderr = stderr process.SysProcAttr = &syscall.SysProcAttr{ Ptrace: true, Setpgid: true, @@ -79,7 +85,6 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt } if foreground { signal.Ignore(syscall.SIGTTOU, syscall.SIGTTIN) - process.Stdin = os.Stdin } if tty != "" { dbp.ctty, err = attachProcessToTTY(process, tty) @@ -92,6 +97,7 @@ func Launch(cmd []string, wd string, foreground bool, debugInfoDirs []string, tt } err = process.Start() }) + closefn() if err != nil { return nil, err } diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index cf51109d..133d218f 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -21,7 +21,7 @@ type osProcessDetails struct { } // Launch creates and begins debugging a new process. -func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*proc.Target, error) { +func Launch(cmd []string, wd string, foreground bool, _ []string, _ string, redirects [3]string) (*proc.Target, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err @@ -29,12 +29,17 @@ func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*pr env := proc.DisableAsyncPreemptEnv() + stdin, stdout, stderr, closefn, err := openRedirects(redirects, true) + if err != nil { + return nil, err + } + var p *os.Process dbp := newProcess(0) dbp.execPtraceFunc(func() { attr := &os.ProcAttr{ Dir: wd, - Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, + Files: []*os.File{stdin, stdout, stderr}, Sys: &syscall.SysProcAttr{ CreationFlags: _DEBUG_ONLY_THIS_PROCESS, }, @@ -42,6 +47,7 @@ func Launch(cmd []string, wd string, foreground bool, _ []string, _ string) (*pr } p, err = os.StartProcess(argv0Go, cmd, attr) }) + closefn() if err != nil { return nil, err } diff --git a/pkg/proc/proc_linux_test.go b/pkg/proc/proc_linux_test.go index 6ea02e0f..4f8b9458 100644 --- a/pkg/proc/proc_linux_test.go +++ b/pkg/proc/proc_linux_test.go @@ -14,7 +14,7 @@ func TestLoadingExternalDebugInfo(t *testing.T) { fixture := protest.BuildFixture("locationsprog", 0) defer os.Remove(fixture.Path) stripAndCopyDebugInfo(fixture, t) - p, err := native.Launch(append([]string{fixture.Path}, ""), "", false, []string{filepath.Dir(fixture.Path)}, "") + p, err := native.Launch(append([]string{fixture.Path}, ""), "", false, []string{filepath.Dir(fixture.Path)}, "", [3]string{}) if err != nil { t.Fatal(err) } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index 4cf74b2d..fedc1fa1 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -69,13 +69,13 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu switch testBackend { case "native": - p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "") + p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{}) case "lldb": - p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "") + p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{}) case "rr": protest.MustHaveRecordingAllowed(t) t.Log("recording") - p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}) + p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) t.Logf("replaying %q", tracedir) default: t.Fatal("unknown backend") @@ -2102,9 +2102,9 @@ func TestUnsupportedArch(t *testing.T) { switch testBackend { case "native": - p, err = native.Launch([]string{outfile}, ".", false, []string{}, "") + p, err = native.Launch([]string{outfile}, ".", false, []string{}, "", [3]string{}) case "lldb": - p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "") + p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", false, []string{}, "", [3]string{}) default: t.Skip("test not valid for this backend") } diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 18db7511..3dcb9c54 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -132,16 +132,22 @@ See also: "help on", "help cond" and "help clear"`}, For recorded targets the command takes the following forms: - restart resets ot the start of the recording - restart [checkpoint] resets the recording to the given checkpoint - restart -r [newargv...] re-records the target process + restart resets ot the start of the recording + restart [checkpoint] resets the recording to the given checkpoint + restart -r [newargv...] [redirects...] re-records the target process For live targets the command takes the following forms: - restart [newargv...] restarts the process + restart [newargv...] [redirects...] restarts the process If newargv is omitted the process is restarted (or re-recorded) with the same argument vector. If -noargs is specified instead, the argument vector is cleared. + +A list of file redirections can be specified after the new argument list to override the redirections defined using the '--redirect' command line option. A syntax similar to Unix shells is used: + + output.txt redirects the standard output of the target process to output.txt + 2>error.txt redirects the standard error of the target process to error.txt `}, {aliases: []string{"rebuild"}, group: runCmds, cmdFn: c.rebuild, allowedPrefixes: revPrefix, helpMsg: "Rebuild the target executable and restarts it. It does not work if the executable was not built by delve."}, {aliases: []string{"continue", "c"}, group: runCmds, cmdFn: c.cont, allowedPrefixes: revPrefix, helpMsg: "Run until breakpoint or program termination."}, @@ -971,6 +977,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error { rerecord := false resetArgs := false newArgv := []string{} + newRedirects := [3]string{} restartPos := "" if len(v) > 0 { @@ -978,7 +985,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error { rerecord = true if len(v) == 2 { var err error - resetArgs, newArgv, err = parseNewArgv(v[1]) + resetArgs, newArgv, newRedirects, err = parseNewArgv(v[1]) if err != nil { return err } @@ -991,7 +998,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error { } } - if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv); err != nil { + if err := restartIntl(t, rerecord, restartPos, resetArgs, newArgv, newRedirects); err != nil { return err } @@ -1015,12 +1022,12 @@ func parseOptionalCount(arg string) (int64, error) { } func restartLive(t *Term, ctx callContext, args string) error { - resetArgs, newArgv, err := parseNewArgv(args) + resetArgs, newArgv, newRedirects, err := parseNewArgv(args) if err != nil { return err } - if err := restartIntl(t, false, "", resetArgs, newArgv); err != nil { + if err := restartIntl(t, false, "", resetArgs, newArgv, newRedirects); err != nil { return err } @@ -1028,8 +1035,8 @@ func restartLive(t *Term, ctx callContext, args string) error { return nil } -func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string) error { - discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, false) +func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newArgv []string, newRedirects [3]string) error { + discarded, err := t.client.RestartFrom(rerecord, restartPos, resetArgs, newArgv, newRedirects, false) if err != nil { return err } @@ -1039,9 +1046,9 @@ func restartIntl(t *Term, rerecord bool, restartPos string, resetArgs bool, newA return nil } -func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) { +func parseNewArgv(args string) (resetArgs bool, newArgv []string, newRedirects [3]string, err error) { if args == "" { - return false, nil, nil + return false, nil, [3]string{}, nil } v, err := argv.Argv(args, func(s string) (string, error) { @@ -1049,22 +1056,58 @@ func parseNewArgv(args string) (resetArgs bool, newArgv []string, err error) { }, nil) if err != nil { - return false, nil, err + return false, nil, [3]string{}, err } if len(v) != 1 { - return false, nil, fmt.Errorf("Illegal commandline '%s'", args) + return false, nil, [3]string{}, fmt.Errorf("illegal commandline '%s'", args) } w := v[0] if len(w) == 0 { - return false, nil, nil + return false, nil, [3]string{}, nil } if w[0] == "-noargs" { if len(w) > 1 { - return false, nil, fmt.Errorf("Too many arguments to restart") + return false, nil, [3]string{}, fmt.Errorf("too many arguments to restart") } - return true, nil, nil + return true, nil, [3]string{}, nil } - return true, w, nil + redirs := [3]string{} + for len(w) > 0 { + var found bool + var err error + w, found, err = parseOneRedirect(w, &redirs) + if err != nil { + return false, nil, [3]string{}, err + } + if !found { + break + } + } + return true, w, redirs, nil +} + +func parseOneRedirect(w []string, redirs *[3]string) ([]string, bool, error) { + prefixes := []string{"<", ">", "2>"} + names := []string{"stdin", "stdout", "stderr"} + if len(w) >= 2 { + for _, prefix := range prefixes { + if w[len(w)-2] == prefix { + w[len(w)-2] += w[len(w)-1] + w = w[:len(w)-1] + break + } + } + } + for i, prefix := range prefixes { + if strings.HasPrefix(w[len(w)-1], prefix) { + if redirs[i] != "" { + return nil, false, fmt.Errorf("redirect error: %s redirected twice", names[i]) + } + redirs[i] = w[len(w)-1][len(prefix):] + return w[:len(w)-1], true, nil + } + } + return w, false, nil } func printcontextNoState(t *Term) { diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 5704f9d9..da9d553b 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -1138,3 +1138,50 @@ func TestPrintCastToInterface(t *testing.T) { t.Logf("%q", out) }) } + +func TestParseNewArgv(t *testing.T) { + testCases := []struct { + in string + tgtargs string + tgtredir string + tgterr string + }{ + {"-noargs", "", " | | ", ""}, + {"-noargs arg1", "", "", "too many arguments to restart"}, + {"arg1 arg2", "arg1 | arg2", " | | ", ""}, + {"arg1 arg2 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 output.txt 2>error.txt", "arg1", "input.txt | output.txt | error.txt", ""}, + {"output.txt 2>error.txt", "", "input.txt | output.txt | error.txt", ""}, + {" %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) + } + } + } +} diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index 1480507e..b4f2bff5 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -1097,6 +1097,12 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { return starlark.None, decorateError(thread, err) } } + if len(args) > 5 && args[5] != starlark.None { + err := unmarshalStarlarkValue(args[5], &rpcArgs.NewRedirects, "NewRedirects") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } for _, kv := range kwargs { var err error switch kv[0].(starlark.String) { @@ -1110,6 +1116,8 @@ func (env *Env) starlarkPredeclare() starlark.StringDict { err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rerecord, "Rerecord") case "Rebuild": err = unmarshalStarlarkValue(kv[1], &rpcArgs.Rebuild, "Rebuild") + case "NewRedirects": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.NewRedirects, "NewRedirects") default: err = fmt.Errorf("unknown argument %q", kv[0]) } diff --git a/service/client.go b/service/client.go index 03efb276..7e623179 100644 --- a/service/client.go +++ b/service/client.go @@ -21,7 +21,7 @@ type Client interface { // Restarts program. Set true if you want to rebuild the process we are debugging. Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) // Restarts program from the specified position. - RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) + RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) // GetState returns the current debugger state. GetState() (*api.DebuggerState, error) diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 7a567f3f..08c465d3 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -117,6 +117,9 @@ type Config struct { // ExecuteKind contains the kind of the executed program. ExecuteKind ExecuteKind + + // Redirects specifies redirect rules for stdin, stdout and stderr + Redirects [3]string } // New creates a new Debugger. ProcessArgs specify the commandline arguments for the @@ -221,16 +224,16 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) } switch d.config.Backend { case "native": - return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY) + return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects) case "lldb": - return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY)) + return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects)) case "rr": if d.target != nil { // restart should not call us if the backend is 'rr' panic("internal error: call to Launch with rr backend and target already exists") } - run, stop, err := gdbserial.RecordAsync(processArgs, wd, false) + run, stop, err := gdbserial.RecordAsync(processArgs, wd, false, d.config.Redirects) if err != nil { return nil, err } @@ -265,9 +268,9 @@ func (d *Debugger) Launch(processArgs []string, wd string) (*proc.Target, error) case "default": if runtime.GOOS == "darwin" { - return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY)) + return betterGdbserialLaunchError(gdbserial.LLDBLaunch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects)) } - return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY) + return native.Launch(processArgs, wd, d.config.Foreground, d.config.DebugInfoDirectories, d.config.TTY, d.config.Redirects) default: return nil, fmt.Errorf("unknown backend %q", d.config.Backend) } @@ -409,7 +412,7 @@ func (d *Debugger) detach(kill bool) error { // If the target process is a recording it will restart it from the given // position. If pos starts with 'c' it's a checkpoint ID, otherwise it's an // event number. If resetArgs is true, newArgs will replace the process args. -func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) { +func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) { d.targetMutex.Lock() defer d.targetMutex.Unlock() @@ -437,6 +440,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } if resetArgs { d.processArgs = append([]string{d.processArgs[0]}, newArgs...) + d.config.Redirects = newRedirects } var p *proc.Target var err error @@ -460,7 +464,7 @@ func (d *Debugger) Restart(rerecord bool, pos string, resetArgs bool, newArgs [] } if recorded { - run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false) + run, stop, err2 := gdbserial.RecordAsync(d.processArgs, d.config.WorkingDir, false, d.config.Redirects) if err2 != nil { return nil, err2 } diff --git a/service/rpc1/server.go b/service/rpc1/server.go index d798307d..128e7903 100644 --- a/service/rpc1/server.go +++ b/service/rpc1/server.go @@ -46,7 +46,7 @@ func (s *RPCServer) Restart(arg1 interface{}, arg2 *int) error { if s.config.Debugger.AttachPid != 0 { return errors.New("cannot restart process Delve did not create") } - _, err := s.debugger.Restart(false, "", false, nil, false) + _, err := s.debugger.Restart(false, "", false, nil, [3]string{}, false) return err } diff --git a/service/rpc2/client.go b/service/rpc2/client.go index c738df26..7c6924f0 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -62,13 +62,13 @@ func (c *RPCClient) Detach(kill bool) error { func (c *RPCClient) Restart(rebuild bool) ([]api.DiscardedBreakpoint, error) { out := new(RestartOut) - err := c.call("Restart", RestartIn{"", false, nil, false, rebuild}, out) + err := c.call("Restart", RestartIn{"", false, nil, false, rebuild, [3]string{}}, out) return out.DiscardedBreakpoints, err } -func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, rebuild bool) ([]api.DiscardedBreakpoint, error) { +func (c *RPCClient) RestartFrom(rerecord bool, pos string, resetArgs bool, newArgs []string, newRedirects [3]string, rebuild bool) ([]api.DiscardedBreakpoint, error) { out := new(RestartOut) - err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild}, out) + err := c.call("Restart", RestartIn{pos, resetArgs, newArgs, rerecord, rebuild, newRedirects}, out) return out.DiscardedBreakpoints, err } diff --git a/service/rpc2/server.go b/service/rpc2/server.go index 4d55fb17..024bd29d 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -68,7 +68,7 @@ type RestartIn struct { // otherwise it's an event number. Only valid for recorded targets. Position string - // ResetArgs tell whether NewArgs should take effect. + // ResetArgs tell whether NewArgs and NewRedirects should take effect. ResetArgs bool // NewArgs are arguments to launch a new process. They replace only the // argv[1] and later. Argv[0] cannot be changed. @@ -79,6 +79,8 @@ type RestartIn struct { // When Rebuild is set the process will be build again Rebuild bool + + NewRedirects [3]string } type RestartOut struct { @@ -93,7 +95,7 @@ func (s *RPCServer) Restart(arg RestartIn, cb service.RPCCallback) { } var out RestartOut var err error - out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.Rebuild) + out.DiscardedBreakpoints, err = s.debugger.Restart(arg.Rerecord, arg.Position, arg.ResetArgs, arg.NewArgs, arg.NewRedirects, arg.Rebuild) cb.Return(out, err) } diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 6b9daec4..3bacc717 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -54,12 +54,12 @@ func TestMain(m *testing.M) { } func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { - withTestClient2Extended(name, t, 0, func(c service.Client, fixture protest.Fixture) { + withTestClient2Extended(name, t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { fn(c) }) } -func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clientConn net.Conn, fixture protest.Fixture) { +func startServer(name string, buildFlags protest.BuildFlags, t *testing.T, redirects [3]string) (clientConn net.Conn, fixture protest.Fixture) { if testBackend == "rr" { protest.MustHaveRecordingAllowed(t) } @@ -69,6 +69,11 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie buildFlags |= protest.BuildModePIE } fixture = protest.BuildFixture(name, buildFlags) + for i := range redirects { + if redirects[i] != "" { + redirects[i] = filepath.Join(fixture.BuildDir, redirects[i]) + } + } server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{fixture.Path}, @@ -78,6 +83,7 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie Packages: []string{fixture.Source}, BuildFlags: "", // build flags can be an empty string here because the only test that uses it, does not set special flags. ExecuteKind: debugger.ExecutingGeneratedFile, + Redirects: redirects, }, }) if err := server.Run(); err != nil { @@ -86,8 +92,8 @@ func startServer(name string, buildFlags protest.BuildFlags, t *testing.T) (clie return clientConn, fixture } -func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, fn func(c service.Client, fixture protest.Fixture)) { - clientConn, fixture := startServer(name, buildFlags, t) +func withTestClient2Extended(name string, t *testing.T, buildFlags protest.BuildFlags, redirects [3]string, fn func(c service.Client, fixture protest.Fixture)) { + clientConn, fixture := startServer(name, buildFlags, t, redirects) client := rpc2.NewClientFromConn(clientConn) defer func() { client.Detach(true) @@ -223,7 +229,7 @@ func TestRestart_rebuild(t *testing.T) { // In the original fixture file the env var tested for is SOMEVAR. os.Setenv("SOMEVAR", "bah") - withTestClient2Extended("testenv", t, 0, func(c service.Client, f protest.Fixture) { + withTestClient2Extended("testenv", t, 0, [3]string{}, func(c service.Client, f protest.Fixture) { <-c.Continue() var1, err := c.EvalVariable(api.EvalScope{GoroutineID: -1}, "x", normalLoadConfig) @@ -772,7 +778,7 @@ func TestClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) }) - withTestClient2Extended("locationsUpperCase", t, 0, func(c service.Client, fixture protest.Fixture) { + withTestClient2Extended("locationsUpperCase", t, 0, [3]string{}, func(c service.Client, fixture protest.Fixture) { // Upper case findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) @@ -1895,7 +1901,7 @@ func (c *brokenRPCClient) call(method string, args, reply interface{}) error { } func TestUnknownMethodCall(t *testing.T) { - clientConn, _ := startServer("continuetestprog", 0, t) + clientConn, _ := startServer("continuetestprog", 0, t, [3]string{}) client := &brokenRPCClient{jsonrpc.NewClient(clientConn)} client.call("SetApiVersion", api.SetAPIVersionIn{APIVersion: 2}, &api.SetAPIVersionOut{}) defer client.Detach(true) @@ -1950,7 +1956,7 @@ func TestRerecord(t *testing.T) { t0 := gett() - _, err = c.RestartFrom(false, "", false, nil, false) + _, err = c.RestartFrom(false, "", false, nil, [3]string{}, false) assertNoError(err, t, "First restart") t1 := gett() @@ -1960,7 +1966,7 @@ func TestRerecord(t *testing.T) { time.Sleep(2 * time.Second) // make sure that we're not running inside the same second - _, err = c.RestartFrom(true, "", false, nil, false) + _, err = c.RestartFrom(true, "", false, nil, [3]string{}, false) assertNoError(err, t, "Second restart") t2 := gett() @@ -2025,7 +2031,7 @@ func TestStopRecording(t *testing.T) { // try rerecording go func() { - c.RestartFrom(true, "", false, nil, false) + c.RestartFrom(true, "", false, nil, [3]string{}, false) }() time.Sleep(time.Second) // hopefully the re-recording started... @@ -2039,7 +2045,7 @@ func TestClearLogicalBreakpoint(t *testing.T) { // Clearing a logical breakpoint should clear all associated physical // breakpoints. // Issue #1955. - withTestClient2Extended("testinline", t, protest.EnableInlining, func(c service.Client, fixture protest.Fixture) { + withTestClient2Extended("testinline", t, protest.EnableInlining, [3]string{}, func(c service.Client, fixture protest.Fixture) { bp, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.inlineThis"}) assertNoError(err, t, "CreateBreakpoint()") t.Logf("breakpoint set at %#v", bp.Addrs) @@ -2058,3 +2064,37 @@ func TestClearLogicalBreakpoint(t *testing.T) { } }) } + +func TestRedirects(t *testing.T) { + const ( + infile = "redirect-input.txt" + outfile = "redirect-output.txt" + ) + protest.AllowRecording(t) + withTestClient2Extended("redirect", t, 0, [3]string{infile, outfile, ""}, func(c service.Client, fixture protest.Fixture) { + outpath := filepath.Join(fixture.BuildDir, outfile) + <-c.Continue() + buf, err := ioutil.ReadFile(outpath) + assertNoError(err, t, "Reading output file") + t.Logf("output %q", buf) + if !strings.HasPrefix(string(buf), "Redirect test") { + t.Fatalf("Wrong output %q", string(buf)) + } + os.Remove(outpath) + if testBackend != "rr" { + _, err = c.Restart(false) + assertNoError(err, t, "Restart") + <-c.Continue() + buf2, err := ioutil.ReadFile(outpath) + t.Logf("output %q", buf2) + assertNoError(err, t, "Reading output file (second time)") + if !strings.HasPrefix(string(buf2), "Redirect test") { + t.Fatalf("Wrong output %q", string(buf2)) + } + if string(buf2) == string(buf) { + t.Fatalf("Expected output change got %q and %q", string(buf), string(buf2)) + } + os.Remove(outpath) + } + }) +} diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 657c81dc..a20cbfba 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -131,13 +131,13 @@ func withTestProcessArgs(name string, t *testing.T, wd string, args []string, bu var tracedir string switch testBackend { case "native": - p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "") + p, err = native.Launch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{}) case "lldb": - p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "") + p, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, false, []string{}, "", [3]string{}) case "rr": protest.MustHaveRecordingAllowed(t) t.Log("recording") - p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}) + p, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{}) t.Logf("replaying %q", tracedir) default: t.Fatalf("unknown backend %q", testBackend)