Skip to content

Commit

Permalink
pkg/proc,service/*: Supports sending output to clients when running p…
Browse files Browse the repository at this point in the history
…rograms remotely (#3253)

* wip: Support sending output when remote debug

* wip: Support local output and remote output

* wip: fix stderr and stdout assignment error

* wip: optimize code

* wip: Only if outputMode is "remote" is the redirected console output

* wip: Redirected debugMode output(Not tested on windows)

* wip: support remote debugging output redirection of windows

* wip: real-time write back output

* wip: support for windows

* wip: fix windows remote debug not output

* wip: fix truncated output redirection

* wip: delete printfln

* wip: use debugger.Config to pass redirect(macOS)

* wip: use debugger.Config to pass redirect(linux)

* wip: Change redirect to a concrete type

* wip: s.wg.wait before sending "terminated"

* wip: add proc/redirect test(darwin and linux)

* Merge branch 'master' of github.com:tttoad/delve into feat-console

* wip: Fix test failure on windows

* fix: undefined: proc.Redirects

* fix: compile failure

* wip: Remove useless code

* fix: filename error

* fix: os.file not close

* test: add server_test.redirect

* fix: Remove 'eol' from end of file

* fix: gdbserial: File not closed in file mode.
(in reality, gdbserial will never use file mode)

* feat: Remove "only-remote". Fix spelling mistakes.

* fix: spelling mistakes

* refactor: redirect

* fix: stdout and stderr are not set to default values

* fix: Restore code logic for rr.openRedirects()

* fix: Optimization Code

* fix: utiltest

* fix: execpt out

* fix: Resource release for redirects

* fix: build failure

* fix: clean->clear

* fix: build failure

* fix: test failure

* fix: Optimization Code

* style: remove useless code

* refactor: namedpipe

* refactor: namedpipe, launch ...

* fix: freebsd compile failure

* fix: proc_darwin compile failure

* style:  remove useless code

* feat: add d.config.Stdxx check on debug.Restart

* style: formatting and adding comments

* style: formatting and adding comments

* feat: add d.config.Stdxx check on debug.Restart

* style: namedpipe->redirector

* style: namedPipe->redirector

---------

Co-authored-by: 李翔 <[email protected]>
  • Loading branch information
tttoad and 李翔 authored Jul 5, 2023
1 parent d963eb1 commit 53998cb
Show file tree
Hide file tree
Showing 20 changed files with 399 additions and 66 deletions.
13 changes: 13 additions & 0 deletions _fixtures/out_redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import (
"fmt"
"os"
)

func main() {
fmt.Println("hello world!")
fmt.Fprintf(os.Stdout, "hello world!")
fmt.Fprintf(os.Stderr, "hello world!\n")
fmt.Fprintf(os.Stderr, "hello world! error!")
}
5 changes: 4 additions & 1 deletion cmd/dlv/cmds/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/go-delve/delve/pkg/gobuild"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/terminal"
"github.com/go-delve/delve/pkg/version"
"github.com/go-delve/delve/service"
Expand Down Expand Up @@ -1015,7 +1016,9 @@ func execute(attachPid int, processArgs []string, conf *config.Config, coreFile
DebugInfoDirectories: conf.DebugInfoDirectories,
CheckGoVersion: checkGoVersion,
TTY: tty,
Redirects: redirects,
Stdin: redirects[0],
Stdout: proc.OutputRedirect{Path: redirects[1]},
Stderr: proc.OutputRedirect{Path: redirects[2]},
DisableASLR: disableASLR,
RrOnProcessPid: rrOnProcessPid,
},
Expand Down
48 changes: 27 additions & 21 deletions pkg/proc/gdbserial/rr.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,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, redirects [3]string) (run func() (string, error), stop func() error, err error) {
func RecordAsync(cmd []string, wd string, quiet bool, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (run func() (string, error), stop func() error, err error) {
if err := checkRRAvailable(); err != nil {
return nil, nil, err
}
Expand All @@ -35,7 +35,7 @@ func RecordAsync(cmd []string, wd string, quiet bool, redirects [3]string) (run
args = append(args, cmd...)
rrcmd := exec.Command("rr", args...)
var closefn func()
rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(redirects, quiet)
rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(stdin, stdout, stderr, quiet)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -63,11 +63,11 @@ func RecordAsync(cmd []string, wd string, quiet bool, redirects [3]string) (run
return run, stop, nil
}

func openRedirects(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
func openRedirects(stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
toclose := []*os.File{}

if redirects[0] != "" {
stdin, err = os.Open(redirects[0])
if stdinPath != "" {
stdin, err = os.Open(stdinPath)
if err != nil {
return nil, nil, nil, nil, err
}
Expand All @@ -76,27 +76,33 @@ func openRedirects(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.F
stdin = os.Stdin
}

create := func(path string, dflt *os.File) *os.File {
if path == "" {
if quiet {
return nil
create := func(redirect proc.OutputRedirect, dflt *os.File) (f *os.File) {
if redirect.Path != "" {
f, err = os.Create(redirect.Path)
if f != nil {
toclose = append(toclose, f)
}
return dflt

return f
} else if redirect.File != nil {
toclose = append(toclose, redirect.File)

return redirect.File
}
var f *os.File
f, err = os.Create(path)
if f != nil {
toclose = append(toclose, f)

if quiet {
return nil
}
return f

return dflt
}

stdout = create(redirects[1], os.Stdout)
stdout = create(stdoutOR, os.Stdout)
if err != nil {
return nil, nil, nil, nil, err
}

stderr = create(redirects[2], os.Stderr)
stderr = create(stderrOR, os.Stderr)
if err != nil {
return nil, nil, nil, nil, err
}
Expand All @@ -112,8 +118,8 @@ func openRedirects(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.F

// 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, redirects [3]string) (tracedir string, err error) {
run, _, err := RecordAsync(cmd, wd, quiet, redirects)
func Record(cmd []string, wd string, quiet bool, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (tracedir string, err error) {
run, _, err := RecordAsync(cmd, wd, quiet, stdin, stdout, stderr)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -288,8 +294,8 @@ func rrParseGdbCommand(line string) rrInit {
}

// RecordAndReplay acts like calling Record and then Replay.
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.TargetGroup, string, error) {
tracedir, err := Record(cmd, wd, quiet, redirects)
func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (*proc.TargetGroup, string, error) {
tracedir, err := Record(cmd, wd, quiet, stdin, stdout, stderr)
if tracedir == "" {
return nil, "", err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/gdbserial/rr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func withTestRecording(name string, t testing.TB, fn func(grp *proc.TargetGroup,
t.Skip("test skipped, rr not found")
}
t.Log("recording")
grp, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, [3]string{})
grp, tracedir, err := gdbserial.RecordAndReplay([]string{fixture.Path}, ".", true, []string{}, "", proc.OutputRedirect{}, proc.OutputRedirect{})
if err != nil {
t.Fatal("Launch():", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/native/nonative_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
var ErrNativeBackendDisabled = errors.New("native backend disabled during compilation")

// Launch returns ErrNativeBackendDisabled.
func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) {
func Launch(_ []string, _ string, _ proc.LaunchFlags, _ []string, _ string, _ string, _ proc.OutputRedirect, _ proc.OutputRedirect) (*proc.TargetGroup, error) {
return nil, ErrNativeBackendDisabled
}

Expand Down
33 changes: 19 additions & 14 deletions pkg/proc/native/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,11 +376,11 @@ func (dbp *nativeProcess) writeSoftwareBreakpoint(thread *nativeThread, addr uin
return err
}

func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
func openRedirects(stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect, foreground bool) (stdin, stdout, stderr *os.File, closefn func(), err error) {
toclose := []*os.File{}

if redirects[0] != "" {
stdin, err = os.Open(redirects[0])
if stdinPath != "" {
stdin, err = os.Open(stdinPath)
if err != nil {
return nil, nil, nil, nil, err
}
Expand All @@ -389,24 +389,29 @@ func openRedirects(redirects [3]string, foreground bool) (stdin, stdout, stderr
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)
create := func(redirect proc.OutputRedirect, dflt *os.File) (f *os.File) {
if redirect.Path != "" {
f, err = os.Create(redirect.Path)
if f != nil {
toclose = append(toclose, f)
}

return f
} else if redirect.File != nil {
toclose = append(toclose, redirect.File)

return redirect.File
}
return f

return dflt
}

stdout = create(redirects[1], os.Stdout)
stdout = create(stdoutOR, os.Stdout)
if err != nil {
return nil, nil, nil, nil, err
}

stderr = create(redirects[2], os.Stderr)
stderr = create(stderrOR, os.Stderr)
if err != nil {
return nil, nil, nil, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/proc/native/proc_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (os *osProcessDetails) Close() {}
// custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin which will turn Unix signals into
// Mach exceptions.
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ [3]string) (*proc.TargetGroup, error) {
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, _ string, _ proc.OutputRedirect, _ proc.OutputRedirect) (*proc.TargetGroup, error) {
argv0Go, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ func (os *osProcessDetails) Close() {}
// to be supplied to that process. `wd` is working directory of the program.
// If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in.
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) {
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) {
var (
process *exec.Cmd
err error
)

foreground := flags&proc.LaunchForeground != 0

stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground)
stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@ func (os *osProcessDetails) Close() {
// to be supplied to that process. `wd` is working directory of the program.
// If the DWARF information cannot be found in the binary, Delve will look
// for external debug files in the directories passed in.
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, redirects [3]string) (*proc.TargetGroup, error) {
func Launch(cmd []string, wd string, flags proc.LaunchFlags, debugInfoDirs []string, tty string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) {
var (
process *exec.Cmd
err error
)

foreground := flags&proc.LaunchForeground != 0

stdin, stdout, stderr, closefn, err := openRedirects(redirects, foreground)
stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, foreground)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/proc/native/proc_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ type osProcessDetails struct {
func (os *osProcessDetails) Close() {}

// Launch creates and begins debugging a new process.
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, redirects [3]string) (*proc.TargetGroup, error) {
func Launch(cmd []string, wd string, flags proc.LaunchFlags, _ []string, _ string, stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect) (*proc.TargetGroup, error) {
argv0Go := cmd[0]

env := proc.DisableAsyncPreemptEnv()

stdin, stdout, stderr, closefn, err := openRedirects(redirects, true)
stdin, stdout, stderr, closefn, err := openRedirects(stdinPath, stdoutOR, stderrOR, true)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/proc/proc_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"testing"

"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/native"
protest "github.com/go-delve/delve/pkg/proc/test"
)
Expand All @@ -14,7 +15,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}, ""), "", 0, []string{filepath.Dir(fixture.Path)}, "", [3]string{})
p, err := native.Launch(append([]string{fixture.Path}, ""), "", 0, []string{filepath.Dir(fixture.Path)}, "", "", proc.OutputRedirect{}, proc.OutputRedirect{})
if err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/proc/proc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ func withTestProcessArgs(name string, t testing.TB, wd string, args []string, bu

switch testBackend {
case "native":
grp, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{})
grp, err = native.Launch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", "", proc.OutputRedirect{}, proc.OutputRedirect{})
case "lldb":
grp, err = gdbserial.LLDBLaunch(append([]string{fixture.Path}, args...), wd, 0, []string{}, "", [3]string{})
case "rr":
protest.MustHaveRecordingAllowed(t)
t.Log("recording")
grp, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, [3]string{})
grp, tracedir, err = gdbserial.RecordAndReplay(append([]string{fixture.Path}, args...), wd, true, []string{}, "", proc.OutputRedirect{}, proc.OutputRedirect{})
t.Logf("replaying %q", tracedir)
default:
t.Fatal("unknown backend")
Expand Down Expand Up @@ -2247,7 +2247,7 @@ func TestUnsupportedArch(t *testing.T) {

switch testBackend {
case "native":
p, err = native.Launch([]string{outfile}, ".", 0, []string{}, "", [3]string{})
p, err = native.Launch([]string{outfile}, ".", 0, []string{}, "", "", proc.OutputRedirect{}, proc.OutputRedirect{})
case "lldb":
p, err = gdbserial.LLDBLaunch([]string{outfile}, ".", 0, []string{}, "", [3]string{})
default:
Expand Down
12 changes: 12 additions & 0 deletions pkg/proc/redirect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package proc

import "os"

// OutputRedirect Specifies where the target program output will be redirected to.
// Only one of "Path" and "File" should be set.
type OutputRedirect struct {
// Path File path.
Path string
// File Redirect file.
File *os.File
}
59 changes: 59 additions & 0 deletions pkg/proc/redirector_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build !windows
// +build !windows

package proc

import (
"crypto/rand"
"encoding/hex"
"io"
"os"
"path/filepath"
"syscall"
)

type openOnRead struct {
path string
rd io.ReadCloser
}

func (oor *openOnRead) Read(p []byte) (n int, err error) {
if oor.rd != nil {
return oor.rd.Read(p)
}

fh, err := os.OpenFile(oor.path, os.O_RDONLY, os.ModeNamedPipe)
if err != nil {
return 0, err
}

oor.rd = fh
return oor.rd.Read(p)
}

func (oor *openOnRead) Close() error {
defer os.Remove(oor.path)

fh, _ := os.OpenFile(oor.path, os.O_WRONLY|syscall.O_NONBLOCK, 0)
if fh != nil {
fh.Close()
}

return oor.rd.Close()
}

func Redirector() (reader io.ReadCloser, output OutputRedirect, err error) {
r := make([]byte, 4)
if _, err = rand.Read(r); err != nil {
return reader, output, err
}

var path = filepath.Join(os.TempDir(), hex.EncodeToString(r))

if err = syscall.Mkfifo(path, 0o600); err != nil {
_ = os.Remove(path)
return reader, output, err
}

return &openOnRead{path: path}, OutputRedirect{Path: path}, nil
}
15 changes: 15 additions & 0 deletions pkg/proc/redirector_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//go:build windows
// +build windows

package proc

import (
"io"
"os"
)

func Redirector() (reader io.ReadCloser, output OutputRedirect, err error) {
reader, output.File, err = os.Pipe()

return reader, output, err
}
Loading

0 comments on commit 53998cb

Please sign in to comment.