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
This commit is contained in:
parent
f62bf8d1e3
commit
4064d6acc0
15
_fixtures/workdir.go
Normal file
15
_fixtures/workdir.go
Normal file
@ -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)
|
||||
}
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -7,4 +7,4 @@
|
||||
#include <fcntl.h>
|
||||
|
||||
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*);
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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{})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user