proc: detect when Launching non-executable files
This provides a better error message when the user tries to run dlv debug on a directory that does not contain a main package, when `dlv exec` is used with a source file. Additionally the architecture of the executable is checked as suggested by @alexbrainman in #443. Fixes #509
This commit is contained in:
parent
5714aa548c
commit
51c39ed171
4
_fixtures/nomaindir/file.go
Normal file
4
_fixtures/nomaindir/file.go
Normal file
@ -0,0 +1,4 @@
|
||||
package nomaindir
|
||||
|
||||
func AFunction() {
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user