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:
parent
047a91af5b
commit
173ee20097
49
proctl/exec_darwin.c
Normal file
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
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"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
sys "golang.org/x/sys/unix"
|
||||
|
||||
@ -34,6 +32,8 @@ type DebuggedProcess struct {
|
||||
goSymTable *gosym.Table
|
||||
frameEntries frame.FrameDescriptionEntries
|
||||
lineInfo *line.DebugLineInfo
|
||||
firstStart bool
|
||||
singleStep bool
|
||||
os *OSProcessDetails
|
||||
ast *source.Searcher
|
||||
breakpointIDCounter int
|
||||
@ -63,35 +63,20 @@ func (pe ProcessExitedError) Error() string {
|
||||
|
||||
// Attach to an existing process with the given PID.
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
// process has exited.
|
||||
func (dbp *DebuggedProcess) Exited() bool {
|
||||
@ -109,10 +94,10 @@ func (dbp *DebuggedProcess) Running() bool {
|
||||
// * Dwarf .debug_frame section
|
||||
// * Dwarf .debug_line section
|
||||
// * Go symbol table.
|
||||
func (dbp *DebuggedProcess) LoadInformation() error {
|
||||
func (dbp *DebuggedProcess) LoadInformation(path string) error {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
exe, err := dbp.findExecutable()
|
||||
exe, err := dbp.findExecutable(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -367,6 +352,8 @@ func (dbp *DebuggedProcess) resume() error {
|
||||
// Single step, will execute a single instruction.
|
||||
func (dbp *DebuggedProcess) Step() (err error) {
|
||||
fn := func() error {
|
||||
dbp.singleStep = true
|
||||
defer func() { dbp.singleStep = false }()
|
||||
for _, th := range dbp.Threads {
|
||||
if th.blocked() {
|
||||
continue
|
||||
@ -491,33 +478,25 @@ func (dbp *DebuggedProcess) FindBreakpoint(pc uint64) (*BreakPoint, bool) {
|
||||
}
|
||||
|
||||
// Returns a new DebuggedProcess struct.
|
||||
func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
|
||||
dbp := DebuggedProcess{
|
||||
Pid: pid,
|
||||
Threads: make(map[int]*ThreadContext),
|
||||
BreakPoints: make(map[uint64]*BreakPoint),
|
||||
os: new(OSProcessDetails),
|
||||
ast: source.New(),
|
||||
}
|
||||
|
||||
func initializeDebugProcess(dbp *DebuggedProcess, path string, attach bool) (*DebuggedProcess, error) {
|
||||
if attach {
|
||||
err := sys.PtraceAttach(pid)
|
||||
err := sys.PtraceAttach(dbp.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, err = wait(pid, 0)
|
||||
_, _, err = wait(dbp.Pid, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
proc, err := os.FindProcess(dbp.Pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbp.Process = proc
|
||||
err = dbp.LoadInformation()
|
||||
err = dbp.LoadInformation(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -526,7 +505,7 @@ func newDebugProcess(pid int, attach bool) (*DebuggedProcess, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dbp, nil
|
||||
return dbp, nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) clearTempBreakpoints() error {
|
||||
@ -575,7 +554,7 @@ func (dbp *DebuggedProcess) handleBreakpointOnThread(id int) (*ThreadContext, er
|
||||
if dbp.halt {
|
||||
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 {
|
||||
|
@ -1,17 +1,20 @@
|
||||
package proctl
|
||||
|
||||
// #include "proctl_darwin.h"
|
||||
// #include "exec_darwin.h"
|
||||
import "C"
|
||||
import (
|
||||
"debug/gosym"
|
||||
"debug/macho"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/line"
|
||||
"github.com/derekparker/delve/source"
|
||||
sys "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -22,6 +25,55 @@ type OSProcessDetails struct {
|
||||
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, ¬ificationPort))
|
||||
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) {
|
||||
var (
|
||||
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) {
|
||||
ret := C.acquire_mach_task(C.int(dbp.Pid), &dbp.os.task, &dbp.os.portSet, &dbp.os.exceptionPort, &dbp.os.notificationPort)
|
||||
if ret != C.KERN_SUCCESS {
|
||||
return nil, fmt.Errorf("could not acquire mach task %d", ret)
|
||||
func (dbp *DebuggedProcess) findExecutable(path string) (*macho.File, error) {
|
||||
if path == "" {
|
||||
path = C.GoString(C.find_executable(C.int(dbp.Pid)))
|
||||
}
|
||||
pathptr, err := C.find_executable(C.int(dbp.Pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
exe, err := macho.Open(C.GoString(pathptr))
|
||||
exe, err := macho.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -175,32 +222,53 @@ func (dbp *DebuggedProcess) findExecutable() (*macho.File, 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 {
|
||||
case dbp.os.notificationPort:
|
||||
_, status, err := wait(dbp.Pid, 0)
|
||||
switch port {
|
||||
case dbp.os.notificationPort:
|
||||
_, 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 _, 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
|
||||
}
|
||||
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")
|
||||
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))
|
||||
return th, nil
|
||||
}
|
||||
|
||||
func wait(pid, options int) (int, *sys.WaitStatus, error) {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"debug/gosym"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
@ -14,6 +15,7 @@ import (
|
||||
|
||||
"github.com/derekparker/delve/dwarf/frame"
|
||||
"github.com/derekparker/delve/dwarf/line"
|
||||
"github.com/derekparker/delve/source"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -25,6 +27,34 @@ const (
|
||||
// Not actually needed for Linux.
|
||||
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) {
|
||||
return sys.Kill(dbp.Pid, sys.SIGSTOP)
|
||||
}
|
||||
@ -102,10 +132,11 @@ func (dbp *DebuggedProcess) updateThreadList() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dbp *DebuggedProcess) findExecutable() (*elf.File, error) {
|
||||
procpath := fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
||||
|
||||
f, err := os.OpenFile(procpath, 0, os.ModePerm)
|
||||
func (dbp *DebuggedProcess) findExecutable(path string) (*elf.File, error) {
|
||||
if path == "" {
|
||||
path = fmt.Sprintf("/proc/%d/exe", dbp.Pid)
|
||||
}
|
||||
f, err := os.OpenFile(path, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -200,13 +231,17 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
|
||||
if wpid == 0 {
|
||||
continue
|
||||
}
|
||||
if th, ok := dbp.Threads[wpid]; ok {
|
||||
th, ok := dbp.Threads[wpid]
|
||||
if ok {
|
||||
th.Status = status
|
||||
}
|
||||
|
||||
if status.Exited() && wpid == dbp.Pid {
|
||||
dbp.exited = true
|
||||
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()}
|
||||
if status.Exited() {
|
||||
if wpid == dbp.Pid {
|
||||
dbp.exited = true
|
||||
return nil, ProcessExitedError{Pid: wpid, Status: status.ExitStatus()}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if status.StopSignal() == sys.SIGTRAP && status.TrapCause() == sys.PTRACE_EVENT_CLONE {
|
||||
// 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)
|
||||
}
|
||||
|
||||
th, err := dbp.addThread(int(cloned), false)
|
||||
th, err = dbp.addThread(int(cloned), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -238,6 +273,11 @@ func (dbp *DebuggedProcess) trapWait(pid int) (*ThreadContext, error) {
|
||||
if status.StopSignal() == sys.SIGSTOP && dbp.halt {
|
||||
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()
|
||||
pe, ok := err.(ProcessExitedError)
|
||||
if !ok {
|
||||
t.Fatalf("Continue() returned unexpected error type")
|
||||
t.Fatalf("Continue() returned unexpected error type %s", err)
|
||||
}
|
||||
if pe.Status != 0 {
|
||||
t.Errorf("Unexpected error status: %d", pe.Status)
|
||||
|
@ -112,6 +112,7 @@ func TestVariableFunctionScoping(t *testing.T) {
|
||||
|
||||
err = p.Continue()
|
||||
assertNoError(err, t, "Continue() returned an error")
|
||||
p.Clear(pc)
|
||||
|
||||
_, err = p.EvalSymbol("a1")
|
||||
assertNoError(err, t, "Unable to find variable a1")
|
||||
|
Loading…
Reference in New Issue
Block a user