diff --git a/client/cli/cli.go b/client/cli/cli.go new file mode 100644 index 00000000..b66b62dc --- /dev/null +++ b/client/cli/cli.go @@ -0,0 +1,136 @@ +package cli + +import ( + "flag" + "fmt" + "io" + "os" + "os/exec" + "strings" + "syscall" + + "github.com/derekparker/delve/command" + "github.com/derekparker/delve/goreadline" + "github.com/derekparker/delve/proctl" +) + +const historyFile string = ".dbg_history" + +func Run(run bool, pid int, args ...[]string) { + var ( + dbp *proctl.DebuggedProcess + err error + ) + + switch { + case run: + const debugname = "debug" + cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l") + err := cmd.Run() + if err != nil { + die(1, "Could not compile program:", err) + } + defer os.Remove(debugname) + + dbp, err = proctl.Launch(append([]string{"./" + debugname}, flag.Args()...)) + if err != nil { + die(1, "Could not launch program:", err) + } + case pid != 0: + dbp, err = proctl.Attach(pid) + if err != nil { + die(1, "Could not attach to process:", err) + } + default: + dbp, err = proctl.Launch(flag.Args()) + if err != nil { + die(1, "Could not launch program:", err) + } + } + + cmds := command.DebugCommands() + goreadline.LoadHistoryFromFile(historyFile) + fmt.Println("Type 'help' for list of commands.") + + for { + cmdstr, err := promptForInput() + if err != nil { + if err == io.EOF { + handleExit(dbp, 0) + } + die(1, "Prompt for input failed.\n") + } + + cmdstr, args := parseCommand(cmdstr) + + if cmdstr == "exit" { + handleExit(dbp, 0) + } + + cmd := cmds.Find(cmdstr) + err = cmd(dbp, args...) + if err != nil { + fmt.Fprintf(os.Stderr, "Command failed: %s\n", err) + } + } +} + +func handleExit(dbp *proctl.DebuggedProcess, status int) { + errno := goreadline.WriteHistoryToFile(historyFile) + fmt.Println("readline:", errno) + + prompt := "Would you like to kill the process? [y/n]" + answerp := goreadline.ReadLine(&prompt) + if answerp == nil { + die(2, io.EOF) + } + answer := strings.TrimSuffix(*answerp, "\n") + + for pc := range dbp.BreakPoints { + if _, err := dbp.Clear(pc); err != nil { + fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err) + } + } + + fmt.Println("Detaching from process...") + err := syscall.PtraceDetach(dbp.Process.Pid) + if err != nil { + die(2, "Could not detach", err) + } + + if answer == "y" { + fmt.Println("Killing process", dbp.Process.Pid) + + err := dbp.Process.Kill() + if err != nil { + fmt.Println("Could not kill process", err) + } + } + + die(status, "Hope I was of service hunting your bug!") +} + +func die(status int, args ...interface{}) { + fmt.Fprint(os.Stderr, args) + fmt.Fprint(os.Stderr, "\n") + os.Exit(status) +} + +func parseCommand(cmdstr string) (string, []string) { + vals := strings.Split(cmdstr, " ") + return vals[0], vals[1:] +} + +func promptForInput() (string, error) { + prompt := "(dlv) " + linep := goreadline.ReadLine(&prompt) + if linep == nil { + return "", io.EOF + } + line := strings.TrimSuffix(*linep, "\n") + if line != "" { + goreadline.AddHistory(line) + } + + return line, nil +} diff --git a/cmd/dlv/main.go b/cmd/dlv/main.go index d382c4c0..4871ecea 100644 --- a/cmd/dlv/main.go +++ b/cmd/dlv/main.go @@ -1,30 +1,16 @@ package main import ( - "bufio" "flag" "fmt" - "io" "os" - "os/exec" - "runtime" - "strings" - "syscall" - "github.com/derekparker/delve/command" - "github.com/derekparker/delve/goreadline" - "github.com/derekparker/delve/proctl" + "github.com/derekparker/delve/client/cli" ) const version string = "0.3.0.beta" -type term struct { - stdin *bufio.Reader -} - -const historyFile string = ".dbg_history" - func init() { // We must ensure here that we are running on the same thread during // the execution of dbg. This is due to the fact that ptrace(2) expects @@ -37,10 +23,6 @@ func main() { pid int run bool printv bool - err error - dbp *proctl.DebuggedProcess - t = newTerm() - cmds = command.DebugCommands() ) flag.IntVar(&pid, "pid", 0, "Pid of running process to attach to.") @@ -58,119 +40,5 @@ func main() { os.Exit(0) } - switch { - case run: - const debugname = "debug" - cmd := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l") - err := cmd.Run() - if err != nil { - die(1, "Could not compile program:", err) - } - defer os.Remove(debugname) - - dbp, err = proctl.Launch(append([]string{"./" + debugname}, flag.Args()...)) - if err != nil { - die(1, "Could not launch program:", err) - } - case pid != 0: - dbp, err = proctl.Attach(pid) - if err != nil { - die(1, "Could not attach to process:", err) - } - default: - dbp, err = proctl.Launch(flag.Args()) - if err != nil { - die(1, "Could not launch program:", err) - } - } - - goreadline.LoadHistoryFromFile(historyFile) - fmt.Println("Type 'help' for list of commands.") - - for { - cmdstr, err := t.promptForInput() - if err != nil { - if err == io.EOF { - handleExit(t, dbp, 0) - } - die(1, "Prompt for input failed.\n") - } - - cmdstr, args := parseCommand(cmdstr) - - if cmdstr == "exit" { - handleExit(t, dbp, 0) - } - - cmd := cmds.Find(cmdstr) - err = cmd(dbp, args...) - if err != nil { - fmt.Fprintf(os.Stderr, "Command failed: %s\n", err) - } - } -} - -func handleExit(t *term, dbp *proctl.DebuggedProcess, status int) { - errno := goreadline.WriteHistoryToFile(historyFile) - fmt.Println("readline:", errno) - - fmt.Println("Would you like to kill the process? [y/n]") - answer, err := t.stdin.ReadString('\n') - if err != nil { - die(2, err.Error()) - } - - for pc := range dbp.BreakPoints { - if _, err := dbp.Clear(pc); err != nil { - fmt.Printf("Can't clear breakpoint @%x: %s\n", pc, err) - } - } - - fmt.Println("Detaching from process...") - err = syscall.PtraceDetach(dbp.Process.Pid) - if err != nil { - die(2, "Could not detach", err) - } - - if answer == "y\n" { - fmt.Println("Killing process", dbp.Process.Pid) - - err := dbp.Process.Kill() - if err != nil { - fmt.Println("Could not kill process", err) - } - } - - die(status, "Hope I was of service hunting your bug!") -} - -func die(status int, args ...interface{}) { - fmt.Fprint(os.Stderr, args) - fmt.Fprint(os.Stderr, "\n") - os.Exit(status) -} - -func newTerm() *term { - return &term{ - stdin: bufio.NewReader(os.Stdin), - } -} - -func parseCommand(cmdstr string) (string, []string) { - vals := strings.Split(cmdstr, " ") - return vals[0], vals[1:] -} - -func (t *term) promptForInput() (string, error) { - prompt := "(dlv) " - linep := goreadline.ReadLine(&prompt) - if linep == nil { - return "", io.EOF - } - line := strings.TrimSuffix(*linep, "\n") - if line != "" { - goreadline.AddHistory(line) - } - - return line, nil + cli.Run(run, pid, flag.Args()) } diff --git a/cmd/dlv/main_test.go b/cmd/dlv/main_test.go deleted file mode 100644 index 1aa8b5ec..00000000 --- a/cmd/dlv/main_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package main - -import ( - "bytes" - "os" - "os/exec" - "strconv" - "strings" - "syscall" - "testing" - "time" -) - -func buildBinary(t *testing.T) { - cmd := exec.Command("go", "build", "-o", "dbg-test") - - err := cmd.Run() - if err != nil { - t.Fatal(err) - } -} - -func startDebugger(t *testing.T, pid int) *os.Process { - cmd := exec.Command("sudo", "./dbg-test", "-pid", strconv.Itoa(pid)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = bytes.NewBufferString("exit\ny\n") - - err := cmd.Start() - if err != nil { - t.Fatal(err) - } - - return cmd.Process -} - -func startTestProg(t *testing.T, proc string) *os.Process { - err := exec.Command("go", "build", "-gcflags=-N -l", "-o", proc, proc+".go").Run() - if err != nil { - t.Fatal("Could not compile", proc, err) - } - defer os.Remove(proc) - cmd := exec.Command(proc) - - err = cmd.Start() - if err != nil { - t.Fatal(err) - } - - return cmd.Process -} - -func TestCleanExit(t *testing.T) { - buildBinary(t) - - var ( - waitchan = make(chan *os.ProcessState) - testprog = startTestProg(t, "../../_fixtures/livetestprog") - ) - - go func() { - ps, err := testprog.Wait() - if err != nil { - t.Fatal(err) - } - waitchan <- ps - }() - - proc := startDebugger(t, testprog.Pid) - - defer func() { - testprog.Kill() - proc.Kill() - - err := os.Remove("dbg-test") - if err != nil { - t.Fatal(err) - } - }() - - timer := time.NewTimer(5 * time.Second) - select { - case ps := <-waitchan: - stat := ps.Sys().(syscall.WaitStatus) - if stat.Signaled() && strings.Contains(ps.String(), "exited") { - t.Fatal("Process has not exited") - } - - case <-timer.C: - t.Fatal("timeout") - } -}