Fix: properly handle random signals

* Ignore signals we do not care about
* Implement custom fork/exec for Darwin to convert signals to mach
  exceptions
This commit is contained in:
Derek Parker 2015-04-25 09:46:16 -05:00
parent 047a91af5b
commit 173ee20097
7 changed files with 228 additions and 82 deletions

49
proctl/exec_darwin.c Normal file

@ -0,0 +1,49 @@
#include "exec_darwin.h"
int
fork_exec(char *argv0, char **argv,
mach_port_name_t *task,
mach_port_t *port_set,
mach_port_t *exception_port,
mach_port_t *notification_port)
{
int fd[2];
if (pipe(fd) < 0) return -1;
kern_return_t kret;
pid_t pid = fork();
if (pid > 0) {
// In parent.
close(fd[0]);
kret = acquire_mach_task(pid, task, port_set, exception_port, notification_port);
if (kret != KERN_SUCCESS) return -1;
char msg = 'c';
write(fd[1], &msg, 1);
close(fd[1]);
return pid;
}
// Fork succeeded, we are in the child.
int pret;
char sig;
close(fd[1]);
read(fd[0], &sig, 1);
close(fd[0]);
// Set errno to zero before a call to ptrace.
// It is documented that ptrace can return -1 even
// for successful calls.
errno = 0;
pret = ptrace(PT_TRACE_ME, 0, 0, 0);
if (pret != 0 && errno != 0) return -errno;
errno = 0;
pret = ptrace(PT_SIGEXC, 0, 0, 0);
if (pret != 0 && errno != 0) return -errno;
// Create the child process.
execve(argv0, argv, NULL);
exit(1);
}

9
proctl/exec_darwin.h Normal file

@ -0,0 +1,9 @@
#include "proctl_darwin.h"
#include <unistd.h>
#include <sys/ptrace.h>
#include <errno.h>
#include <stdlib.h>
int
fork_exec(char *argv0, char **argv, mach_port_name_t*, mach_port_t*, mach_port_t*, mach_port_t*);

@ -6,12 +6,10 @@ import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
@ -34,6 +32,8 @@ type DebuggedProcess struct {
goSymTable *gosym.Table goSymTable *gosym.Table
frameEntries frame.FrameDescriptionEntries frameEntries frame.FrameDescriptionEntries
lineInfo *line.DebugLineInfo lineInfo *line.DebugLineInfo
firstStart bool
singleStep bool
os *OSProcessDetails os *OSProcessDetails
ast *source.Searcher ast *source.Searcher
breakpointIDCounter int breakpointIDCounter int
@ -63,35 +63,20 @@ func (pe ProcessExitedError) Error() string {
// Attach to an existing process with the given PID. // Attach to an existing process with the given PID.
func Attach(pid int) (*DebuggedProcess, error) { func Attach(pid int) (*DebuggedProcess, error) {
dbp, err := newDebugProcess(pid, true) dbp := &DebuggedProcess{
Pid: pid,
Threads: make(map[int]*ThreadContext),
BreakPoints: make(map[uint64]*BreakPoint),
os: new(OSProcessDetails),
ast: source.New(),
}
dbp, err := initializeDebugProcess(dbp, "", true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return dbp, nil return dbp, nil
} }
// Create and begin debugging a new process. First entry in
// `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process.
func Launch(cmd []string) (*DebuggedProcess, error) {
proc := exec.Command(cmd[0])
proc.Args = cmd
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
if err := proc.Start(); err != nil {
return nil, err
}
_, _, err := wait(proc.Process.Pid, 0)
if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
}
return newDebugProcess(proc.Process.Pid, false)
}
// Returns whether or not Delve thinks the debugged // Returns whether or not Delve thinks the debugged
// process has exited. // process has exited.
func (dbp *DebuggedProcess) Exited() bool { func (dbp *DebuggedProcess) Exited() bool {
@ -109,10 +94,10 @@ func (dbp *DebuggedProcess) Running() bool {
// * Dwarf .debug_frame section // * Dwarf .debug_frame section
// * Dwarf .debug_line section // * Dwarf .debug_line section
// * Go symbol table. // * Go symbol table.
func (dbp *DebuggedProcess) LoadInformation() error { func (dbp *DebuggedProcess) LoadInformation(path string) error {
var wg sync.WaitGroup var wg sync.WaitGroup
exe, err := dbp.findExecutable() exe, err := dbp.findExecutable(path)
if err != nil { if err != nil {
return err return err
} }
@ -367,6 +352,8 @@ func (dbp *DebuggedProcess) resume() error {
// Single step, will execute a single instruction. // Single step, will execute a single instruction.
func (dbp *DebuggedProcess) Step() (err error) { func (dbp *DebuggedProcess) Step() (err error) {
fn := func() error { fn := func() error {
dbp.singleStep = true
defer func() { dbp.singleStep = false }()
for _, th := range dbp.Threads { for _, th := range dbp.Threads {
if th.blocked() { if th.blocked() {
continue continue
@ -491,33 +478,25 @@ func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*BreakPoint, bool) {
} }
// Returns a new DebuggedProcess struct. // Returns a new DebuggedProcess struct.
func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) { func initializeDebugProcess(dbp *DebuggedProcess, path string, attach bool) (*DebuggedProcess, error) {
dbp := DebuggedProcess{
Pid: pid,
Threads: make(map[int]*ThreadContext),
BreakPoints: make(map[uint64]*BreakPoint),
os: new(OSProcessDetails),
ast: source.New(),
}
if attach { if attach {
err := sys.PtraceAttach(pid) err := sys.PtraceAttach(dbp.Pid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, _, err = wait(pid, 0) _, _, err = wait(dbp.Pid, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
proc, err := os.FindProcess(pid) proc, err := os.FindProcess(dbp.Pid)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dbp.Process = proc dbp.Process = proc
err = dbp.LoadInformation() err = dbp.LoadInformation(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -526,7 +505,7 @@ func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
return nil, err return nil, err
} }
return &dbp, nil return dbp, nil
} }
func (dbp *DebuggedProcess) clearTempBreakpoints() error { func (dbp *DebuggedProcess) clearTempBreakpoints() error {
@ -575,7 +554,7 @@ func (dbp *DebuggedProcess) handleBreakpointOnThread(id int) (*ThreadContext, er
if dbp.halt { if dbp.halt {
return thread, nil return thread, nil
} }
return nil, fmt.Errorf("no breakpoint at %#v", pc) return nil, NoBreakPointError{addr: pc}
} }
func (dbp *DebuggedProcess) run(fn func() error) error { func (dbp *DebuggedProcess) run(fn func() error) error {

@ -1,17 +1,20 @@
package proctl package proctl
// #include "proctl_darwin.h" // #include "proctl_darwin.h"
// #include "exec_darwin.h"
import "C" import "C"
import ( import (
"debug/gosym" "debug/gosym"
"debug/macho" "debug/macho"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sync" "sync"
"unsafe" "unsafe"
"github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/source"
sys "golang.org/x/sys/unix" sys "golang.org/x/sys/unix"
) )
@ -22,6 +25,55 @@ type OSProcessDetails struct {
notificationPort C.mach_port_t notificationPort C.mach_port_t
} }
// Create and begin debugging a new process. Uses a
// custom fork/exec process in order to take advantage of
// PT_SIGEXC on Darwin.
func Launch(cmd []string) (*DebuggedProcess, error) {
argv0, err := filepath.Abs(cmd[0])
if err != nil {
return nil, err
}
var (
task C.mach_port_name_t
portSet C.mach_port_t
exceptionPort C.mach_port_t
notificationPort C.mach_port_t
argv = C.CString(cmd[0])
)
if len(cmd) == 1 {
argv = nil
}
pid := int(C.fork_exec(C.CString(argv0), &argv, &task, &portSet, &exceptionPort, &notificationPort))
if pid <= 0 {
return nil, fmt.Errorf("could not fork/exec")
}
dbp := &DebuggedProcess{
Pid: pid,
Threads: make(map[int]*ThreadContext),
BreakPoints: make(map[uint64]*BreakPoint),
firstStart: true,
os: new(OSProcessDetails),
ast: source.New(),
}
dbp.os = &OSProcessDetails{
task: task,
portSet: portSet,
exceptionPort: exceptionPort,
notificationPort: notificationPort,
}
dbp, err = initializeDebugProcess(dbp, argv0, false)
if err != nil {
return nil, err
}
err = dbp.Continue()
return dbp, err
}
func (dbp *DebuggedProcess) requestManualStop() (err error) { func (dbp *DebuggedProcess) requestManualStop() (err error) {
var ( var (
task = C.mach_port_t(dbp.os.task) task = C.mach_port_t(dbp.os.task)
@ -153,16 +205,11 @@ func (dbp *DebuggedProcess) parseDebugLineInfo(exe *macho.File, wg *sync.WaitGro
} }
} }
func (dbp *DebuggedProcess) findExecutable() (*macho.File, error) { func (dbp *DebuggedProcess) findExecutable(path string) (*macho.File, error) {
ret := C.acquire_mach_task(C.int(dbp.Pid), &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort) if path == "" {
if ret != C.KERN_SUCCESS { path = C.GoString(C.find_executable(C.int(dbp.Pid)))
return nil, fmt.Errorf("could not acquire mach task %d", ret)
} }
pathptr, err := C.find_executable(C.int(dbp.Pid)) exe, err := macho.Open(path)
if err != nil {
return nil, err
}
exe, err := macho.Open(C.GoString(pathptr))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -175,32 +222,53 @@ func (dbp *DebuggedProcess) findExecutable() (*macho.File, error) {
} }
func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) { func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
port := C.mach_port_wait(dbp.os.portSet) var (
th *ThreadContext
err error
)
for {
port := C.mach_port_wait(dbp.os.portSet)
switch port { switch port {
case dbp.os.notificationPort: case dbp.os.notificationPort:
_, status, err := wait(dbp.Pid, 0) _, status, err := wait(dbp.Pid, 0)
if err != nil {
return nil, err
}
dbp.exited = true
return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
case C.MACH_RCV_INTERRUPTED:
if !dbp.halt {
// Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_.
return dbp.trapWait(pid)
}
return nil, ManualStopError{}
case 0:
return nil, fmt.Errorf("error while waiting for task")
}
// Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them.
dbp.updateThreadList()
th, err = dbp.handleBreakpointOnThread(int(port))
if err != nil { if err != nil {
if _, ok := err.(NoBreakPointError); ok {
if dbp.firstStart || dbp.singleStep {
dbp.firstStart = false
return dbp.Threads[int(port)], nil
}
if th, ok := dbp.Threads[int(port)]; ok {
th.Continue()
}
continue
}
return nil, err return nil, err
} }
dbp.exited = true return th, nil
return nil, ProcessExitedError{Pid: dbp.Pid, Status: status.ExitStatus()}
case C.MACH_RCV_INTERRUPTED:
if !dbp.halt {
// Call trapWait again, it seems
// MACH_RCV_INTERRUPTED is emitted before
// process natural death _sometimes_.
return dbp.trapWait(pid)
}
return nil, ManualStopError{}
case 0:
return nil, fmt.Errorf("error while waiting for task")
} }
return th, nil
// Since we cannot be notified of new threads on OS X
// this is as good a time as any to check for them.
dbp.updateThreadList()
return dbp.handleBreakpointOnThread(int(port))
} }
func wait(pid, options int) (int, *sys.WaitStatus, error) { func wait(pid, options int) (int, *sys.WaitStatus, error) {

@ -5,6 +5,7 @@ import (
"debug/gosym" "debug/gosym"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"sync" "sync"
@ -14,6 +15,7 @@ import (
"github.com/derekparker/delve/dwarf/frame" "github.com/derekparker/delve/dwarf/frame"
"github.com/derekparker/delve/dwarf/line" "github.com/derekparker/delve/dwarf/line"
"github.com/derekparker/delve/source"
) )
const ( const (
@ -25,6 +27,34 @@ const (
// Not actually needed for Linux. // Not actually needed for Linux.
type OSProcessDetails interface{} type OSProcessDetails interface{}
// Create and begin debugging a new process. First entry in
// `cmd` is the program to run, and then rest are the arguments
// to be supplied to that process.
func Launch(cmd []string) (*DebuggedProcess, error) {
proc := exec.Command(cmd[0])
proc.Args = cmd
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.SysProcAttr = &syscall.SysProcAttr{Ptrace: true}
if err := proc.Start(); err != nil {
return nil, err
}
_, _, err := wait(proc.Process.Pid, 0)
if err != nil {
return nil, fmt.Errorf("waiting for target execve failed: %s", err)
}
dbp := &DebuggedProcess{
Pid: proc.Process.Pid,
Threads: make(map[int]*ThreadContext),
BreakPoints: make(map[uint64]*BreakPoint),
os: new(OSProcessDetails),
ast: source.New(),
}
return initializeDebugProcess(dbp, proc.Path, false)
}
func (dbp *DebuggedProcess) requestManualStop() (err error) { func (dbp *DebuggedProcess) requestManualStop() (err error) {
return sys.Kill(dbp.Pid, sys.SIGSTOP) return sys.Kill(dbp.Pid, sys.SIGSTOP)
} }
@ -102,10 +132,11 @@ func (dbp *DebuggedProcess) updateThreadList() error {
return nil return nil
} }
func (dbp *DebuggedProcess) findExecutable() (*elf.File, error) { func (dbp *DebuggedProcess) findExecutable(path string) (*elf.File, error) {
procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid) if path == "" {
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
f, err := os.OpenFile(procpath, 0, os.ModePerm) }
f, err := os.OpenFile(path, 0, os.ModePerm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -200,13 +231,17 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
if wpid == 0 { if wpid == 0 {
continue continue
} }
if th, ok := dbp.Threads[wpid]; ok { th, ok := dbp.Threads[wpid]
if ok {
th.Status = status th.Status = status
} }
if status.Exited() && wpid == dbp.Pid { if status.Exited() {
dbp.exited = true if wpid == dbp.Pid {
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()} dbp.exited = true
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()}
}
continue
} }
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE { if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
// A traced thread has cloned a new thread, grab the pid and // A traced thread has cloned a new thread, grab the pid and
@ -216,7 +251,7 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
return nil, fmt.Errorf("could not get event message: %s", err) return nil, fmt.Errorf("could not get event message: %s", err)
} }
th, err := dbp.addThread(int(cloned), false) th, err = dbp.addThread(int(cloned), false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -238,6 +273,11 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
if status.StopSignal() == sys.SIGSTOP && dbp.halt { if status.StopSignal() == sys.SIGSTOP && dbp.halt {
return nil, ManualStopError{} return nil, ManualStopError{}
} }
if th != nil {
if err := th.Continue(); err != nil {
return nil, err
}
}
} }
} }

@ -77,7 +77,7 @@ func TestExit(t *testing.T) {
err := p.Continue() err := p.Continue()
pe, ok := err.(ProcessExitedError) pe, ok := err.(ProcessExitedError)
if !ok { if !ok {
t.Fatalf("Continue() returned unexpected error type") t.Fatalf("Continue() returned unexpected error type %s", err)
} }
if pe.Status != 0 { if pe.Status != 0 {
t.Errorf("Unexpected error status: %d", pe.Status) t.Errorf("Unexpected error status: %d", pe.Status)

@ -112,6 +112,7 @@ func TestVariableFunctionScoping(t *testing.T) {
err = p.Continue() err = p.Continue()
assertNoError(err, t, "Continue() returned an error") assertNoError(err, t, "Continue() returned an error")
p.Clear(pc)
_, err = p.EvalSymbol("a1") _, err = p.EvalSymbol("a1")
assertNoError(err, t, "Unable to find variable a1") assertNoError(err, t, "Unable to find variable a1")