提交 51c39ed1 编写于 作者: A aarzilli

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
上级 5714aa54
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
}
......
......@@ -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) {
......
......@@ -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
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册