提交 7fec8251 编写于 作者: D Derek Parker

Refactor: move cli logic into cli client

First of a few refactors to allow multiple clients / frontends for
Delve. Current implementation now uses a cli client, but conceivably we
could have an http or socket based client as well.
上级 864918ab
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
}
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())
}
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")
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册