diff options
Diffstat (limited to 'libgo/go/debug/proc/proc_linux.go')
-rw-r--r-- | libgo/go/debug/proc/proc_linux.go | 1322 |
1 files changed, 0 insertions, 1322 deletions
diff --git a/libgo/go/debug/proc/proc_linux.go b/libgo/go/debug/proc/proc_linux.go deleted file mode 100644 index 17c8fa5..0000000 --- a/libgo/go/debug/proc/proc_linux.go +++ /dev/null @@ -1,1322 +0,0 @@ -// 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 - -// TODO(rsc): Imports here after to be in proc.go too in order -// for deps.bash to get the right answer. -import ( - "container/vector" - "fmt" - "io/ioutil" - "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. -// -// To send a message to the monitor thread, first receive from the -// ready channel. If the ready channel returns true, the monitor is -// still running and will accept a message. If the ready channel -// returns false, the monitor is not running (the ready channel has -// been closed), and the reason it is not running will be stored in err. -type process struct { - pid int - threads map[int]*thread - breakpoints map[uintptr]*breakpoint - ready chan bool - debugEvents chan *debugEvent - debugReqs chan *debugReq - stopReq chan os.Error - transitionHandlers vector.Vector - err os.Error -} - -// 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) -} - -type ProcessExited struct{} - -func (p ProcessExited) String() string { return "process exited" } - -/* - * 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 ...interface{}) { - 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.PC()) - } - } - fmt.Fprint(os.Stderr, ": ") - fmt.Fprintf(os.Stderr, format, args...) - fmt.Fprint(os.Stderr, "\n") -} - -func (t *thread) warn(format string, args ...interface{}) { - 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 ...interface{}) { - 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 { - if len(p.threads) == 0 { - return nil - } - 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 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(fmt.Sprint("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. - } - if !<-t.proc.ready { - // The monitor exited - break - } - 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(newState threadState) { - oldState := t.state - t.state = newState - t.logTrace("state %v -> %v", oldState, newState) - - if !oldState.isRunning() && (newState.isRunning() || newState.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 = nil - for _, h := range handlers { - h := h.(*transitionHandler) - h.handle(t, oldState, newState) - } -} - -// 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.PC())-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.PC()) - - regs.SetPC(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. -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 err == nil { - p.ready <- true - select { - case event := <-p.debugEvents: - err = event.process() - - case req := <-p.debugReqs: - req.res <- req.f() - - case err = <-p.stopReq: - break - } - - if len(p.threads) == 0 { - if err == nil && hadThreads { - p.logTrace("no more threads; monitor exiting") - err = ProcessExited{} - } - } else { - hadThreads = true - } - } - - // Abort waiting handlers - // TODO(austin) How do I stop the wait threads? - for _, h := range p.transitionHandlers { - h := h.(*transitionHandler) - h.onErr(err) - } - - // Indicate that the monitor cannot receive any more messages - p.err = err - close(p.ready) -} - -// 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 { - if !<-p.ready { - return p.err - } - 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) { - if err == nil { - panic("cannot stop the monitor with no error") - } - if <-p.ready { - p.stopReq <- err - } -} - -/* - * 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.PC())]; 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) - 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 - } - - _, err = p.attachThread(tid) - if err != nil { - // There could have been a race, or - // this process could be a zobmie. - statFile, err2 := ioutil.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), - ready: make(chan bool, 1), - debugEvents: make(chan *debugEvent), - debugReqs: make(chan *debugReq), - stopReq: make(chan os.Error), - } - - 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 -} - -// StartProcess forks the current process and execs argv0, stopping the -// new process after the exec syscall. See os.StartProcess for additional -// details. -func StartProcess(argv0 string, argv []string, attr *os.ProcAttr) (Process, os.Error) { - sysattr := &syscall.ProcAttr{ - Dir: attr.Dir, - Env: attr.Env, - Ptrace: true, - } - p := newProcess(-1) - - // Create array of integer (system) fds. - intfd := make([]int, len(attr.Files)) - for i, f := range attr.Files { - if f == nil { - intfd[i] = -1 - } else { - intfd[i] = f.Fd() - } - } - sysattr.Files = intfd - - // Fork from the monitor thread so we get the right tracer pid. - err := p.do(func() os.Error { - pid, _, errno := syscall.StartProcess(argv0, argv, sysattr) - if errno != 0 { - return &os.PathError{"fork/exec", argv0, os.Errno(errno)} - } - p.pid = pid - - // The process will raise SIGTRAP when it reaches execve. - _, err := p.newThread(pid, syscall.SIGTRAP, false) - return err - }) - if err != nil { - p.stopMonitor(err) - return nil, err - } - - return p, nil -} |