Launch prog from cli, also exit cleanly
This commit is contained in:
parent
44dba87c5d
commit
93db6249a0
BIN
_fixtures/integrationprog
Executable file
BIN
_fixtures/integrationprog
Executable file
Binary file not shown.
18
_fixtures/integrationprog.go
Normal file
18
_fixtures/integrationprog.go
Normal file
@ -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 (
|
import (
|
||||||
"debug/gosym"
|
"debug/gosym"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/derekparker/dbg/proctl"
|
"github.com/derekparker/dbg/proctl"
|
||||||
)
|
)
|
||||||
@ -23,7 +21,6 @@ type Commands struct {
|
|||||||
// Returns a Commands struct with default commands defined.
|
// Returns a Commands struct with default commands defined.
|
||||||
func DebugCommands() *Commands {
|
func DebugCommands() *Commands {
|
||||||
cmds := map[string]cmdfunc{
|
cmds := map[string]cmdfunc{
|
||||||
"exit": exitFunc,
|
|
||||||
"continue": cont,
|
"continue": cont,
|
||||||
"next": next,
|
"next": next,
|
||||||
"break": breakpoint,
|
"break": breakpoint,
|
||||||
@ -66,16 +63,6 @@ func noCmdAvailable(p *proctl.DebuggedProcess, ars ...string) error {
|
|||||||
return fmt.Errorf("command not available")
|
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 {
|
func nullCommand(p *proctl.DebuggedProcess, ars ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
92
main.go
92
main.go
@ -2,11 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/derekparker/dbg/command"
|
"github.com/derekparker/dbg/command"
|
||||||
"github.com/derekparker/dbg/proctl"
|
"github.com/derekparker/dbg/proctl"
|
||||||
@ -23,32 +25,35 @@ func main() {
|
|||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
pid int
|
||||||
|
proc string
|
||||||
t = newTerm()
|
t = newTerm()
|
||||||
cmds = command.DebugCommands()
|
cmds = command.DebugCommands()
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(os.Args) == 1 {
|
flag.IntVar(&pid, "pid", 0, "Pid of running process to attach to.")
|
||||||
die("You must provide a pid\n")
|
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])
|
dbgproc := beginTrace(pid, proc)
|
||||||
if err != nil {
|
|
||||||
die(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dbgproc, err := proctl.NewDebugProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
die("Could not start debugging process:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
cmdstr, err := t.promptForInput()
|
cmdstr, err := t.promptForInput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
die("Prompt for input failed.\n")
|
die(1, "Prompt for input failed.\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdstr, args := parseCommand(cmdstr)
|
cmdstr, args := parseCommand(cmdstr)
|
||||||
|
|
||||||
|
if cmdstr == "exit" {
|
||||||
|
handleExit(t, dbgproc, 0)
|
||||||
|
}
|
||||||
|
|
||||||
cmd := cmds.Find(cmdstr)
|
cmd := cmds.Find(cmdstr)
|
||||||
err = cmd(dbgproc, args...)
|
err = cmd(dbgproc, args...)
|
||||||
if err != nil {
|
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)
|
fmt.Fprint(os.Stderr, args)
|
||||||
os.Exit(1)
|
fmt.Fprint(os.Stderr, "\n")
|
||||||
|
os.Exit(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTerm() *term {
|
func newTerm() *term {
|
||||||
|
89
main_test.go
Normal file
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")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user