diff --git a/_fixtures/nomaindir/file.go b/_fixtures/nomaindir/file.go new file mode 100644 index 00000000..f175e6ab --- /dev/null +++ b/_fixtures/nomaindir/file.go @@ -0,0 +1,4 @@ +package nomaindir + +func AFunction() { +} diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index f9f4a5ef..37d7b02c 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -151,7 +151,7 @@ consider compiling debugging binaries with -gcflags="-N -l".`, return nil }, Run: func(cmd *cobra.Command, args []string) { - os.Exit(execute(0, args, conf)) + os.Exit(execute(0, args, conf, executingExistingFile)) }, } RootCommand.AddCommand(execCommand) @@ -231,7 +231,7 @@ func debugCmd(cmd *cobra.Command, args []string) { defer os.Remove(fp) processArgs := append([]string{"./" + debugname}, targetArgs...) - return execute(0, processArgs, conf) + return execute(0, processArgs, conf, executingGeneratedFile) }() os.Exit(status) } @@ -318,7 +318,7 @@ func testCmd(cmd *cobra.Command, args []string) { defer os.Remove("./" + testdebugname) processArgs := append([]string{"./" + testdebugname}, targetArgs...) - return execute(0, processArgs, conf) + return execute(0, processArgs, conf, executingOther) }() os.Exit(status) } @@ -329,7 +329,7 @@ func attachCmd(cmd *cobra.Command, args []string) { fmt.Fprintf(os.Stderr, "Invalid pid: %s\n", args[0]) os.Exit(1) } - os.Exit(execute(pid, nil, conf)) + os.Exit(execute(pid, nil, conf, executingOther)) } func connectCmd(cmd *cobra.Command, args []string) { @@ -360,7 +360,15 @@ func connect(addr string, conf *config.Config) int { return status } -func execute(attachPid int, processArgs []string, conf *config.Config) int { +type executeKind int + +const ( + executingExistingFile = executeKind(iota) + executingGeneratedFile + executingOther +) + +func execute(attachPid int, processArgs []string, conf *config.Config, kind executeKind) int { // Make a TCP listener listener, err := net.Listen("tcp", Addr) if err != nil { @@ -404,6 +412,18 @@ func execute(attachPid int, processArgs []string, conf *config.Config) int { } if err := server.Run(); err != nil { + if err == api.NotExecutableErr { + switch kind { + case executingGeneratedFile: + fmt.Fprintln(os.Stderr, "Can not debug non-main package") + return 1 + case executingExistingFile: + fmt.Fprintf(os.Stderr, "%s is not executable\n", processArgs[0]) + return 1 + default: + // fallthrough + } + } fmt.Fprintln(os.Stderr, err) return 1 } diff --git a/proc/mem.go b/proc/mem.go index f8680b18..ea45360b 100644 --- a/proc/mem.go +++ b/proc/mem.go @@ -14,7 +14,7 @@ type memCache struct { } func (m *memCache) contains(addr uintptr, size int) bool { - return addr >= m.cacheAddr && addr <= (m.cacheAddr+uintptr(len(m.cache) - size)) + return addr >= m.cacheAddr && addr <= (m.cacheAddr+uintptr(len(m.cache)-size)) } func (m *memCache) readMemory(addr uintptr, size int) (data []byte, err error) { diff --git a/proc/proc.go b/proc/proc.go index ae8d1544..885825fa 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -63,6 +63,8 @@ type Process struct { moduleData []moduleData } +var NotExecutableErr = errors.New("not an executable file") + // New returns an initialized Process struct. Before returning, // it will also launch a goroutine in order to handle ptrace(2) // functions. For more information, see the documentation on diff --git a/proc/proc_darwin.go b/proc/proc_darwin.go index a6e6bc05..5df450be 100644 --- a/proc/proc_darwin.go +++ b/proc/proc_darwin.go @@ -38,6 +38,11 @@ type OSProcessDetails struct { // PT_SIGEXC on Darwin which will turn Unix signals into // Mach exceptions. func Launch(cmd []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 @@ -266,6 +271,8 @@ func (dbp *Process) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGroup) { } } +var UnsupportedArchErr = errors.New("unsupported architecture - only darwin/amd64 is supported") + func (dbp *Process) findExecutable(path string) (*macho.File, error) { if path == "" { path = C.GoString(C.find_executable(C.int(dbp.Pid))) @@ -274,6 +281,9 @@ func (dbp *Process) findExecutable(path string) (*macho.File, error) { if err != nil { return nil, err } + if exe.Cpu != macho.CpuAmd64 { + return nil, UnsupportedArchErr + } dbp.dwarf, err = exe.DWARF() if err != nil { return nil, err diff --git a/proc/proc_linux.go b/proc/proc_linux.go index 0297d132..6ea8af45 100644 --- a/proc/proc_linux.go +++ b/proc/proc_linux.go @@ -49,6 +49,10 @@ func Launch(cmd []string) (*Process, error) { proc *exec.Cmd err 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 + } dbp := New(0) dbp.execPtraceFunc(func() { proc = exec.Command(cmd[0]) @@ -162,6 +166,8 @@ func (dbp *Process) updateThreadList() error { return nil } +var UnsupportedArchErr = errors.New("unsupported architecture - only linux/amd64 is supported") + func (dbp *Process) findExecutable(path string) (*elf.File, error) { if path == "" { path = fmt.Sprintf("/proc/%d/exe", dbp.Pid) @@ -174,6 +180,9 @@ func (dbp *Process) findExecutable(path string) (*elf.File, error) { if err != nil { return nil, err } + if elfFile.Machine != elf.EM_X86_64 { + return nil, UnsupportedArchErr + } dbp.dwarf, err = elfFile.DWARF() if err != nil { return nil, err diff --git a/proc/proc_test.go b/proc/proc_test.go index fd8c606e..017c32c8 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "os/exec" "path/filepath" "reflect" "runtime" @@ -1826,3 +1827,57 @@ func TestStepParked(t *testing.T) { } }) } + +func TestIssue509(t *testing.T) { + fixturesDir := protest.FindFixturesDir() + nomaindir := filepath.Join(fixturesDir, "nomaindir") + cmd := exec.Command("go", "build", "-gcflags=-N -l", "-o", "debug") + cmd.Dir = nomaindir + assertNoError(cmd.Run(), t, "go build") + exepath := filepath.Join(nomaindir, "debug") + _, err := Launch([]string{exepath}) + if err == nil { + t.Fatalf("expected error but none was generated") + } + if err != NotExecutableErr { + t.Fatalf("expected error \"%v\" got \"%v\"", NotExecutableErr, err) + } + os.Remove(exepath) +} + +func TestUnsupportedArch(t *testing.T) { + ver, _ := ParseVersionString(runtime.Version()) + if ver.Major < 0 || !ver.AfterOrEqual(GoVersion{1, 6, -1, 0, 0}) { + // cross compile (with -N?) works only on select versions of go + return + } + + fixturesDir := protest.FindFixturesDir() + infile := filepath.Join(fixturesDir, "math.go") + outfile := filepath.Join(fixturesDir, "_math_debug_386") + + cmd := exec.Command("go", "build", "-gcflags=-N -l", "-o", outfile, infile) + for _, v := range os.Environ() { + if !strings.HasPrefix(v, "GOARCH=") { + cmd.Env = append(cmd.Env, v) + } + } + cmd.Env = append(cmd.Env, "GOARCH=386") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go build failed: %v: %v", err, string(out)) + } + defer os.Remove(outfile) + + p, err := Launch([]string{outfile}) + switch err { + case UnsupportedArchErr: + // all good + case nil: + p.Halt() + p.Kill() + t.Fatal("Launch is expected to fail, but succeeded") + default: + t.Fatal(err) + } +} diff --git a/proc/proc_windows.go b/proc/proc_windows.go index 85f57224..5f551d13 100644 --- a/proc/proc_windows.go +++ b/proc/proc_windows.go @@ -37,15 +37,20 @@ func Launch(cmd []string) (*Process, error) { if err != nil { return nil, err } - // Make sure the binary exists. + + // Make sure the binary exists and is an executable file if filepath.Base(cmd[0]) == cmd[0] { if _, err := exec.LookPath(cmd[0]); err != nil { return nil, err } } - if _, err := os.Stat(argv0Go); err != nil { - return nil, err + + peFile, err := openExecutablePath(argv0Go) + if err != nil { + return nil, NotExecutableErr } + peFile.Close() + // Duplicate the stdin/stdout/stderr handles files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)} p, _ := syscall.GetCurrentProcess() @@ -308,19 +313,20 @@ func (dbp *Process) parseDebugLineInfo(exe *pe.File, wg *sync.WaitGroup) { } } +var UnsupportedArchErr = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported") + func (dbp *Process) findExecutable(path string) (*pe.File, error) { if path == "" { // TODO: Find executable path from PID/handle on Windows: // https://msdn.microsoft.com/en-us/library/aa366789(VS.85).aspx return nil, fmt.Errorf("not yet implemented") } - f, err := os.OpenFile(path, 0, os.ModePerm) + peFile, err := openExecutablePath(path) if err != nil { return nil, err } - peFile, err := pe.NewFile(f) - if err != nil { - return nil, err + if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 { + return nil, UnsupportedArchErr } dbp.dwarf, err = dwarfFromPE(peFile) if err != nil { @@ -329,6 +335,14 @@ func (dbp *Process) findExecutable(path string) (*pe.File, error) { return peFile, nil } +func openExecutablePath(path string) (*pe.File, error) { + f, err := os.OpenFile(path, 0, os.ModePerm) + if err != nil { + return nil, err + } + return pe.NewFile(f) +} + // Adapted from src/debug/pe/file.go: pe.(*File).DWARF() func dwarfFromPE(f *pe.File) (*dwarf.Data, error) { // There are many other DWARF sections, but these diff --git a/service/api/types.go b/service/api/types.go index 8a2c4f80..c9e58cb1 100644 --- a/service/api/types.go +++ b/service/api/types.go @@ -10,6 +10,8 @@ import ( "github.com/derekparker/delve/proc" ) +var NotExecutableErr = proc.NotExecutableErr + // DebuggerState represents the current context of the debugger. type DebuggerState struct { // CurrentThread is the currently selected debugger thread. diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 785e41a7..4e7ed83f 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -61,7 +61,10 @@ func New(config *Config) (*Debugger, error) { log.Printf("launching process with args: %v", d.config.ProcessArgs) p, err := proc.Launch(d.config.ProcessArgs) if err != nil { - return nil, fmt.Errorf("could not launch process: %s", err) + if err != proc.NotExecutableErr && err != proc.UnsupportedArchErr { + err = fmt.Errorf("could not launch process: %s", err) + } + return nil, err } d.process = p }