diff options
| author | Russ Cox <rsc@golang.org> | 2009-09-01 11:51:05 -0700 |
|---|---|---|
| committer | Russ Cox <rsc@golang.org> | 2009-09-01 11:51:05 -0700 |
| commit | 178758050d974bdf866d31882518921391f6bd76 (patch) | |
| tree | e0132f21a5b33a1ca0aa3b741adc7a98452243b9 /src/pkg/debug/proc/proc_linux.go | |
| parent | 590a332ef8085b8d06bce352c16fff2187050cfa (diff) | |
| download | golang-178758050d974bdf866d31882518921391f6bd76.tar.gz | |
import debug/proc from usr/austin/ptrace
R=austin
DELTA=1892 (1892 added, 0 deleted, 0 changed)
OCL=34183
CL=34197
Diffstat (limited to 'src/pkg/debug/proc/proc_linux.go')
| -rw-r--r-- | src/pkg/debug/proc/proc_linux.go | 1316 |
1 files changed, 1316 insertions, 0 deletions
diff --git a/src/pkg/debug/proc/proc_linux.go b/src/pkg/debug/proc/proc_linux.go new file mode 100644 index 000000000..88269100f --- /dev/null +++ b/src/pkg/debug/proc/proc_linux.go @@ -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(®s); + 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(®s); + 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(®s); + 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(®s); + }); + 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(®s, 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(®s); + 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; +} |
