From: Austin Clements Date: Tue, 14 Jul 2009 22:12:10 +0000 (-0700) Subject: Implementation of process tracing using Linux's ptrace. X-Git-Tag: weekly.2009-11-06~1147 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c105de748d6ee49d69fe087e63953fb15ec467ef;p=gostls13.git Implementation of process tracing using Linux's ptrace. R=rsc APPROVED=rsc DELTA=1543 (1528 added, 0 deleted, 15 changed) OCL=31570 CL=31630 --- diff --git a/usr/austin/ptrace/process.go b/usr/austin/ptrace/process.go index cabb30884d..a26c6b0fe5 100644 --- a/usr/austin/ptrace/process.go +++ b/usr/austin/ptrace/process.go @@ -23,16 +23,32 @@ type Cause interface { // 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; @@ -42,7 +58,7 @@ type Regs interface { Get(i int) Word; // Set sets the value of a register. - Set(i int, val Word); + Set(i int, val Word) os.Error; } // Thread is a thread in the process being traced. @@ -86,7 +102,7 @@ type Thread interface { // process's state extends to all of its threads. type Process interface { // Threads returns an array of all threads in this process. - Threads() []*Thread; + Threads() []Thread; // AddBreakpoint creates a new breakpoint at program counter // pc. Breakpoints can only be created when the process is @@ -105,7 +121,8 @@ type Process interface { // 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 will receive the pending signal. + // 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 @@ -118,14 +135,14 @@ type Process interface { Detach() os.Error; } -// Paused is a stop cause used for threads that are stopped either by +// 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 Paused struct {} +type Stopped struct {} -func (c Paused) String() string { - return "paused"; +func (c Stopped) String() string { + return "stopped"; } // Breakpoint is a stop cause resulting from a thread reaching a set @@ -176,7 +193,7 @@ func (c ThreadCreate) String() string { // accessible. type ThreadExit struct { exitStatus int; - signal int; + signal string; } // Exited returns true if the thread exited normally. @@ -192,12 +209,12 @@ func (c ThreadExit) ExitStatus() int { // Signaled returns true if the thread was terminated by a signal. func (c ThreadExit) Signaled() bool { - return c.signal != -1; + return c.exitStatus == -1; } -// StopSignal returns the signal that terminated the thread, or -1 if +// StopSignal returns the signal that terminated the thread, or "" if // it was not terminated by a signal. -func (c ThreadExit) StopSignal() int { +func (c ThreadExit) StopSignal() string { return c.signal; } @@ -207,7 +224,7 @@ func (c ThreadExit) String() string { case c.Exited(): res += "with status " + strconv.Itoa(c.ExitStatus()); case c.Signaled(): - res += "from signal " + strconv.Itoa(c.StopSignal()); + res += "from signal " + c.StopSignal(); default: res += "from unknown cause"; } diff --git a/usr/austin/ptrace/ptrace-nptl.txt b/usr/austin/ptrace/ptrace-nptl.txt new file mode 100644 index 0000000000..c526404737 --- /dev/null +++ b/usr/austin/ptrace/ptrace-nptl.txt @@ -0,0 +1,131 @@ +// 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. + +== 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/usr/austin/ptrace/ptrace_linux.go b/usr/austin/ptrace/ptrace_linux.go new file mode 100644 index 0000000000..e241c5bc81 --- /dev/null +++ b/usr/austin/ptrace/ptrace_linux.go @@ -0,0 +1,1242 @@ +// 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 + +import ( + "container/vector"; + "fmt"; + "io"; + "os"; + "ptrace"; + "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 = true; + traceIP = false; +) + +/* + * Thread state + */ + +// Each thread can be in one of the following set of states. +// Each state satisfies (isRunning() || isStopped() || isTerminal()). +type threadState string; + +const ( + running threadState = "Running"; + singleStepping = "SingleStepping"; // Transient + stopping = "Stopping"; // Transient + stopped = "Stopped"; + stoppedBreakpoint = "StoppedBreakpoint"; + stoppedSignal = "StoppedSignal"; + stoppedThreadCreate = "StoppedThreadCreate"; + stoppedExiting = "StoppedExiting"; + exiting = "Exiting"; // Transient (except main thread) + exited = "Exited"; + detached = "Detached"; +) + +func (ts threadState) isRunning() bool { + return ts == running || ts == singleStepping || ts == stopping || ts == exiting; +} + +func (ts threadState) isStopped() bool { + return ts == stopped || ts == stoppedBreakpoint || ts == stoppedSignal || ts == stoppedThreadCreate || ts == stoppedExiting; +} + +func (ts threadState) isTerminal() bool { + return ts == exited || ts == detached; +} + +func (ts threadState) String() string { + return string(ts); +} + +/* + * Basic types + */ + +type thread struct + +// 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 ""; + } + 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; +} + +func (p *process) newThread(tid int) (*thread, os.Error) + +/* + * 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); +} + +/* + * Ptrace wrappers + */ + +func (t *thread) ptracePeekText(addr uintptr, out []byte) (int, os.Error) { + c, err := syscall.PtracePeekText(t.tid, addr, out); + 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); + 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() { + // 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. +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)); + 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; + } + + 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 "". +func sigName(signal int) string { + if signal < 0 || signal >= len(sigNames) { + return ""; + } + 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; + } + 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 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 +// SIGSTOP. +// +// Must be run from the monitor thread. +func (p *process) newThread(tid int) (*thread, os.Error) { + t := &thread{tid: tid, proc: p, state: stopped}; + + // Get the SIGSTOP from the thread + // TODO(austin) Thread might already be stopped + w, err := os.Wait(tid, syscall.WALL); + if err != nil { + return nil, err; + } + if w.Pid != tid || w.StopSignal() != syscall.SIGSTOP { + return nil, os.EINVAL; + } + + 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); + 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; +} + +// Attach attaches to process pid and stops all of its threads. +func Attach(pid int) (Process, os.Error) { + 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) + }; + + // All ptrace calls must be done from the same thread. Start + // the monitor thread now so we can attach from within it. + go p.monitor(); + + // 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; + } + + // Set ptrace options for all threads + err = p.do(func () os.Error { + for _, t := range p.threads { + err := t.ptraceSetOptions(syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEEXIT); + if err != nil { + return err; + } + } + return nil; + }); + if err != nil { + p.Detach(); + // TODO(austin) + //p.stopMonitor(err); + return nil, err; + } + + return p, nil; +} diff --git a/usr/austin/ptrace/regs_linux_amd64.go b/usr/austin/ptrace/regs_linux_amd64.go new file mode 100644 index 0000000000..c843bb134f --- /dev/null +++ b/usr/austin/ptrace/regs_linux_amd64.go @@ -0,0 +1,150 @@ +// 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 + +import ( + "os"; + "ptrace"; + "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; +}