Launch prog from cli, also exit cleanly

This commit is contained in:
Derek Parker 2014-08-23 08:20:56 -05:00
parent 44dba87c5d
commit 93db6249a0
5 changed files with 184 additions and 28 deletions

BIN
_fixtures/integrationprog Executable file

Binary file not shown.

@ -0,0 +1,18 @@
package main
import (
"fmt"
"time"
)
func sayhi() {
fmt.Println("hi")
}
func main() {
time.Sleep(1 * time.Second)
for i := 0; i < 3; i++ {
sayhi()
time.Sleep(1 * time.Second)
}
}

@ -5,11 +5,9 @@ package command
import (
"debug/gosym"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"github.com/derekparker/dbg/proctl"
)
@ -23,7 +21,6 @@ type Commands struct {
// Returns a Commands struct with default commands defined.
func DebugCommands() *Commands {
cmds := map[string]cmdfunc{
"exit": exitFunc,
"continue": cont,
"next": next,
"break": breakpoint,
@ -66,16 +63,6 @@ func noCmdAvailable(p *proctl.DebuggedProcess, ars ...string) error {
return fmt.Errorf("command not available")
}
func exitFunc(p *proctl.DebuggedProcess, ars ...string) error {
err := syscall.PtraceDetach(p.Pid)
if err != nil {
return err
}
os.Exit(0)
return nil
}
func nullCommand(p *proctl.DebuggedProcess, ars ...string) error {
return nil
}

92
main.go

@ -2,11 +2,13 @@ package main
import (
"bufio"
"flag"
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/derekparker/dbg/command"
"github.com/derekparker/dbg/proctl"
@ -23,32 +25,35 @@ func main() {
runtime.LockOSThread()
var (
pid int
proc string
t = newTerm()
cmds = command.DebugCommands()
)
if len(os.Args) == 1 {
die("You must provide a pid\n")
flag.IntVar(&pid, "pid", 0, "Pid of running process to attach to.")
flag.StringVar(&proc, "proc", "", "Path to process to run and debug.")
flag.Parse()
if flag.NFlag() == 0 {
flag.Usage()
os.Exit(0)
}
pid, err := strconv.Atoi(os.Args[1])
if err != nil {
die(err)
}
dbgproc, err := proctl.NewDebugProcess(pid)
if err != nil {
die("Could not start debugging process:", err)
}
dbgproc := beginTrace(pid, proc)
for {
cmdstr, err := t.promptForInput()
if err != nil {
die("Prompt for input failed.\n")
die(1, "Prompt for input failed.\n")
}
cmdstr, args := parseCommand(cmdstr)
if cmdstr == "exit" {
handleExit(t, dbgproc, 0)
}
cmd := cmds.Find(cmdstr)
err = cmd(dbgproc, args...)
if err != nil {
@ -57,9 +62,66 @@ func main() {
}
}
func die(args ...interface{}) {
func beginTrace(pid int, proc string) *proctl.DebuggedProcess {
var (
err error
dbgproc *proctl.DebuggedProcess
)
if pid != 0 {
dbgproc, err = proctl.NewDebugProcess(pid)
if err != nil {
die(1, "Could not start debugging process:", err)
}
}
if proc != "" {
proc := exec.Command(proc)
proc.Stdout = os.Stdout
err = proc.Start()
if err != nil {
die(1, "Could not start process:", err)
}
dbgproc, err = proctl.NewDebugProcess(proc.Process.Pid)
if err != nil {
die(1, "Could not start debugging process:", err)
}
}
return dbgproc
}
func handleExit(t *term, dbp *proctl.DebuggedProcess, status int) {
fmt.Println("Would you like to kill the process? [y/n]")
answer, err := t.stdin.ReadString('\n')
if err != nil {
die(2, err.Error())
}
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)
os.Exit(1)
fmt.Fprint(os.Stderr, "\n")
os.Exit(status)
}
func newTerm() *term {

89
main_test.go Normal file

@ -0,0 +1,89 @@
package main
import (
"bytes"
"os"
"os/exec"
"strconv"
"strings"
"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.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 {
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:
// Admittedly, this is weird.
// There is/was a bug in Go that marked
// a process as done whenever `Wait()` was
// called on it, I fixed that, but it seems
// there may be another connected bug somewhere
// where the process is not marked as exited.
if strings.Contains(ps.String(), "exited") {
t.Fatal("Process has not exited")
}
case <-timer.C:
t.Fatal("timeout")
}
}