mirror of
https://github.com/golang/go
synced 2024-11-27 00:11:19 -07:00
8b7951495c
R=rsc APPROVED=rsc DELTA=81 (53 added, 3 deleted, 25 changed) OCL=31651 CL=31675
1293 lines
31 KiB
Go
1293 lines
31 KiB
Go
// 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 "<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;
|
|
}
|
|
|
|
func (p *process) newThread(tid int, signal int, cloned bool) (*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);
|
|
}
|
|
|
|
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);
|
|
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.
|
|
//
|
|
// 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;
|
|
}
|
|
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() {
|
|
// TODO(austin) This gets stuck on
|
|
// zombie threads.
|
|
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
|
|
// 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;
|
|
}
|