]> Cypherpunks repositories - gostls13.git/commitdiff
import debug/proc from usr/austin/ptrace
authorRuss Cox <rsc@golang.org>
Tue, 1 Sep 2009 18:51:05 +0000 (11:51 -0700)
committerRuss Cox <rsc@golang.org>
Tue, 1 Sep 2009 18:51:05 +0000 (11:51 -0700)
R=austin
DELTA=1892  (1892 added, 0 deleted, 0 changed)
OCL=34183
CL=34197

src/pkg/debug/proc/Makefile [new file with mode: 0644]
src/pkg/debug/proc/proc.go [new file with mode: 0644]
src/pkg/debug/proc/proc_darwin.go [new file with mode: 0644]
src/pkg/debug/proc/proc_linux.go [new file with mode: 0644]
src/pkg/debug/proc/ptrace-nptl.txt [new file with mode: 0644]
src/pkg/debug/proc/regs_darwin_386.go [new file with mode: 0644]
src/pkg/debug/proc/regs_darwin_amd64.go [new file with mode: 0644]
src/pkg/debug/proc/regs_linux_386.go [new file with mode: 0644]
src/pkg/debug/proc/regs_linux_amd64.go [new file with mode: 0644]

diff --git a/src/pkg/debug/proc/Makefile b/src/pkg/debug/proc/Makefile
new file mode 100644 (file)
index 0000000..988c495
--- /dev/null
@@ -0,0 +1,13 @@
+# Copyright 2009 The Go Authors.  All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include $(GOROOT)/src/Make.$(GOARCH)
+
+TARG=ptrace
+GOFILES=\
+       proc.go\
+       proc_linux.go\
+       regs_$(GOOS)_$(GOARCH).go\
+
+include $(GOROOT)/src/Make.pkg
diff --git a/src/pkg/debug/proc/proc.go b/src/pkg/debug/proc/proc.go
new file mode 100644 (file)
index 0000000..023f477
--- /dev/null
@@ -0,0 +1,232 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package ptrace provides a platform-independent interface for
+// tracing and controlling running processes.  It supports
+// multi-threaded processes and provides typical low-level debugging
+// controls such as breakpoints, single stepping, and manipulating
+// memory and registers.
+package proc
+
+import (
+       "os";
+       "strconv";
+)
+
+type Word uint64
+
+// A Cause explains why a thread is stopped.
+type Cause interface {
+       String() string;
+}
+
+// Regs is a set of named machine registers, including a program
+// counter, link register, and stack pointer.
+//
+// TODO(austin) There's quite a proliferation of methods here.  We
+// could make a Reg interface with Get and Set and make this just PC,
+// Link, SP, Names, and Reg.  We could also put Index in Reg and that
+// makes it easy to get the index of things like the PC (currently
+// there's just no way to know that).  This would also let us include
+// other per-register information like how to print it.
+type Regs interface {
+       // PC returns the value of the program counter.
+       PC() Word;
+
+       // SetPC sets the program counter to val.
+       SetPC(val Word) os.Error;
+
+       // Link returns the link register, if any.
+       Link() Word;
+
+       // SetLink sets the link register to val.
+       SetLink(val Word) os.Error;
+
+       // SP returns the value of the stack pointer.
+       SP() Word;
+
+       // SetSP sets the stack pointer register to val.
+       SetSP(val Word) os.Error;
+
+       // Names returns the names of all of the registers.
+       Names() []string;
+
+       // Get returns the value of a register, where i corresponds to
+       // the index of the register's name in the array returned by
+       // Names.
+       Get(i int) Word;
+
+       // Set sets the value of a register.
+       Set(i int, val Word) os.Error;
+}
+
+// Thread is a thread in the process being traced.
+type Thread interface {
+       // Step steps this thread by a single instruction.  The thread
+       // must be stopped.  If the thread is currently stopped on a
+       // breakpoint, this will step over the breakpoint.
+       //
+       // XXX What if it's stopped because of a signal?
+       Step() os.Error;
+
+       // Stopped returns the reason that this thread is stopped.  It
+       // is an error is the thread not stopped.
+       Stopped() (Cause, os.Error);
+
+       // Regs retrieves the current register values from this
+       // thread.  The thread must be stopped.
+       Regs() (Regs, os.Error);
+
+       // Peek reads len(out) bytes from the address addr in this
+       // thread into out.  The thread must be stopped.  It returns
+       // the number of bytes successfully read.  If an error occurs,
+       // such as attempting to read unmapped memory, this count
+       // could be short and an error will be returned.  If this does
+       // encounter unmapped memory, it will read up to the byte
+       // preceding the unmapped area.
+       Peek(addr Word, out []byte) (int, os.Error);
+
+       // Poke writes b to the address addr in this thread.  The
+       // thread must be stopped.  It returns the number of bytes
+       // successfully written.  If an error occurs, such as
+       // attempting to write to unmapped memory, this count could be
+       // short and an error will be returned.  If this does
+       // encounter unmapped memory, it will write up to the byte
+       // preceding the unmapped area.
+       Poke(addr Word, b []byte) (int, os.Error);
+}
+
+// Process is a process being traced.  It consists of a set of
+// threads.  A process can be running, stopped, or terminated.  The
+// process's state extends to all of its threads.
+type Process interface {
+       // Threads returns an array of all threads in this process.
+       Threads() []Thread;
+
+       // AddBreakpoint creates a new breakpoint at program counter
+       // pc.  Breakpoints can only be created when the process is
+       // stopped.  It is an error if a breakpoint already exists at
+       // pc.
+       AddBreakpoint(pc Word) os.Error;
+
+       // RemoveBreakpoint removes the breakpoint at the program
+       // counter pc.  It is an error if no breakpoint exists at pc.
+       RemoveBreakpoint(pc Word) os.Error;
+
+       // Stop stops all running threads in this process before
+       // returning.
+       Stop() os.Error;
+
+       // Continue resumes execution of all threads in this process.
+       // Any thread that is stopped on a breakpoint will be stepped
+       // over that breakpoint.  Any thread that is stopped because
+       // of a signal (other than SIGSTOP or SIGTRAP) will receive
+       // the pending signal.
+       Continue() os.Error;
+
+       // WaitStop waits until all threads in process p are stopped
+       // as a result of some thread hitting a breakpoint, receiving
+       // a signal, creating a new thread, or exiting.
+       WaitStop() os.Error;
+
+       // Detach detaches from this process.  All stopped threads
+       // will be resumed.
+       Detach() os.Error;
+}
+
+// Stopped is a stop cause used for threads that are stopped either by
+// user request (e.g., from the Stop method or after single stepping),
+// or that are stopped because some other thread caused the program to
+// stop.
+type Stopped struct {}
+
+func (c Stopped) String() string {
+       return "stopped";
+}
+
+// Breakpoint is a stop cause resulting from a thread reaching a set
+// breakpoint.
+type Breakpoint        Word
+
+// PC returns the program counter that the program is stopped at.
+func (c Breakpoint) PC() Word {
+       return Word(c);
+}
+
+func (c Breakpoint) String() string {
+       return "breakpoint at 0x" + strconv.Uitob64(uint64(c.PC()), 16);
+}
+
+// Signal is a stop cause resulting from a thread receiving a signal.
+// When the process is continued, the signal will be delivered.
+type Signal string
+
+// Signal returns the signal being delivered to the thread.
+func (c Signal) Name() string {
+       return string(c);
+}
+
+func (c Signal) String() string {
+       return c.Name();
+}
+
+// ThreadCreate is a stop cause returned from an existing thread when
+// it creates a new thread.  The new thread exists in a primordial
+// form at this point and will begin executing in earnest when the
+// process is continued.
+type ThreadCreate struct {
+       thread Thread;
+}
+
+func (c *ThreadCreate) NewThread() Thread {
+       return c.thread;
+}
+
+func (c *ThreadCreate) String() string {
+       return "thread create";
+}
+
+// ThreadExit is a stop cause resulting from a thread exiting.  When
+// this cause first arises, the thread will still be in the list of
+// process threads and its registers and memory will still be
+// accessible.
+type ThreadExit struct {
+       exitStatus int;
+       signal string;
+}
+
+// Exited returns true if the thread exited normally.
+func (c *ThreadExit) Exited() bool {
+       return c.exitStatus != -1;
+}
+
+// ExitStatus returns the exit status of the thread if it exited
+// normally or -1 otherwise.
+func (c *ThreadExit) ExitStatus() int {
+       return c.exitStatus;
+}
+
+// Signaled returns true if the thread was terminated by a signal.
+func (c *ThreadExit) Signaled() bool {
+       return c.exitStatus == -1;
+}
+
+// StopSignal returns the signal that terminated the thread, or "" if
+// it was not terminated by a signal.
+func (c *ThreadExit) StopSignal() string {
+       return c.signal;
+}
+
+func (c *ThreadExit) String() string {
+       res := "thread exited ";
+       switch {
+       case c.Exited():
+               res += "with status " + strconv.Itoa(c.ExitStatus());
+       case c.Signaled():
+               res += "from signal " + c.StopSignal();
+       default:
+               res += "from unknown cause";
+       }
+       return res;
+}
diff --git a/src/pkg/debug/proc/proc_darwin.go b/src/pkg/debug/proc/proc_darwin.go
new file mode 100644 (file)
index 0000000..2c7b423
--- /dev/null
@@ -0,0 +1,17 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import "os"
+
+// Process tracing is not supported on OS X yet.
+
+func Attach(pid int) (Process, os.Error) {
+       return nil, os.NewError("debug/proc not implemented on OS X");
+}
+
+func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) {
+       return Attach(0);
+}
diff --git a/src/pkg/debug/proc/proc_linux.go b/src/pkg/debug/proc/proc_linux.go
new file mode 100644 (file)
index 0000000..8826910
--- /dev/null
@@ -0,0 +1,1316 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import (
+       "container/vector";
+       "fmt";
+       "io";
+       "os";
+       "runtime";
+       "strconv";
+       "strings";
+       "sync";
+       "syscall";
+)
+
+// This is an implementation of the process tracing interface using
+// Linux's ptrace(2) interface.  The implementation is multi-threaded.
+// Each attached process has an associated monitor thread, and each
+// running attached thread has an associated "wait" thread.  The wait
+// thread calls wait4 on the thread's TID and reports any wait events
+// or errors via "debug events".  The monitor thread consumes these
+// wait events and updates the internally maintained state of each
+// thread.  All ptrace calls must run in the monitor thread, so the
+// monitor executes closures received on the debugReq channel.
+//
+// As ptrace's documentation is somewhat light, this is heavily based
+// on information gleaned from the implementation of ptrace found at
+//   http://lxr.linux.no/linux+v2.6.30/kernel/ptrace.c
+//   http://lxr.linux.no/linux+v2.6.30/arch/x86/kernel/ptrace.c#L854
+// as well as experimentation and examination of gdb's behavior.
+
+const (
+       trace = false;
+       traceIP = false;
+       traceMem = false;
+)
+
+/*
+ * Thread state
+ */
+
+// Each thread can be in one of the following set of states.
+// Each state satisfies
+//  isRunning() || isStopped() || isZombie() || isTerminal().
+//
+// Running threads can be sent signals and must be waited on, but they
+// cannot be inspected using ptrace.
+//
+// Stopped threads can be inspected and continued, but cannot be
+// meaningfully waited on.  They can be sent signals, but the signals
+// will be queued until they are running again.
+//
+// Zombie threads cannot be inspected, continued, or sent signals (and
+// therefore they cannot be stopped), but they must be waited on.
+//
+// Terminal threads no longer exist in the OS and thus you can't do
+// anything with them.
+type threadState string;
+
+const (
+       running             threadState = "Running";
+       singleStepping      threadState = "SingleStepping";     // Transient
+       stopping            threadState = "Stopping";   // Transient
+       stopped             threadState = "Stopped";
+       stoppedBreakpoint   threadState = "StoppedBreakpoint";
+       stoppedSignal       threadState = "StoppedSignal";
+       stoppedThreadCreate threadState = "StoppedThreadCreate";
+       stoppedExiting      threadState = "StoppedExiting";
+       exiting             threadState = "Exiting";    // Transient (except main thread)
+       exited              threadState = "Exited";
+       detached            threadState = "Detached";
+)
+
+func (ts threadState) isRunning() bool {
+       return ts == running || ts == singleStepping || ts == stopping;
+}
+
+func (ts threadState) isStopped() bool {
+       return ts == stopped || ts == stoppedBreakpoint || ts == stoppedSignal || ts == stoppedThreadCreate || ts == stoppedExiting;
+}
+
+func (ts threadState) isZombie() bool {
+       return ts == exiting;
+}
+
+func (ts threadState) isTerminal() bool {
+       return ts == exited || ts == detached;
+}
+
+func (ts threadState) String() string {
+       return string(ts);
+}
+
+/*
+ * Basic types
+ */
+
+// A breakpoint stores information about a single breakpoint,
+// including its program counter, the overwritten text if the
+// breakpoint is installed.
+type breakpoint struct {
+       pc uintptr;
+       olddata []byte;
+}
+
+func (bp *breakpoint) String() string {
+       if bp == nil {
+               return "<nil>";
+       }
+       return fmt.Sprintf("%#x", bp.pc);
+}
+
+// bpinst386 is the breakpoint instruction used on 386 and amd64.
+var bpinst386 = []byte{0xcc};
+
+// A debugEvent represents a reason a thread stopped or a wait error.
+type debugEvent struct {
+       *os.Waitmsg;
+       t *thread;
+       err os.Error;
+}
+
+// A debugReq is a request to execute a closure in the monitor thread.
+type debugReq struct {
+       f func () os.Error;
+       res chan os.Error;
+}
+
+// A transitionHandler specifies a function to be called when a thread
+// changes state and a function to be called when an error occurs in
+// the monitor.  Both run in the monitor thread.  Before the monitor
+// invokes a handler, it removes the handler from the handler queue.
+// The handler should re-add itself if needed.
+type transitionHandler struct {
+       handle func (*thread, threadState, threadState);
+       onErr func (os.Error);
+}
+
+// A process is a Linux process, which consists of a set of threads.
+// Each running process has one monitor thread, which processes
+// messages from the debugEvents, debugReqs, and stopReq channels and
+// calls transition handlers.
+type process struct {
+       pid int;
+       threads map[int] *thread;
+       breakpoints map[uintptr] *breakpoint;
+       debugEvents chan *debugEvent;
+       debugReqs chan *debugReq;
+       stopReq chan os.Error;
+       transitionHandlers *vector.Vector;
+}
+
+// A thread represents a Linux thread in another process that is being
+// debugged.  Each running thread has an associated goroutine that
+// waits for thread updates and sends them to the process monitor.
+type thread struct {
+       tid int;
+       proc *process;
+       // Whether to ignore the next SIGSTOP received by wait.
+       ignoreNextSigstop bool;
+
+       // Thread state.  Only modified via setState.
+       state threadState;
+       // If state == StoppedBreakpoint
+       breakpoint *breakpoint;
+       // If state == StoppedSignal or state == Exited
+       signal int;
+       // If state == StoppedThreadCreate
+       newThread *thread;
+       // If state == Exited
+       exitStatus int;
+}
+
+/*
+ * Errors
+ */
+
+type badState struct {
+       thread *thread;
+       message string;
+       state threadState;
+}
+
+func (e *badState) String() string {
+       return fmt.Sprintf("Thread %d %s from state %v", e.thread.tid, e.message, e.state);
+}
+
+type breakpointExistsError Word
+
+func (e breakpointExistsError) String() string {
+       return fmt.Sprintf("breakpoint already exists at PC %#x", e);
+}
+
+type noBreakpointError Word
+
+func (e noBreakpointError) String() string {
+       return fmt.Sprintf("no breakpoint at PC %#x", e);
+}
+
+type newThreadError struct {
+       *os.Waitmsg;
+       wantPid int;
+       wantSig int;
+}
+
+func (e *newThreadError) String() string {
+       return fmt.Sprintf("newThread wait wanted pid %v and signal %v, got %v and %v", e.Pid, e.StopSignal(), e.wantPid, e.wantSig);
+}
+
+/*
+ * Ptrace wrappers
+ */
+
+func (t *thread) ptracePeekText(addr uintptr, out []byte) (int, os.Error) {
+       c, err := syscall.PtracePeekText(t.tid, addr, out);
+       if traceMem {
+               fmt.Printf("peek(%#x) => %v, %v\n", addr, out, err);
+       }
+       return c, os.NewSyscallError("ptrace(PEEKTEXT)", err);
+}
+
+func (t *thread) ptracePokeText(addr uintptr, out []byte) (int, os.Error) {
+       c, err := syscall.PtracePokeText(t.tid, addr, out);
+       if traceMem {
+               fmt.Printf("poke(%#x, %v) => %v\n", addr, out, err);
+       }
+       return c, os.NewSyscallError("ptrace(POKETEXT)", err);
+}
+
+func (t *thread) ptraceGetRegs(regs *syscall.PtraceRegs) os.Error {
+       err := syscall.PtraceGetRegs(t.tid, regs);
+       return os.NewSyscallError("ptrace(GETREGS)", err);
+}
+
+func (t *thread) ptraceSetRegs(regs *syscall.PtraceRegs) os.Error {
+       err := syscall.PtraceSetRegs(t.tid, regs);
+       return os.NewSyscallError("ptrace(SETREGS)", err);
+}
+
+func (t *thread) ptraceSetOptions(options int) os.Error {
+       err := syscall.PtraceSetOptions(t.tid, options);
+       return os.NewSyscallError("ptrace(SETOPTIONS)", err);
+}
+
+func (t *thread) ptraceGetEventMsg() (uint, os.Error) {
+       msg, err := syscall.PtraceGetEventMsg(t.tid);
+       return msg, os.NewSyscallError("ptrace(GETEVENTMSG)", err);
+}
+
+func (t *thread) ptraceCont() os.Error {
+       err := syscall.PtraceCont(t.tid, 0);
+       return os.NewSyscallError("ptrace(CONT)", err);
+}
+
+func (t *thread) ptraceContWithSignal(sig int) os.Error {
+       err := syscall.PtraceCont(t.tid, sig);
+       return os.NewSyscallError("ptrace(CONT)", err);
+}
+
+func (t *thread) ptraceStep() os.Error {
+       err := syscall.PtraceSingleStep(t.tid);
+       return os.NewSyscallError("ptrace(SINGLESTEP)", err);
+}
+
+func (t *thread) ptraceDetach() os.Error {
+       err := syscall.PtraceDetach(t.tid);
+       return os.NewSyscallError("ptrace(DETACH)", err);
+}
+
+/*
+ * Logging utilties
+ */
+
+var logLock sync.Mutex
+
+func (t *thread) logTrace(format string, args ...) {
+       if !trace {
+               return;
+       }
+       logLock.Lock();
+       defer logLock.Unlock();
+       fmt.Fprintf(os.Stderr, "Thread %d", t.tid);
+       if traceIP {
+               var regs syscall.PtraceRegs;
+               err := t.ptraceGetRegs(&regs);
+               if err == nil {
+                       fmt.Fprintf(os.Stderr, "@%x", regs.Rip);
+               }
+       }
+       fmt.Fprint(os.Stderr, ": ");
+       fmt.Fprintf(os.Stderr, format, args);
+       fmt.Fprint(os.Stderr, "\n");
+}
+
+func (t *thread) warn(format string, args ...) {
+       logLock.Lock();
+       defer logLock.Unlock();
+       fmt.Fprintf(os.Stderr, "Thread %d: WARNING ", t.tid);
+       fmt.Fprintf(os.Stderr, format, args);
+       fmt.Fprint(os.Stderr, "\n");
+}
+
+func (p *process) logTrace(format string, args ...) {
+       if !trace {
+               return;
+       }
+       logLock.Lock();
+       defer logLock.Unlock();
+       fmt.Fprintf(os.Stderr, "Process %d: ", p.pid);
+       fmt.Fprintf(os.Stderr, format, args);
+       fmt.Fprint(os.Stderr, "\n");
+}
+
+/*
+ * State utilities
+ */
+
+// someStoppedThread returns a stopped thread from the process.
+// Returns nil if no threads are stopped.
+//
+// Must be called from the monitor thread.
+func (p *process) someStoppedThread() *thread {
+       for _, t := range p.threads {
+               if t.state.isStopped() {
+                       return t;
+               }
+       }
+       return nil;
+}
+
+// someRunningThread returns a running thread from the process.
+// Returns nil if no threads are running.
+//
+// Must be called from the monitor thread.
+func (p *process) someRunningThread() *thread {
+       for _, t := range p.threads {
+               if t.state.isRunning() {
+                       return t;
+               }
+       }
+       return nil;
+}
+
+/*
+ * Breakpoint utilities
+ */
+
+// installBreakpoints adds breakpoints to the attached process.
+//
+// Must be called from the monitor thread.
+func (p *process) installBreakpoints() os.Error {
+       n := 0;
+       main := p.someStoppedThread();
+       for _, b := range p.breakpoints {
+               if b.olddata != nil {
+                       continue;
+               }
+
+               b.olddata = make([]byte, len(bpinst386));
+               _, err := main.ptracePeekText(uintptr(b.pc), b.olddata);
+               if err != nil {
+                       b.olddata = nil;
+                       return err;
+               }
+
+               _, err = main.ptracePokeText(uintptr(b.pc), bpinst386);
+               if err != nil {
+                       b.olddata = nil;
+                       return err;
+               }
+               n++;
+       }
+       if n > 0 {
+               p.logTrace("installed %d/%d breakpoints", n, len(p.breakpoints));
+       }
+
+       return nil;
+}
+
+// uninstallBreakpoints removes the installed breakpoints from p.
+//
+// Must be called from the monitor thread.
+func (p *process) uninstallBreakpoints() os.Error {
+       n := 0;
+       main := p.someStoppedThread();
+       for _, b := range p.breakpoints {
+               if b.olddata == nil {
+                       continue;
+               }
+
+               _, err := main.ptracePokeText(uintptr(b.pc), b.olddata);
+               if err != nil {
+                       return err;
+               }
+               b.olddata = nil;
+               n++;
+       }
+       if n > 0 {
+               p.logTrace("uninstalled %d/%d breakpoints", n, len(p.breakpoints));
+       }
+
+       return nil;
+}
+
+/*
+ * Debug event handling
+ */
+
+// wait waits for a wait event from this thread and sends it on the
+// debug events channel for this thread's process.  This should be
+// started in its own goroutine when the attached thread enters a
+// running state.  The goroutine will exit as soon as it sends a debug
+// event.
+func (t *thread) wait() {
+       for {
+               var err os.Error;
+               var ev debugEvent;
+               ev.t = t;
+               t.logTrace("beginning wait");
+               ev.Waitmsg, ev.err = os.Wait(t.tid, syscall.WALL);
+               if ev.err == nil && ev.Pid != t.tid {
+                       panic("Wait returned pid ", ev.Pid, " wanted ", t.tid);
+               }
+               if ev.StopSignal() == syscall.SIGSTOP && t.ignoreNextSigstop {
+                       // Spurious SIGSTOP.  See Thread.Stop().
+                       t.ignoreNextSigstop = false;
+                       err := t.ptraceCont();
+                       if err == nil {
+                               continue;
+                       }
+                       // If we failed to continue, just let
+                       // the stop go through so we can
+                       // update the thread's state.
+               }
+               t.proc.debugEvents <- &ev;
+               break;
+       }
+}
+
+// setState sets this thread's state, starts a wait thread if
+// necessary, and invokes state transition handlers.
+//
+// Must be called from the monitor thread.
+func (t *thread) setState(new threadState) {
+       old := t.state;
+       t.state = new;
+       t.logTrace("state %v -> %v", old, new);
+
+       if !old.isRunning() && (new.isRunning() || new.isZombie()) {
+               // Start waiting on this thread
+               go t.wait();
+       }
+
+       // Invoke state change handlers
+       handlers := t.proc.transitionHandlers;
+       if handlers.Len() == 0 {
+               return;
+       }
+
+       t.proc.transitionHandlers = vector.New(0);
+       for _, h := range handlers.Data() {
+               h := h.(*transitionHandler);
+               h.handle(t, old, new);
+       }
+}
+
+// sendSigstop sends a SIGSTOP to this thread.
+func (t *thread) sendSigstop() os.Error {
+       t.logTrace("sending SIGSTOP");
+       err := syscall.Tgkill(t.proc.pid, t.tid, syscall.SIGSTOP);
+       return os.NewSyscallError("tgkill", err);
+}
+
+// stopAsync sends SIGSTOP to all threads in state 'running'.
+//
+// Must be called from the monitor thread.
+func (p *process) stopAsync() os.Error {
+       for _, t := range p.threads {
+               if t.state == running {
+                       err := t.sendSigstop();
+                       if err != nil {
+                               return err;
+                       }
+                       t.setState(stopping);
+               }
+       }
+       return nil;
+}
+
+// doTrap handles SIGTRAP debug events with a cause of 0.  These can
+// be caused either by an installed breakpoint, a breakpoint in the
+// program text, or by single stepping.
+//
+// TODO(austin) I think we also get this on an execve syscall.
+func (ev *debugEvent) doTrap() (threadState, os.Error) {
+       t := ev.t;
+
+       if t.state == singleStepping {
+               return stopped, nil;
+       }
+
+       // Hit a breakpoint.  Linux leaves the program counter after
+       // the breakpoint.  If this is an installed breakpoint, we
+       // need to back the PC up to the breakpoint PC.
+       var regs syscall.PtraceRegs;
+       err := t.ptraceGetRegs(&regs);
+       if err != nil {
+               return stopped, err;
+       }
+
+       b, ok := t.proc.breakpoints[uintptr(regs.Rip)-uintptr(len(bpinst386))];
+       if !ok {
+               // We must have hit a breakpoint that was actually in
+               // the program.  Leave the IP where it is so we don't
+               // re-execute the breakpoint instruction.  Expose the
+               // fact that we stopped with a SIGTRAP.
+               return stoppedSignal, nil;
+       }
+
+       t.breakpoint = b;
+       t.logTrace("at breakpoint %v, backing up PC from %#x", b, regs.Rip);
+
+       regs.Rip = uint64(b.pc);
+       err = t.ptraceSetRegs(&regs);
+       if err != nil {
+               return stopped, err;
+       }
+       return stoppedBreakpoint, nil;
+}
+
+// doPtraceClone handles SIGTRAP debug events with a PTRACE_EVENT_CLONE
+// cause.  It initializes the new thread, adds it to the process, and
+// returns the appropriate thread state for the existing thread.
+func (ev *debugEvent) doPtraceClone() (threadState, os.Error) {
+       t := ev.t;
+
+       // Get the TID of the new thread
+       tid, err := t.ptraceGetEventMsg();
+       if err != nil {
+               return stopped, err;
+       }
+
+       nt, err := t.proc.newThread(int(tid), syscall.SIGSTOP, true);
+       if err != nil {
+               return stopped, err;
+       }
+
+       // Remember the thread
+       t.newThread = nt;
+
+       return stoppedThreadCreate, nil;
+}
+
+// doPtraceExit handles SIGTRAP debug events with a PTRACE_EVENT_EXIT
+// cause.  It sets up the thread's state, but does not remove it from
+// the process.  A later WIFEXITED debug event will remove it from the
+// process.
+func (ev *debugEvent) doPtraceExit() (threadState, os.Error) {
+       t := ev.t;
+
+       // Get exit status
+       exitStatus, err := t.ptraceGetEventMsg();
+       if err != nil {
+               return stopped, err;
+       }
+       ws := syscall.WaitStatus(exitStatus);
+       t.logTrace("exited with %v", ws);
+       switch {
+       case ws.Exited():
+               t.exitStatus = ws.ExitStatus();
+       case ws.Signaled():
+               t.signal = ws.Signal();
+       }
+
+       // We still need to continue this thread and wait on this
+       // thread's WIFEXITED event.  We'll delete it then.
+       return stoppedExiting, nil;
+}
+
+// process handles a debug event.  It modifies any thread or process
+// state as necessary, uninstalls breakpoints if necessary, and stops
+// any running threads.
+func (ev *debugEvent) process() os.Error {
+       if ev.err != nil {
+               return ev.err;
+       }
+
+       t := ev.t;
+       t.exitStatus = -1;
+       t.signal = -1;
+
+       // Decode wait status.
+       var state threadState;
+       switch {
+       case ev.Stopped():
+               state = stoppedSignal;
+               t.signal = ev.StopSignal();
+               t.logTrace("stopped with %v", ev);
+               if ev.StopSignal() == syscall.SIGTRAP {
+                       // What caused the debug trap?
+                       var err os.Error;
+                       switch cause := ev.TrapCause(); cause {
+                       case 0:
+                               // Breakpoint or single stepping
+                               state, err = ev.doTrap();
+
+                       case syscall.PTRACE_EVENT_CLONE:
+                               state, err = ev.doPtraceClone();
+
+                       case syscall.PTRACE_EVENT_EXIT:
+                               state, err = ev.doPtraceExit();
+
+                       default:
+                               t.warn("Unknown trap cause %d", cause);
+                       }
+
+                       if err != nil {
+                               t.setState(stopped);
+                               t.warn("failed to handle trap %v: %v", ev, err);
+                       }
+               }
+
+       case ev.Exited():
+               state = exited;
+               t.proc.threads[t.tid] = nil, false;
+               t.logTrace("exited %v", ev);
+               // We should have gotten the exit status in
+               // PTRACE_EVENT_EXIT, but just in case.
+               t.exitStatus = ev.ExitStatus();
+
+       case ev.Signaled():
+               state = exited;
+               t.proc.threads[t.tid] = nil, false;
+               t.logTrace("signaled %v", ev);
+               // Again, this should be redundant.
+               t.signal = ev.Signal();
+
+       default:
+               panic(fmt.Sprintf("Unexpected wait status %v", ev.Waitmsg));
+       }
+
+       // If we sent a SIGSTOP to the thread (indicated by state
+       // Stopping), we might have raced with a different type of
+       // stop.  If we didn't get the stop we expected, then the
+       // SIGSTOP we sent is now queued up, so we should ignore the
+       // next one we get.
+       if t.state == stopping && ev.StopSignal() != syscall.SIGSTOP {
+               t.ignoreNextSigstop = true;
+       }
+
+       // TODO(austin) If we're in state stopping and get a SIGSTOP,
+       // set state stopped instead of stoppedSignal.
+
+       t.setState(state);
+
+       if t.proc.someRunningThread() == nil {
+               // Nothing is running, uninstall breakpoints
+               return t.proc.uninstallBreakpoints();
+       }
+       // Stop any other running threads
+       return t.proc.stopAsync();
+}
+
+// onStop adds a handler for state transitions from running to
+// non-running states.  The handler will be called from the monitor
+// thread.
+//
+// Must be called from the monitor thread.
+func (t *thread) onStop(handle func (), onErr func (os.Error)) {
+       // TODO(austin) This is rather inefficient for things like
+       // stepping all threads during a continue.  Maybe move
+       // transitionHandlers to the thread, or have both per-thread
+       // and per-process transition handlers.
+       h := &transitionHandler{nil, onErr};
+       h.handle = func (st *thread, old, new threadState) {
+               if t == st && old.isRunning() && !new.isRunning() {
+                       handle();
+               } else {
+                       t.proc.transitionHandlers.Push(h);
+               }
+       };
+       t.proc.transitionHandlers.Push(h);
+}
+
+/*
+ * Event monitor
+ */
+
+// monitor handles debug events and debug requests for p, exiting when
+// there are no threads left in p.
+//
+// TODO(austin) When an unrecoverable error occurs, abort the monitor
+// and record this error so all future calls to do will return it
+// immediately.
+func (p *process) monitor() {
+       var err os.Error;
+
+       // Linux requires that all ptrace calls come from the thread
+       // that originally attached.  Prevent the Go scheduler from
+       // migrating us to other OS threads.
+       runtime.LockOSThread();
+       defer runtime.UnlockOSThread();
+
+       hadThreads := false;
+       for {
+               select {
+               case event := <-p.debugEvents:
+                       err = event.process();
+                       if err != nil {
+                               break;
+                       }
+
+               case req := <-p.debugReqs:
+                       req.res <- req.f();
+
+               case err = <-p.stopReq:
+                       break;
+               }
+
+               if len(p.threads) == 0 {
+                       if hadThreads {
+                               p.logTrace("no more threads; monitor exiting");
+                               // TODO(austin) Use a real error do
+                               // future operations will fail
+                               err = nil;
+                               break;
+                       }
+               } else {
+                       hadThreads = true;
+               }
+       }
+
+       // Abort waiting handlers
+       for _, h := range p.transitionHandlers.Data() {
+               h := h.(*transitionHandler);
+               h.onErr(err);
+       }
+
+       // TODO(austin) How do I stop the wait threads?
+       if err != nil {
+               panic(err.String());
+       }
+}
+
+// do executes f in the monitor thread (and, thus, atomically with
+// respect to thread state changes).  f must not block.
+//
+// Must NOT be called from the monitor thread.
+func (p *process) do(f func () os.Error) os.Error {
+       // TODO(austin) If monitor is stopped, return error.
+       req := &debugReq{f, make(chan os.Error)};
+       p.debugReqs <- req;
+       return <-req.res;
+}
+
+// stopMonitor stops the monitor with the given error.  If the monitor
+// is already stopped, does nothing.
+func (p *process) stopMonitor(err os.Error) {
+       doNotBlock := p.stopReq <- err;
+       // TODO(austin) Wait until monitor has exited?
+}
+
+/*
+ * Public thread interface
+ */
+
+func (t *thread) Regs() (Regs, os.Error) {
+       var regs syscall.PtraceRegs;
+
+       err := t.proc.do(func () os.Error {
+               if !t.state.isStopped() {
+                       return &badState{t, "cannot get registers", t.state};
+               }
+               return t.ptraceGetRegs(&regs);
+       });
+       if err != nil {
+               return nil, err;
+       }
+
+       setter := func (r *syscall.PtraceRegs) os.Error {
+               return t.proc.do(func () os.Error {
+                       if !t.state.isStopped() {
+                               return &badState{t, "cannot get registers", t.state};
+                       }
+                       return t.ptraceSetRegs(r);
+               });
+       };
+       return newRegs(&regs, setter), nil;
+}
+
+func (t *thread) Peek(addr Word, out []byte) (int, os.Error) {
+       var c int;
+
+       err := t.proc.do(func () os.Error {
+               if !t.state.isStopped() {
+                       return &badState{t, "cannot peek text", t.state};
+               }
+
+               var err os.Error;
+               c, err = t.ptracePeekText(uintptr(addr), out);
+               return err;
+       });
+
+       return c, err;
+}
+
+func (t *thread) Poke(addr Word, out []byte) (int, os.Error) {
+       var c int;
+
+       err := t.proc.do(func () os.Error {
+               if !t.state.isStopped() {
+                       return &badState{t, "cannot poke text", t.state};
+               }
+
+               var err os.Error;
+               c, err = t.ptracePokeText(uintptr(addr), out);
+               return err;
+       });
+
+       return c, err;
+}
+
+// stepAsync starts this thread single stepping.  When the single step
+// is complete, it will send nil on the given channel.  If an error
+// occurs while setting up the single step, it returns that error.  If
+// an error occurs while waiting for the single step to complete, it
+// sends that error on the channel.
+func (t *thread) stepAsync(ready chan os.Error) os.Error {
+       if err := t.ptraceStep(); err != nil {
+               return err;
+       }
+       t.setState(singleStepping);
+       t.onStop(func () {
+                       ready <- nil;
+               },
+               func (err os.Error) {
+                       ready <- err;
+               });
+       return nil;
+}
+
+func (t *thread) Step() os.Error {
+       t.logTrace("Step {");
+       defer t.logTrace("}");
+
+       ready := make(chan os.Error);
+
+       err := t.proc.do(func () os.Error {
+               if !t.state.isStopped() {
+                       return &badState{t, "cannot single step", t.state};
+               }
+               return t.stepAsync(ready);
+       });
+       if err != nil {
+               return err;
+       }
+
+       err = <-ready;
+       return err;
+}
+
+// TODO(austin) We should probably get this via C's strsignal.
+var sigNames = [...]string {
+       "SIGEXIT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
+       "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
+       "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM",
+       "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
+       "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
+       "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGPOLL",
+       "SIGPWR", "SIGSYS"
+}
+
+// sigName returns the symbolic name for the given signal number.  If
+// the signal number is invalid, returns "<invalid>".
+func sigName(signal int) string {
+       if signal < 0 || signal >= len(sigNames) {
+               return "<invalid>";
+       }
+       return sigNames[signal];
+}
+
+func (t *thread) Stopped() (Cause, os.Error) {
+       var c Cause;
+       err := t.proc.do(func() os.Error {
+               switch t.state {
+               case stopped:
+                       c = Stopped{};
+
+               case stoppedBreakpoint:
+                       c = Breakpoint(t.breakpoint.pc);
+
+               case stoppedSignal:
+                       c = Signal(sigName(t.signal));
+
+               case stoppedThreadCreate:
+                       c = &ThreadCreate{t.newThread};
+
+               case stoppedExiting, exiting, exited:
+                       if t.signal == -1 {
+                               c = &ThreadExit{t.exitStatus, ""};
+                       } else {
+                               c = &ThreadExit{t.exitStatus, sigName(t.signal)};
+                       }
+
+               default:
+                       return &badState{t, "cannot get stop cause", t.state};
+               }
+               return nil;
+       });
+       if err != nil {
+               return nil, err;
+       }
+
+       return c, nil;
+}
+
+func (p *process) Threads() []Thread {
+       var res []Thread;
+
+       p.do(func () os.Error {
+               res = make([]Thread, len(p.threads));
+               i := 0;
+               for _, t := range p.threads {
+                       // Exclude zombie threads.
+                       st := t.state;
+                       if st == exiting || st == exited || st == detached {
+                               continue;
+                       }
+
+                       res[i] = t;
+                       i++;
+               }
+               res = res[0:i];
+               return nil;
+       });
+       return res;
+}
+
+func (p *process) AddBreakpoint(pc Word) os.Error {
+       return p.do(func () os.Error {
+               if t := p.someRunningThread(); t != nil {
+                       return &badState{t, "cannot add breakpoint", t.state};
+               }
+               if _, ok := p.breakpoints[uintptr(pc)]; ok {
+                       return breakpointExistsError(pc);
+               }
+               p.breakpoints[uintptr(pc)] = &breakpoint{pc: uintptr(pc)};
+               return nil;
+       });
+}
+
+func (p *process) RemoveBreakpoint(pc Word) os.Error {
+       return p.do(func () os.Error {
+               if t := p.someRunningThread(); t != nil {
+                       return &badState{t, "cannot remove breakpoint", t.state};
+               }
+               if _, ok := p.breakpoints[uintptr(pc)]; !ok {
+                       return noBreakpointError(pc);
+               }
+               p.breakpoints[uintptr(pc)] = nil, false;
+               return nil;
+       });
+}
+
+func (p *process) Continue() os.Error {
+       // Single step any threads that are stopped at breakpoints so
+       // we can reinstall breakpoints.
+       var ready chan os.Error;
+       count := 0;
+
+       err := p.do(func () os.Error {
+               // We make the ready channel big enough to hold all
+               // ready message so we don't jam up the monitor if we
+               // stop listening (e.g., if there's an error).
+               ready = make(chan os.Error, len(p.threads));
+
+               for _, t := range p.threads {
+                       if !t.state.isStopped() {
+                               continue;
+                       }
+
+                       // We use the breakpoint map directly here
+                       // instead of checking the stop cause because
+                       // it could have been stopped at a breakpoint
+                       // for some other reason, or the breakpoint
+                       // could have been added since it was stopped.
+                       var regs syscall.PtraceRegs;
+                       err := t.ptraceGetRegs(&regs);
+                       if err != nil {
+                               return err;
+                       }
+                       if b, ok := p.breakpoints[uintptr(regs.Rip)]; ok {
+                               t.logTrace("stepping over breakpoint %v", b);
+                               if err := t.stepAsync(ready); err != nil {
+                                       return err;
+                               }
+                               count++;
+                       }
+               }
+               return nil;
+       });
+       if err != nil {
+               p.stopMonitor(err);
+               return err;
+       }
+
+       // Wait for single stepping threads
+       for count > 0 {
+               err = <-ready;
+               if err != nil {
+                       p.stopMonitor(err);
+                       return err;
+               }
+               count--;
+       }
+
+       // Continue all threads
+       err = p.do(func () os.Error {
+               if err := p.installBreakpoints(); err != nil {
+                       return err;
+               }
+
+               for _, t := range p.threads {
+                       var err os.Error;
+                       switch {
+                       case !t.state.isStopped():
+                               continue;
+
+                       case t.state == stoppedSignal && t.signal != syscall.SIGSTOP && t.signal != syscall.SIGTRAP:
+                               t.logTrace("continuing with signal %d", t.signal);
+                               err = t.ptraceContWithSignal(t.signal);
+
+                       default:
+                               t.logTrace("continuing");
+                               err = t.ptraceCont();
+                       }
+                       if err != nil {
+                               return err;
+                       }
+                       if t.state == stoppedExiting {
+                               t.setState(exiting);
+                       } else {
+                               t.setState(running);
+                       }
+               }
+               return nil;
+       });
+       if err != nil {
+               // TODO(austin) Do we need to stop the monitor with
+               // this error atomically with the do-routine above?
+               p.stopMonitor(err);
+               return err;
+       }
+
+       return nil;
+}
+
+func (p *process) WaitStop() os.Error {
+       // We need a non-blocking ready channel for the case where all
+       // threads are already stopped.
+       ready := make(chan os.Error, 1);
+
+       err := p.do(func () os.Error {
+               // Are all of the threads already stopped?
+               if p.someRunningThread() == nil {
+                       ready <- nil;
+                       return nil;
+               }
+
+               // Monitor state transitions
+               h := &transitionHandler{};
+               h.handle = func (st *thread, old, new threadState) {
+                       if !new.isRunning() {
+                               if p.someRunningThread() == nil {
+                                       ready <- nil;
+                                       return;
+                               }
+                       }
+                       p.transitionHandlers.Push(h);
+               };
+               h.onErr = func (err os.Error) {
+                       ready <- err;
+               };
+               p.transitionHandlers.Push(h);
+               return nil;
+       });
+       if err != nil {
+               return err;
+       }
+
+       return <-ready;
+}
+
+func (p *process) Stop() os.Error {
+       err := p.do(func () os.Error {
+               return p.stopAsync();
+       });
+       if err != nil {
+               return err;
+       }
+
+       return p.WaitStop();
+}
+
+func (p *process) Detach() os.Error {
+       if err := p.Stop(); err != nil {
+               return err;
+       }
+
+       err := p.do(func () os.Error {
+               if err := p.uninstallBreakpoints(); err != nil {
+                       return err;
+               }
+
+               for pid, t := range p.threads {
+                       if t.state.isStopped() {
+                               // We can't detach from zombies.
+                               if err := t.ptraceDetach(); err != nil {
+                                       return err;
+                               }
+                       }
+                       t.setState(detached);
+                       p.threads[pid] = nil, false;
+               }
+               return nil;
+       });
+       // TODO(austin) Wait for monitor thread to exit?
+       return err;
+}
+
+// newThread creates a new thread object and waits for its initial
+// signal.  If cloned is true, this thread was cloned from a thread we
+// are already attached to.
+//
+// Must be run from the monitor thread.
+func (p *process) newThread(tid int, signal int, cloned bool) (*thread, os.Error) {
+       t := &thread{tid: tid, proc: p, state: stopped};
+
+       // Get the signal from the thread
+       // TODO(austin) Thread might already be stopped if we're attaching.
+       w, err := os.Wait(tid, syscall.WALL);
+       if err != nil {
+               return nil, err;
+       }
+       if w.Pid != tid || w.StopSignal() != signal {
+               return nil, &newThreadError{w, tid, signal};
+       }
+
+       if !cloned {
+               err = t.ptraceSetOptions(syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEEXIT);
+               if err != nil {
+                       return nil, err;
+               }
+       }
+
+       p.threads[tid] = t;
+
+       return t, nil;
+}
+
+// attachThread attaches a running thread to the process.
+//
+// Must NOT be run from the monitor thread.
+func (p *process) attachThread(tid int) (*thread, os.Error) {
+       p.logTrace("attaching to thread %d", tid);
+       var thr *thread;
+       err := p.do(func () os.Error {
+               errno := syscall.PtraceAttach(tid);
+               if errno != 0 {
+                       return os.NewSyscallError("ptrace(ATTACH)", errno);
+               }
+
+               var err os.Error;
+               thr, err = p.newThread(tid, syscall.SIGSTOP, false);
+               return err;
+       });
+       return thr, err;
+}
+
+// attachAllThreads attaches to all threads in a process.
+func (p *process) attachAllThreads() os.Error {
+       taskPath := "/proc/" + strconv.Itoa(p.pid) + "/task";
+       taskDir, err := os.Open(taskPath, os.O_RDONLY, 0);
+       if err != nil {
+               return err;
+       }
+       defer taskDir.Close();
+
+       // We stop threads as we attach to them; however, because new
+       // threads can appear while we're looping over all of them, we
+       // have to repeatly scan until we know we're attached to all
+       // of them.
+       for again := true; again; {
+               again = false;
+
+               tids, err := taskDir.Readdirnames(-1);
+               if err != nil {
+                       return err;
+               }
+
+               for _, tidStr := range tids {
+                       tid, err := strconv.Atoi(tidStr);
+                       if err != nil {
+                               return err;
+                       }
+                       if _, ok := p.threads[tid]; ok {
+                               continue;
+                       }
+
+                       t, err := p.attachThread(tid);
+                       if err != nil {
+                               // There could have been a race, or
+                               // this process could be a zobmie.
+                               statFile, err2 := io.ReadFile(taskPath + "/" + tidStr + "/stat");
+                               if err2 != nil {
+                                       switch err2 := err2.(type) {
+                                       case *os.PathError:
+                                               if err2.Error == os.ENOENT {
+                                                       // Raced with thread exit
+                                                       p.logTrace("raced with thread %d exit", tid);
+                                                       continue;
+                                               }
+                                       }
+                                       // Return the original error
+                                       return err;
+                               }
+
+                               statParts := strings.Split(string(statFile), " ", 4);
+                               if len(statParts) > 2 && statParts[2] == "Z" {
+                                       // tid is a zombie
+                                       p.logTrace("thread %d is a zombie", tid);
+                                       continue;
+                               }
+
+                               // Return the original error
+                               return err;
+                       }
+                       again = true;
+               }
+       }
+
+       return nil;
+}
+
+// newProcess creates a new process object and starts its monitor thread.
+func newProcess(pid int) *process {
+       p := &process{
+               pid: pid,
+               threads: make(map[int] *thread),
+               breakpoints: make(map[uintptr] *breakpoint),
+               debugEvents: make(chan *debugEvent),
+               debugReqs: make(chan *debugReq),
+               stopReq: make(chan os.Error),
+               transitionHandlers: vector.New(0)
+       };
+
+       go p.monitor();
+
+       return p;
+}
+
+// Attach attaches to process pid and stops all of its threads.
+func Attach(pid int) (Process, os.Error) {
+       p := newProcess(pid);
+
+       // Attach to all threads
+       err := p.attachAllThreads();
+       if err != nil {
+               p.Detach();
+               // TODO(austin) Detach stopped the monitor already
+               //p.stopMonitor(err);
+               return nil, err;
+       }
+
+       return p, nil;
+}
+
+// ForkExec forks the current process and execs argv0, stopping the
+// new process after the exec syscall.  See os.ForkExec for additional
+// details.
+func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error)
+{
+       p := newProcess(-1);
+
+       // Create array of integer (system) fds.
+       intfd := make([]int, len(fd));
+       for i, f := range fd {
+               if f == nil {
+                       intfd[i] = -1;
+               } else {
+                       intfd[i] = f.Fd();
+               }
+       }
+
+       // Fork from the monitor thread so we get the right tracer pid.
+       err := p.do(func () os.Error {
+               pid, errno := syscall.PtraceForkExec(argv0, argv, envv, dir, intfd);
+               if errno != 0 {
+                       return &os.PathError{"fork/exec", argv0, os.Errno(errno)};
+               }
+               p.pid = pid;
+
+               // The process will raise SIGTRAP when it reaches execve.
+               t, err := p.newThread(pid, syscall.SIGTRAP, false);
+               return err;
+       });
+       if err != nil {
+               p.stopMonitor(err);
+               return nil, err;
+       }
+
+       return p, nil;
+}
diff --git a/src/pkg/debug/proc/ptrace-nptl.txt b/src/pkg/debug/proc/ptrace-nptl.txt
new file mode 100644 (file)
index 0000000..62cbf77
--- /dev/null
@@ -0,0 +1,132 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+ptrace and NTPL, the missing manpage
+
+== Signals ==
+
+A signal sent to a ptrace'd process or thread causes only the thread
+that receives it to stop and report to the attached process.
+
+Use tgkill to target a signal (for example, SIGSTOP) at a particular
+thread.  If you use kill, the signal could be delivered to another
+thread in the same process.
+
+Note that SIGSTOP differs from its usual behavior when a process is
+being traced.  Usually, a SIGSTOP sent to any thread in a thread group
+will stop all threads in the thread group.  When a thread is traced,
+however, a SIGSTOP affects only the receiving thread (and any other
+threads in the thread group that are not traced).
+
+SIGKILL behaves like it does for non-traced processes.  It affects all
+threads in the process and terminates them without the WSTOPSIG event
+generated by other signals.  However, if PTRACE_O_TRACEEXIT is set,
+the attached process will still receive PTRACE_EVENT_EXIT events
+before receiving WIFSIGNALED events.
+
+See "Following thread death" for a caveat regarding signal delivery to
+zombie threads.
+
+== Waiting on threads ==
+
+Cloned threads in ptrace'd processes are treated similarly to cloned
+threads in your own process.  Thus, you must use the __WALL option in
+order to receive notifications from threads created by the child
+process.  Similarly, the __WCLONE option will wait only on
+notifications from threads created by the child process and *not* on
+notifications from the initial child thread.
+
+Even when waiting on a specific thread's PID using waitpid or similar,
+__WALL or __WCLONE is necessary or waitpid will return ECHILD.
+
+== Attaching to existing threads ==
+
+libthread_db (which gdb uses), attaches to existing threads by pulling
+the pthread data structures out of the traced process.  The much
+easier way is to traverse the /proc/PID/task directory, though it's
+unclear how the semantics of these two approaches differ.
+
+Unfortunately, if the main thread has exited (but the overall process
+has not), it sticks around as a zombie process.  This zombie will
+appear in the /proc/PID/task directory, but trying to attach to it
+will yield EPERM.  In this case, the third field of the
+/proc/PID/task/PID/stat file will be "Z".  Attempting to open the stat
+file is also a convenient way to detect races between listing the task
+directory and the thread exiting.  Coincidentally, gdb will simply
+fail to attach to a process whose main thread is a zombie.
+
+Because new threads may be created while the debugger is in the
+process of attaching to existing threads, the debugger must repeatedly
+re-list the task directory until it has attached to (and thus stopped)
+every thread listed.
+
+In order to follow new threads created by existing threads,
+PTRACE_O_TRACECLONE must be set on each thread attached to.
+
+== Following new threads ==
+
+With the child process stopped, use PTRACE_SETOPTIONS to set the
+PTRACE_O_TRACECLONE option.  This option is per-thread, and thus must
+be set on each existing thread individually.  When an existing thread
+with PTRACE_O_TRACECLONE set spawns a new thread, the existing thread
+will stop with (SIGTRAP | PTRACE_EVENT_CLONE << 8) and the PID of the
+new thread can be retrieved with PTRACE_GETEVENTMSG on the creating
+thread.  At this time, the new thread will exist, but will initially
+be stopped with a SIGSTOP.  The new thread will automatically be
+traced and will inherit the PTRACE_O_TRACECLONE option from its
+parent.  The attached process should wait on the new thread to receive
+the SIGSTOP notification.
+
+When using waitpid(-1, ...), don't rely on the parent thread reporting
+a SIGTRAP before receiving the SIGSTOP from the new child thread.
+
+Without PTRACE_O_TRACECLONE, newly cloned threads will not be
+ptrace'd.  As a result, signals received by new threads will be
+handled in the usual way, which may affect the parent and in turn
+appear to the attached process, but attributed to the parent (possibly
+in unexpected ways).
+
+== Following thread death ==
+
+If any thread with the PTRACE_O_TRACEEXIT option set exits (either by
+returning or pthread_exit'ing), the tracing process will receive an
+immediate PTRACE_EVENT_EXIT.  At this point, the thread will still
+exist.  The exit status, encoded as for wait, can be queried using
+PTRACE_GETEVENTMSG on the exiting thread's PID.  The thread should be
+continued so it can actually exit, after which its wait behavior is
+the same as for a thread without the PTRACE_O_TRACEEXIT option.
+
+If a non-main thread exits (either by returning or pthread_exit'ing),
+its corresponding process will also exit, producing a WIFEXITED event
+(after the process is continued from a possible PTRACE_EVENT_EXIT
+event).  It is *not* necessary for another thread to ptrace_join for
+this to happen.
+
+If the main thread exits by returning, then all threads will exit,
+first generating a PTRACE_EVENT_EXIT event for each thread if
+appropriate, then producing a WIFEXITED event for each thread.
+
+If the main thread exits using pthread_exit, then it enters a
+non-waitable zombie state.  It will still produce an immediate
+PTRACE_O_TRACEEXIT event, but the WIFEXITED event will be delayed
+until the entire process exits.  This state exists so that shells
+don't think the process is done until all of the threads have exited.
+Unfortunately, signals cannot be delivered to non-waitable zombies.
+Most notably, SIGSTOP cannot be delivered; as a result, when you
+broadcast SIGSTOP to all of the threads, you must not wait for
+non-waitable zombies to stop.  Furthermore, any ptrace command on a
+non-waitable zombie, including PTRACE_DETACH, will return ESRCH.
+
+== Multi-threaded debuggers ==
+
+If the debugger itself is multi-threaded, ptrace calls must come from
+the same thread that originally attached to the remote thread.  The
+kernel simply compares the PID of the caller of ptrace against the
+tracer PID of the process passed to ptrace.  Because each debugger
+thread has a different PID, calling ptrace from a different thread
+might as well be calling it from a different process and the kernel
+will return ESRCH.
+
+wait, on the other hand, does not have this restriction.  Any debugger
+thread can wait on any thread in the attached process.
diff --git a/src/pkg/debug/proc/regs_darwin_386.go b/src/pkg/debug/proc/regs_darwin_386.go
new file mode 100644 (file)
index 0000000..47a7fa8
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import "syscall"
+
+func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs {
+       panic("newRegs unimplemented on darwin/386");
+}
diff --git a/src/pkg/debug/proc/regs_darwin_amd64.go b/src/pkg/debug/proc/regs_darwin_amd64.go
new file mode 100644 (file)
index 0000000..b0d5b52
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import "syscall"
+
+func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs {
+       panic("newRegs unimplemented on darwin/amd64");
+}
diff --git a/src/pkg/debug/proc/regs_linux_386.go b/src/pkg/debug/proc/regs_linux_386.go
new file mode 100644 (file)
index 0000000..89de83d
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import "syscall"
+
+func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs {
+       panic("newRegs unimplemented on linux/386");
+}
diff --git a/src/pkg/debug/proc/regs_linux_amd64.go b/src/pkg/debug/proc/regs_linux_amd64.go
new file mode 100644 (file)
index 0000000..ef2a005
--- /dev/null
@@ -0,0 +1,149 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package proc
+
+import (
+       "os";
+       "strconv";
+       "syscall";
+)
+
+type amd64Regs struct {
+       syscall.PtraceRegs;
+       setter func (*syscall.PtraceRegs) os.Error;
+}
+
+var names = [...]string {
+       "rax",
+       "rbx",
+       "rcx",
+       "rdx",
+       "rsi",
+       "rdi",
+       "rbp",
+       "rsp",
+       "r8",
+       "r9",
+       "r10",
+       "r11",
+       "r12",
+       "r13",
+       "r14",
+       "r15",
+       "rip",
+       "eflags",
+       "cs",
+       "ss",
+       "ds",
+       "es",
+       "fs",
+       "gs",
+
+       // PtraceRegs contains these registers, but I don't think
+       // they're actually meaningful.
+       //"orig_rax",
+       //"fs_base",
+       //"gs_base",
+}
+
+func (r *amd64Regs) PC() Word {
+       return Word(r.Rip);
+}
+
+func (r *amd64Regs) SetPC(val Word) os.Error {
+       r.Rip = uint64(val);
+       return r.setter(&r.PtraceRegs);
+}
+
+func (r *amd64Regs) Link() Word {
+       // TODO(austin)
+       panic("No link register");
+}
+
+func (r *amd64Regs) SetLink(val Word) os.Error {
+       panic("No link register");
+}
+
+func (r *amd64Regs) SP() Word {
+       return Word(r.Rsp);
+}
+
+func (r *amd64Regs) SetSP(val Word) os.Error {
+       r.Rsp = uint64(val);
+       return r.setter(&r.PtraceRegs);
+}
+
+func (r *amd64Regs) Names() []string {
+       return &names;
+}
+
+func (r *amd64Regs) Get(i int) Word {
+       switch i {
+       case 0: return Word(r.Rax);
+       case 1: return Word(r.Rbx);
+       case 2: return Word(r.Rcx);
+       case 3: return Word(r.Rdx);
+       case 4: return Word(r.Rsi);
+       case 5: return Word(r.Rdi);
+       case 6: return Word(r.Rbp);
+       case 7: return Word(r.Rsp);
+       case 8: return Word(r.R8);
+       case 9: return Word(r.R9);
+       case 10: return Word(r.R10);
+       case 11: return Word(r.R11);
+       case 12: return Word(r.R12);
+       case 13: return Word(r.R13);
+       case 14: return Word(r.R14);
+       case 15: return Word(r.R15);
+       case 16: return Word(r.Rip);
+       case 17: return Word(r.Eflags);
+       case 18: return Word(r.Cs);
+       case 19: return Word(r.Ss);
+       case 20: return Word(r.Ds);
+       case 21: return Word(r.Es);
+       case 22: return Word(r.Fs);
+       case 23: return Word(r.Gs);
+       }
+       panic("invalid register index ", strconv.Itoa(i));
+}
+
+func (r *amd64Regs) Set(i int, val Word) os.Error {
+       switch i {
+       case 0: r.Rax = uint64(val);
+       case 1: r.Rbx = uint64(val);
+       case 2: r.Rcx = uint64(val);
+       case 3: r.Rdx = uint64(val);
+       case 4: r.Rsi = uint64(val);
+       case 5: r.Rdi = uint64(val);
+       case 6: r.Rbp = uint64(val);
+       case 7: r.Rsp = uint64(val);
+       case 8: r.R8 = uint64(val);
+       case 9: r.R9 = uint64(val);
+       case 10: r.R10 = uint64(val);
+       case 11: r.R11 = uint64(val);
+       case 12: r.R12 = uint64(val);
+       case 13: r.R13 = uint64(val);
+       case 14: r.R14 = uint64(val);
+       case 15: r.R15 = uint64(val);
+       case 16: r.Rip = uint64(val);
+       case 17: r.Eflags = uint64(val);
+       case 18: r.Cs = uint64(val);
+       case 19: r.Ss = uint64(val);
+       case 20: r.Ds = uint64(val);
+       case 21: r.Es = uint64(val);
+       case 22: r.Fs = uint64(val);
+       case 23: r.Gs = uint64(val);
+       default:
+               panic("invalid register index ", strconv.Itoa(i));
+       }
+       return r.setter(&r.PtraceRegs);
+}
+
+func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs {
+       res := amd64Regs{};
+       res.PtraceRegs = *regs;
+       res.setter = setter;
+       return &res;
+}