From 4064d6acc06f89e9a2c336d01323b11e7a132d78 Mon Sep 17 00:00:00 2001 From: Evgeny L Date: Tue, 1 Nov 2016 12:58:43 -0700 Subject: [PATCH] Flag to set working directory (#650) * proc: Add `wd` to Launch This change adds the `wd` arg which specify working directory of the program. Fixes #295 * service/debugger: Add `Wd` field to debugger.Config This change adds the `Wd` field which specify working directory of the program launched by debugger. Fixes #295 * service: Add `Wd` to service.Config This change adds the `Wd` field which specify working directory of the program debugger will launch. Fixes #295 * cmd/dlv: Add `Wd` flag This change adds `Wd` flag which specify working directory of the program which launched by debugger. Fixes #295 * only set the Linux working directory if it is set, stub out param in darwin and windows * set working directory for Windows https://godoc.org/golang.org/x/sys/windows#CreateProcess https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx * Windows workingDir must be an *uint16 * attempt to chdir on darwin via @yuntan * proc/exec_darwin.c: fix working directory for darwin * Add tests to check if working directory works. * Fix darwin implementation of fork/exec, which paniced if child fork returned. * cmd, service: rename Wd to WorkingDir --- _fixtures/workdir.go | 15 +++++++++++ cmd/dlv/cmds/commands.go | 13 ++++++++-- proc/exec_darwin.c | 29 ++++++++++++++++++--- proc/exec_darwin.h | 2 +- proc/proc_darwin.go | 4 +-- proc/proc_linux.go | 7 ++++-- proc/proc_test.go | 46 ++++++++++++++++++++++++---------- proc/proc_windows.go | 15 +++++++++-- service/config.go | 4 +++ service/debugger/debugger.go | 8 ++++-- service/rpccommon/server.go | 1 + service/test/variables_test.go | 2 +- 12 files changed, 117 insertions(+), 29 deletions(-) create mode 100644 _fixtures/workdir.go diff --git a/_fixtures/workdir.go b/_fixtures/workdir.go new file mode 100644 index 00000000..8cecd14a --- /dev/null +++ b/_fixtures/workdir.go @@ -0,0 +1,15 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + pwd, err := os.Getwd() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println(pwd) +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index f6fe2d91..548f0112 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -39,6 +39,8 @@ var ( InitFile string // BuildFlags is the flags passed during compiler invocation. BuildFlags string + // WorkingDir is the working directory for running the program. + WorkingDir string // RootCommand is the root of the command tree. RootCommand *cobra.Command @@ -86,6 +88,7 @@ func New() *cobra.Command { RootCommand.PersistentFlags().IntVar(&APIVersion, "api-version", 1, "Selects API version when headless.") RootCommand.PersistentFlags().StringVar(&InitFile, "init", "", "Init file, executed by the terminal client.") RootCommand.PersistentFlags().StringVar(&BuildFlags, "build-flags", buildFlagsDefault, "Build flags, to be passed to the compiler.") + RootCommand.PersistentFlags().StringVar(&WorkingDir, "wd", ".", "Working directory for running the program.") // 'attach' subcommand. attachCommand := &cobra.Command{ @@ -231,8 +234,12 @@ func debugCmd(cmd *cobra.Command, args []string) { return 1 } defer os.Remove(fp) - - processArgs := append([]string{"./" + debugname}, targetArgs...) + abs, err := filepath.Abs(debugname) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return 1 + } + processArgs := append([]string{abs}, targetArgs...) return execute(0, processArgs, conf, executingGeneratedFile) }() os.Exit(status) @@ -275,6 +282,7 @@ func traceCmd(cmd *cobra.Command, args []string) { ProcessArgs: processArgs, AttachPid: traceAttachPid, APIVersion: 2, + WorkingDir: WorkingDir, }, Log) if err := server.Run(); err != nil { fmt.Fprintln(os.Stderr, err) @@ -398,6 +406,7 @@ func execute(attachPid int, processArgs []string, conf *config.Config, kind exec AttachPid: attachPid, AcceptMulti: AcceptMulti, APIVersion: APIVersion, + WorkingDir: WorkingDir, }, Log) default: fmt.Println("Unknown API version %d", APIVersion) diff --git a/proc/exec_darwin.c b/proc/exec_darwin.c index 9b9ed6e1..adeaa1bd 100644 --- a/proc/exec_darwin.c +++ b/proc/exec_darwin.c @@ -1,4 +1,5 @@ #include "exec_darwin.h" +#include "stdio.h" extern char** environ; @@ -12,6 +13,7 @@ close_exec_pipe(int fd[2]) { int fork_exec(char *argv0, char **argv, int size, + char *wd, task_t *task, mach_port_t *port_set, mach_port_t *exception_port, @@ -53,7 +55,7 @@ fork_exec(char *argv0, char **argv, int size, } // Fork succeeded, we are in the child. - int pret; + int pret, cret; char sig; close(fd[1]); @@ -62,7 +64,8 @@ fork_exec(char *argv0, char **argv, int size, // Create a new process group. if (setpgid(0, 0) < 0) { - return -1; + perror("setpgid"); + exit(1); } // Set errno to zero before a call to ptrace. @@ -70,11 +73,29 @@ fork_exec(char *argv0, char **argv, int size, // for successful calls. errno = 0; pret = ptrace(PT_TRACE_ME, 0, 0, 0); - if (pret != 0 && errno != 0) return -errno; + if (pret != 0 && errno != 0) { + perror("ptrace"); + exit(1); + } + + // Change working directory if wd is not empty. + if (wd && wd[0]) { + errno = 0; + cret = chdir(wd); + if (cret != 0 && errno != 0) { + char *error_msg; + asprintf(&error_msg, "%s '%s'", "chdir", wd); + perror(error_msg); + exit(1); + } + } errno = 0; pret = ptrace(PT_SIGEXC, 0, 0, 0); - if (pret != 0 && errno != 0) return -errno; + if (pret != 0 && errno != 0) { + perror("ptrace"); + exit(1); + } // Create the child process. execve(argv0, argv, environ); diff --git a/proc/exec_darwin.h b/proc/exec_darwin.h index c25ae4d8..995e7e88 100644 --- a/proc/exec_darwin.h +++ b/proc/exec_darwin.h @@ -7,4 +7,4 @@ #include int -fork_exec(char *, char **, int, task_t*, mach_port_t*, mach_port_t*, mach_port_t*); +fork_exec(char *, char **, int, char *, task_t*, mach_port_t*, mach_port_t*, mach_port_t*); diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index 0e596d53..0e1a9ce3 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -37,12 +37,11 @@ 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) (*Process, error) { +func Launch(cmd []string, wd string) (*Process, error) { // check that the argument to Launch is an executable file if fi, staterr := os.Stat(cmd[0]); staterr == nil && (fi.Mode()&0111) == 0 { return nil, NotExecutableErr } - argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err @@ -69,6 +68,7 @@ func Launch(cmd []string) (*Process, error) { var pid int dbp.execPtraceFunc(func() { ret := C.fork_exec(argv0, &argvSlice[0], C.int(len(argvSlice)), + C.CString(wd), &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort) pid = int(ret) diff --git a/proc/proc_linux.go b/proc/proc_linux.go index 53098434..a886585b 100644 --- a/proc/proc_linux.go +++ b/proc/proc_linux.go @@ -45,8 +45,8 @@ type OSProcessDetails struct { // Launch creates and begins debugging a new process. First entry in // `cmd` is the program to run, and then rest are the arguments -// to be supplied to that process. -func Launch(cmd []string) (*Process, error) { +// to be supplied to that process. `wd` is working directory of the program. +func Launch(cmd []string, wd string) (*Process, error) { var ( proc *exec.Cmd err error @@ -62,6 +62,9 @@ func Launch(cmd []string) (*Process, error) { proc.Stdout = os.Stdout proc.Stderr = os.Stderr proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true, Setpgid: true} + if wd != "" { + proc.Dir = wd + } err = proc.Start() }) if err != nil { diff --git a/proc/proc_test.go b/proc/proc_test.go index 80f8ad73..b0c359a2 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -33,7 +33,7 @@ func TestMain(m *testing.M) { func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture protest.Fixture)) { fixture := protest.BuildFixture(name) - p, err := Launch([]string{fixture.Path}) + p, err := Launch([]string{fixture.Path}, ".") if err != nil { t.Fatal("Launch():", err) } @@ -46,9 +46,9 @@ func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture prot fn(p, fixture) } -func withTestProcessArgs(name string, t testing.TB, fn func(p *Process, fixture protest.Fixture), args []string) { +func withTestProcessArgs(name string, t testing.TB, wd string, fn func(p *Process, fixture protest.Fixture), args []string) { fixture := protest.BuildFixture(name) - p, err := Launch(append([]string{fixture.Path}, args...)) + p, err := Launch(append([]string{fixture.Path}, args...), wd) if err != nil { t.Fatal("Launch():", err) } @@ -1739,17 +1739,17 @@ func TestCmdLineArgs(t *testing.T) { } // make sure multiple arguments (including one with spaces) are passed to the binary correctly - withTestProcessArgs("testargs", t, expectSuccess, []string{"test"}) - withTestProcessArgs("testargs", t, expectSuccess, []string{"test", "pass flag"}) + withTestProcessArgs("testargs", t, ".", expectSuccess, []string{"test"}) + withTestProcessArgs("testargs", t, ".", expectSuccess, []string{"test", "pass flag"}) // check that arguments with spaces are *only* passed correctly when correctly called - withTestProcessArgs("testargs", t, expectPanic, []string{"test pass", "flag"}) - withTestProcessArgs("testargs", t, expectPanic, []string{"test", "pass", "flag"}) - withTestProcessArgs("testargs", t, expectPanic, []string{"test pass flag"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"test pass", "flag"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"test", "pass", "flag"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"test pass flag"}) // and that invalid cases (wrong arguments or no arguments) panic withTestProcess("testargs", t, expectPanic) - withTestProcessArgs("testargs", t, expectPanic, []string{"invalid"}) - withTestProcessArgs("testargs", t, expectPanic, []string{"test", "invalid"}) - withTestProcessArgs("testargs", t, expectPanic, []string{"invalid", "pass flag"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"invalid"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"test", "invalid"}) + withTestProcessArgs("testargs", t, ".", expectPanic, []string{"invalid", "pass flag"}) } func TestIssue462(t *testing.T) { @@ -1872,7 +1872,7 @@ func TestIssue509(t *testing.T) { cmd.Dir = nomaindir assertNoError(cmd.Run(), t, "go build") exepath := filepath.Join(nomaindir, "debug") - _, err := Launch([]string{exepath}) + _, err := Launch([]string{exepath}, ".") if err == nil { t.Fatalf("expected error but none was generated") } @@ -1906,7 +1906,7 @@ func TestUnsupportedArch(t *testing.T) { } defer os.Remove(outfile) - p, err := Launch([]string{outfile}) + p, err := Launch([]string{outfile}, ".") switch err { case UnsupportedArchErr: // all good @@ -2306,3 +2306,23 @@ func TestStepOutPanicAndDirectCall(t *testing.T) { } }) } + +func TestWorkDir(t *testing.T) { + wd := os.TempDir() + // For Darwin `os.TempDir()` returns `/tmp` which is symlink to `/private/tmp`. + if runtime.GOOS == "darwin" { + wd = "/private/tmp" + } + withTestProcessArgs("workdir", t, wd, func(p *Process, fixture protest.Fixture) { + addr, _, err := p.goSymTable.LineToPC(fixture.Source, 14) + assertNoError(err, t, "LineToPC") + p.SetBreakpoint(addr, UserBreakpoint, nil) + p.Continue() + v, err := evalVariable(p, "pwd") + assertNoError(err, t, "EvalVariable") + str := constant.StringVal(v.Value) + if wd != str { + t.Fatalf("Expected %s got %s\n", wd, str) + } + }, []string{}) +} diff --git a/proc/proc_windows.go b/proc/proc_windows.go index 10a2393a..f0bad43f 100644 --- a/proc/proc_windows.go +++ b/proc/proc_windows.go @@ -26,7 +26,7 @@ type OSProcessDetails struct { } // Launch creates and begins debugging a new process. -func Launch(cmd []string) (*Process, error) { +func Launch(cmd []string, wd string) (*Process, error) { argv0Go, err := filepath.Abs(cmd[0]) if err != nil { return nil, err @@ -82,6 +82,13 @@ func Launch(cmd []string) (*Process, error) { return nil, err } } + + var workingDir *uint16 + if wd != "" { + if workingDir, err = syscall.UTF16PtrFromString(wd); err != nil { + return nil, err + } + } // Initialize the startup info and create process si := new(sys.StartupInfo) @@ -94,7 +101,11 @@ func Launch(cmd []string) (*Process, error) { dbp := New(0) dbp.execPtraceFunc(func() { - err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi) + if wd == "" { + err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, nil, si, pi) + } else { + err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, _DEBUG_ONLY_THIS_PROCESS, nil, workingDir, si, pi) + } }) if err != nil { return nil, err diff --git a/service/config.go b/service/config.go index c3571c34..dd2991c9 100644 --- a/service/config.go +++ b/service/config.go @@ -13,6 +13,10 @@ type Config struct { Listener net.Listener // ProcessArgs are the arguments to launch a new process. ProcessArgs []string + // WorkingDir is working directory of the new process. This field is used + // only when launching a new process. + WorkingDir string + // AttachPid is the PID of an existing process to which the debugger should // attach. AttachPid int diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index ec9770a7..95f2e232 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -38,6 +38,10 @@ type Debugger struct { type Config struct { // ProcessArgs are the arguments to launch a new process. ProcessArgs []string + // WorkingDir is working directory of the new process. This field is used + // only when launching a new process. + WorkingDir string + // AttachPid is the PID of an existing process to which the debugger should // attach. AttachPid int @@ -59,7 +63,7 @@ func New(config *Config) (*Debugger, error) { d.process = p } else { log.Printf("launching process with args: %v", d.config.ProcessArgs) - p, err := proc.Launch(d.config.ProcessArgs) + p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { if err != proc.NotExecutableErr && err != proc.UnsupportedArchErr { err = fmt.Errorf("could not launch process: %s", err) @@ -112,7 +116,7 @@ func (d *Debugger) Restart() error { return err } } - p, err := proc.Launch(d.config.ProcessArgs) + p, err := proc.Launch(d.config.ProcessArgs, d.config.WorkingDir) if err != nil { return fmt.Errorf("could not launch process: %s", err) } diff --git a/service/rpccommon/server.go b/service/rpccommon/server.go index 528eeb93..61262c4a 100644 --- a/service/rpccommon/server.go +++ b/service/rpccommon/server.go @@ -111,6 +111,7 @@ func (s *ServerImpl) Run() error { if s.debugger, err = debugger.New(&debugger.Config{ ProcessArgs: s.config.ProcessArgs, AttachPid: s.config.AttachPid, + WorkingDir: s.config.WorkingDir, }); err != nil { return err } diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 9c410220..d6df2fa1 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -79,7 +79,7 @@ const varTestBreakpointLineNumber = 59 func withTestProcess(name string, t *testing.T, fn func(p *proc.Process, fixture protest.Fixture)) { fixture := protest.BuildFixture(name) - p, err := proc.Launch([]string{fixture.Path}) + p, err := proc.Launch([]string{fixture.Path}, ".") if err != nil { t.Fatal("Launch():", err) } -- GitLab