Plan 9 from Bell Labs’s /usr/web/sources/contrib/ericvh/go-plan9/src/pkg/debug/proc/proc_linux.go

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


// 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 ...) {
	if !trace {
		return
	}
	logLock.Lock();
	defer logLock.Unlock();
	fmt.Fprintf(os.Stderr, "Thread %d", t.tid);
	if traceIP {
		var regs syscall.PtraceRegs;
		err := t.ptraceGetRegs(&regs);
		if err == nil {
			fmt.Fprintf(os.Stderr, "@%x", regs.PC())
		}
	}
	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 {
	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("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 = new(vector.Vector);
	for _, h := range handlers.Data() {
		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(&regs);
	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(&regs);
	if err != nil {
		return stopped, err
	}
	return stoppedBreakpoint, nil;
}

// doPtraceClone handles SIGTRAP debug events with a PTRACE_EVENT_CLONE
// cause.  It initializes the new thread, adds it to the process, and
// returns the appropriate thread state for the existing thread.
func (ev *debugEvent) doPtraceClone() (threadState, os.Error) {
	t := ev.t;

	// Get the TID of the new thread
	tid, err := t.ptraceGetEventMsg();
	if err != nil {
		return stopped, err
	}

	nt, err := t.proc.newThread(int(tid), syscall.SIGSTOP, true);
	if err != nil {
		return stopped, err
	}

	// Remember the thread
	t.newThread = nt;

	return stoppedThreadCreate, nil;
}

// doPtraceExit handles SIGTRAP debug events with a PTRACE_EVENT_EXIT
// cause.  It sets up the thread's state, but does not remove it from
// the process.  A later WIFEXITED debug event will remove it from the
// process.
func (ev *debugEvent) doPtraceExit() (threadState, os.Error) {
	t := ev.t;

	// Get exit status
	exitStatus, err := t.ptraceGetEventMsg();
	if err != nil {
		return stopped, err
	}
	ws := syscall.WaitStatus(exitStatus);
	t.logTrace("exited with %v", ws);
	switch {
	case ws.Exited():
		t.exitStatus = ws.ExitStatus()
	case ws.Signaled():
		t.signal = ws.Signal()
	}

	// We still need to continue this thread and wait on this
	// thread's WIFEXITED event.  We'll delete it then.
	return stoppedExiting, nil;
}

// process handles a debug event.  It modifies any thread or process
// state as necessary, uninstalls breakpoints if necessary, and stops
// any running threads.
func (ev *debugEvent) process() os.Error {
	if ev.err != nil {
		return ev.err
	}

	t := ev.t;
	t.exitStatus = -1;
	t.signal = -1;

	// Decode wait status.
	var state threadState;
	switch {
	case ev.Stopped():
		state = stoppedSignal;
		t.signal = ev.StopSignal();
		t.logTrace("stopped with %v", ev);
		if ev.StopSignal() == syscall.SIGTRAP {
			// What caused the debug trap?
			var err os.Error;
			switch cause := ev.TrapCause(); cause {
			case 0:
				// Breakpoint or single stepping
				state, err = ev.doTrap()

			case syscall.PTRACE_EVENT_CLONE:
				state, err = ev.doPtraceClone()

			case syscall.PTRACE_EVENT_EXIT:
				state, err = ev.doPtraceExit()

			default:
				t.warn("Unknown trap cause %d", cause)
			}

			if err != nil {
				t.setState(stopped);
				t.warn("failed to handle trap %v: %v", ev, err);
			}
		}

	case ev.Exited():
		state = exited;
		t.proc.threads[t.tid] = nil, false;
		t.logTrace("exited %v", ev);
		// We should have gotten the exit status in
		// PTRACE_EVENT_EXIT, but just in case.
		t.exitStatus = ev.ExitStatus();

	case ev.Signaled():
		state = exited;
		t.proc.threads[t.tid] = nil, false;
		t.logTrace("signaled %v", ev);
		// Again, this should be redundant.
		t.signal = ev.Signal();

	default:
		panic(fmt.Sprintf("Unexpected wait status %v", ev.Waitmsg))
	}

	// If we sent a SIGSTOP to the thread (indicated by state
	// Stopping), we might have raced with a different type of
	// stop.  If we didn't get the stop we expected, then the
	// SIGSTOP we sent is now queued up, so we should ignore the
	// next one we get.
	if t.state == stopping && ev.StopSignal() != syscall.SIGSTOP {
		t.ignoreNextSigstop = true
	}

	// TODO(austin) If we're in state stopping and get a SIGSTOP,
	// set state stopped instead of stoppedSignal.

	t.setState(state);

	if t.proc.someRunningThread() == nil {
		// Nothing is running, uninstall breakpoints
		return t.proc.uninstallBreakpoints()
	}
	// Stop any other running threads
	return t.proc.stopAsync();
}

// onStop adds a handler for state transitions from running to
// non-running states.  The handler will be called from the monitor
// thread.
//
// Must be called from the monitor thread.
func (t *thread) onStop(handle func(), onErr func(os.Error)) {
	// TODO(austin) This is rather inefficient for things like
	// stepping all threads during a continue.  Maybe move
	// transitionHandlers to the thread, or have both per-thread
	// and per-process transition handlers.
	h := &transitionHandler{nil, onErr};
	h.handle = func(st *thread, old, new threadState) {
		if t == st && old.isRunning() && !new.isRunning() {
			handle()
		} else {
			t.proc.transitionHandlers.Push(h)
		}
	};
	t.proc.transitionHandlers.Push(h);
}

/*
 * Event monitor
 */

// monitor handles debug events and debug requests for p, exiting when
// there are no threads left in p.
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.Data() {
		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(&regs);
	});
	if err != nil {
		return nil, err
	}

	setter := func(r *syscall.PtraceRegs) os.Error {
		return t.proc.do(func() os.Error {
			if !t.state.isStopped() {
				return &badState{t, "cannot get registers", t.state}
			}
			return t.ptraceSetRegs(r);
		})
	};
	return newRegs(&regs, setter), nil;
}

func (t *thread) Peek(addr Word, out []byte) (int, os.Error) {
	var c int;

	err := t.proc.do(func() os.Error {
		if !t.state.isStopped() {
			return &badState{t, "cannot peek text", t.state}
		}

		var err os.Error;
		c, err = t.ptracePeekText(uintptr(addr), out);
		return err;
	});

	return c, err;
}

func (t *thread) Poke(addr Word, out []byte) (int, os.Error) {
	var c int;

	err := t.proc.do(func() os.Error {
		if !t.state.isStopped() {
			return &badState{t, "cannot poke text", t.state}
		}

		var err os.Error;
		c, err = t.ptracePokeText(uintptr(addr), out);
		return err;
	});

	return c, err;
}

// stepAsync starts this thread single stepping.  When the single step
// is complete, it will send nil on the given channel.  If an error
// occurs while setting up the single step, it returns that error.  If
// an error occurs while waiting for the single step to complete, it
// sends that error on the channel.
func (t *thread) stepAsync(ready chan os.Error) os.Error {
	if err := t.ptraceStep(); err != nil {
		return err
	}
	t.setState(singleStepping);
	t.onStop(func() { ready <- nil },
		func(err os.Error) { ready <- err });
	return nil;
}

func (t *thread) Step() os.Error {
	t.logTrace("Step {");
	defer t.logTrace("}");

	ready := make(chan os.Error);

	err := t.proc.do(func() os.Error {
		if !t.state.isStopped() {
			return &badState{t, "cannot single step", t.state}
		}
		return t.stepAsync(ready);
	});
	if err != nil {
		return err
	}

	err = <-ready;
	return err;
}

// TODO(austin) We should probably get this via C's strsignal.
var sigNames = [...]string{
	"SIGEXIT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL",
	"SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL",
	"SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM",
	"SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP",
	"SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU",
	"SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGPOLL",
	"SIGPWR", "SIGSYS",
}

// sigName returns the symbolic name for the given signal number.  If
// the signal number is invalid, returns "<invalid>".
func sigName(signal int) string {
	if signal < 0 || signal >= len(sigNames) {
		return "<invalid>"
	}
	return sigNames[signal];
}

func (t *thread) Stopped() (Cause, os.Error) {
	var c Cause;
	err := t.proc.do(func() os.Error {
		switch t.state {
		case stopped:
			c = Stopped{}

		case stoppedBreakpoint:
			c = Breakpoint(t.breakpoint.pc)

		case stoppedSignal:
			c = Signal(sigName(t.signal))

		case stoppedThreadCreate:
			c = &ThreadCreate{t.newThread}

		case stoppedExiting, exiting, exited:
			if t.signal == -1 {
				c = &ThreadExit{t.exitStatus, ""}
			} else {
				c = &ThreadExit{t.exitStatus, sigName(t.signal)}
			}

		default:
			return &badState{t, "cannot get stop cause", t.state}
		}
		return nil;
	});
	if err != nil {
		return nil, err
	}

	return c, nil;
}

func (p *process) Threads() []Thread {
	var res []Thread;

	p.do(func() os.Error {
		res = make([]Thread, len(p.threads));
		i := 0;
		for _, t := range p.threads {
			// Exclude zombie threads.
			st := t.state;
			if st == exiting || st == exited || st == detached {
				continue
			}

			res[i] = t;
			i++;
		}
		res = res[0:i];
		return nil;
	});
	return res;
}

func (p *process) AddBreakpoint(pc Word) os.Error {
	return p.do(func() os.Error {
		if t := p.someRunningThread(); t != nil {
			return &badState{t, "cannot add breakpoint", t.state}
		}
		if _, ok := p.breakpoints[uintptr(pc)]; ok {
			return breakpointExistsError(pc)
		}
		p.breakpoints[uintptr(pc)] = &breakpoint{pc: uintptr(pc)};
		return nil;
	})
}

func (p *process) RemoveBreakpoint(pc Word) os.Error {
	return p.do(func() os.Error {
		if t := p.someRunningThread(); t != nil {
			return &badState{t, "cannot remove breakpoint", t.state}
		}
		if _, ok := p.breakpoints[uintptr(pc)]; !ok {
			return noBreakpointError(pc)
		}
		p.breakpoints[uintptr(pc)] = nil, false;
		return nil;
	})
}

func (p *process) Continue() os.Error {
	// Single step any threads that are stopped at breakpoints so
	// we can reinstall breakpoints.
	var ready chan os.Error;
	count := 0;

	err := p.do(func() os.Error {
		// We make the ready channel big enough to hold all
		// ready message so we don't jam up the monitor if we
		// stop listening (e.g., if there's an error).
		ready = make(chan os.Error, len(p.threads));

		for _, t := range p.threads {
			if !t.state.isStopped() {
				continue
			}

			// We use the breakpoint map directly here
			// instead of checking the stop cause because
			// it could have been stopped at a breakpoint
			// for some other reason, or the breakpoint
			// could have been added since it was stopped.
			var regs syscall.PtraceRegs;
			err := t.ptraceGetRegs(&regs);
			if err != nil {
				return err
			}
			if b, ok := p.breakpoints[uintptr(regs.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, 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
			}

			_, 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),
		transitionHandlers: new(vector.Vector),
	};

	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.
		_, err := p.newThread(pid, syscall.SIGTRAP, false);
		return err;
	});
	if err != nil {
		p.stopMonitor(err);
		return nil, err;
	}

	return p, nil;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.