diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libgo/go/debug/proc | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libgo/go/debug/proc')
-rw-r--r-- | libgo/go/debug/proc/proc.go | 222 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_darwin.go | 17 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_freebsd.go | 17 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_linux.go | 1316 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_rtems.go | 17 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_solaris.go | 17 | ||||
-rw-r--r-- | libgo/go/debug/proc/proc_windows.go | 17 | ||||
-rw-r--r-- | libgo/go/debug/proc/ptrace-nptl.txt | 132 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_darwin_386.go | 5 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_darwin_amd64.go | 5 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_freebsd_386.go | 5 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_freebsd_amd64.go | 5 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_linux_386.go | 143 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_linux_amd64.go | 191 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_linux_arm.go | 39 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_windows_386.go | 5 | ||||
-rw-r--r-- | libgo/go/debug/proc/regs_windows_amd64.go | 5 |
17 files changed, 2158 insertions, 0 deletions
diff --git a/libgo/go/debug/proc/proc.go b/libgo/go/debug/proc/proc.go new file mode 100644 index 000000000..d89649cf8 --- /dev/null +++ b/libgo/go/debug/proc/proc.go @@ -0,0 +1,222 @@ +// 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 provides a platform-independent interface for +// tracing and controlling running processes. It supports +// multi-threaded processes and provides typical low-level debugging +// controls such as breakpoints, single stepping, and manipulating +// memory and registers. +package proc + +// TODO(rsc): Have to import everything that proc_linux.go +// and proc_darwin.go do, because deps.bash only looks at +// this file. +import ( + _ "container/vector" + _ "fmt" + _ "io" + "os" + _ "runtime" + "strconv" + _ "strings" + _ "sync" + _ "syscall" +) + +type Word uint64 + +// A Cause explains why a thread is stopped. +type Cause interface { + String() string +} + +// 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 + + // Get returns the value of a register, where i corresponds to + // the index of the register's name in the array returned by + // Names. + Get(i int) Word + + // Set sets the value of a register. + Set(i int, val Word) os.Error +} + +// Thread is a thread in the process being traced. +type Thread interface { + // Step steps this thread by a single instruction. The thread + // must be stopped. If the thread is currently stopped on a + // breakpoint, this will step over the breakpoint. + // + // XXX What if it's stopped because of a signal? + Step() os.Error + + // Stopped returns the reason that this thread is stopped. It + // is an error is the thread not stopped. + Stopped() (Cause, os.Error) + + // Regs retrieves the current register values from this + // thread. The thread must be stopped. + Regs() (Regs, os.Error) + + // Peek reads len(out) bytes from the address addr in this + // thread into out. The thread must be stopped. It returns + // the number of bytes successfully read. If an error occurs, + // such as attempting to read unmapped memory, this count + // could be short and an error will be returned. If this does + // encounter unmapped memory, it will read up to the byte + // preceding the unmapped area. + Peek(addr Word, out []byte) (int, os.Error) + + // Poke writes b to the address addr in this thread. The + // thread must be stopped. It returns the number of bytes + // successfully written. If an error occurs, such as + // attempting to write to unmapped memory, this count could be + // short and an error will be returned. If this does + // encounter unmapped memory, it will write up to the byte + // preceding the unmapped area. + Poke(addr Word, b []byte) (int, os.Error) +} + +// Process is a process being traced. It consists of a set of +// threads. A process can be running, stopped, or terminated. The +// process's state extends to all of its threads. +type Process interface { + // Threads returns an array of all threads in this process. + Threads() []Thread + + // AddBreakpoint creates a new breakpoint at program counter + // pc. Breakpoints can only be created when the process is + // stopped. It is an error if a breakpoint already exists at + // pc. + AddBreakpoint(pc Word) os.Error + + // RemoveBreakpoint removes the breakpoint at the program + // counter pc. It is an error if no breakpoint exists at pc. + RemoveBreakpoint(pc Word) os.Error + + // Stop stops all running threads in this process before + // returning. + Stop() os.Error + + // 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 (other than SIGSTOP or SIGTRAP) will receive + // the pending signal. + Continue() os.Error + + // WaitStop waits until all threads in process p are stopped + // as a result of some thread hitting a breakpoint, receiving + // a signal, creating a new thread, or exiting. + WaitStop() os.Error + + // Detach detaches from this process. All stopped threads + // will be resumed. + Detach() os.Error +} + +// 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 Stopped struct{} + +func (c Stopped) String() string { return "stopped" } + +// Breakpoint is a stop cause resulting from a thread reaching a set +// breakpoint. +type Breakpoint Word + +// PC returns the program counter that the program is stopped at. +func (c Breakpoint) PC() Word { return Word(c) } + +func (c Breakpoint) String() string { + return "breakpoint at 0x" + strconv.Uitob64(uint64(c.PC()), 16) +} + +// Signal is a stop cause resulting from a thread receiving a signal. +// When the process is continued, the signal will be delivered. +type Signal string + +// Signal returns the signal being delivered to the thread. +func (c Signal) Name() string { return string(c) } + +func (c Signal) String() string { return c.Name() } + +// ThreadCreate is a stop cause returned from an existing thread when +// it creates a new thread. The new thread exists in a primordial +// form at this point and will begin executing in earnest when the +// process is continued. +type ThreadCreate struct { + thread Thread +} + +func (c *ThreadCreate) NewThread() Thread { return c.thread } + +func (c *ThreadCreate) String() string { return "thread create" } + +// ThreadExit is a stop cause resulting from a thread exiting. When +// this cause first arises, the thread will still be in the list of +// process threads and its registers and memory will still be +// accessible. +type ThreadExit struct { + exitStatus int + signal string +} + +// Exited returns true if the thread exited normally. +func (c *ThreadExit) Exited() bool { return c.exitStatus != -1 } + +// ExitStatus returns the exit status of the thread if it exited +// normally or -1 otherwise. +func (c *ThreadExit) ExitStatus() int { return c.exitStatus } + +// Signaled returns true if the thread was terminated by a signal. +func (c *ThreadExit) Signaled() bool { return c.exitStatus == -1 } + +// StopSignal returns the signal that terminated the thread, or "" if +// it was not terminated by a signal. +func (c *ThreadExit) StopSignal() string { return c.signal } + +func (c *ThreadExit) String() string { + res := "thread exited " + switch { + case c.Exited(): + res += "with status " + strconv.Itoa(c.ExitStatus()) + case c.Signaled(): + res += "from signal " + c.StopSignal() + default: + res += "from unknown cause" + } + return res +} diff --git a/libgo/go/debug/proc/proc_darwin.go b/libgo/go/debug/proc/proc_darwin.go new file mode 100644 index 000000000..7caf3a21a --- /dev/null +++ b/libgo/go/debug/proc/proc_darwin.go @@ -0,0 +1,17 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import "os" + +// Process tracing is not supported on OS X yet. + +func Attach(pid int) (Process, os.Error) { + return nil, os.NewError("debug/proc not implemented on OS X") +} + +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) { + return Attach(0) +} diff --git a/libgo/go/debug/proc/proc_freebsd.go b/libgo/go/debug/proc/proc_freebsd.go new file mode 100644 index 000000000..f6474ce80 --- /dev/null +++ b/libgo/go/debug/proc/proc_freebsd.go @@ -0,0 +1,17 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import "os" + +// Process tracing is not supported on FreeBSD yet. + +func Attach(pid int) (Process, os.Error) { + return nil, os.NewError("debug/proc not implemented on FreeBSD") +} + +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) { + return Attach(0) +} diff --git a/libgo/go/debug/proc/proc_linux.go b/libgo/go/debug/proc/proc_linux.go new file mode 100644 index 000000000..f0cc43a10 --- /dev/null +++ b/libgo/go/debug/proc/proc_linux.go @@ -0,0 +1,1316 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +// TODO(rsc): Imports here after to be in proc.go too in order +// for deps.bash to get the right answer. +import ( + "container/vector" + "fmt" + "io/ioutil" + "os" + "runtime" + "strconv" + "strings" + "sync" + "syscall" +) + +// This is an implementation of the process tracing interface using +// Linux's ptrace(2) interface. The implementation is multi-threaded. +// Each attached process has an associated monitor thread, and each +// running attached thread has an associated "wait" thread. The wait +// thread calls wait4 on the thread's TID and reports any wait events +// or errors via "debug events". The monitor thread consumes these +// wait events and updates the internally maintained state of each +// thread. All ptrace calls must run in the monitor thread, so the +// monitor executes closures received on the debugReq channel. +// +// As ptrace's documentation is somewhat light, this is heavily based +// on information gleaned from the implementation of ptrace found at +// http://lxr.linux.no/linux+v2.6.30/kernel/ptrace.c +// http://lxr.linux.no/linux+v2.6.30/arch/x86/kernel/ptrace.c#L854 +// as well as experimentation and examination of gdb's behavior. + +const ( + trace = false + traceIP = false + traceMem = false +) + +/* + * Thread state + */ + +// Each thread can be in one of the following set of states. +// Each state satisfies +// isRunning() || isStopped() || isZombie() || isTerminal(). +// +// Running threads can be sent signals and must be waited on, but they +// cannot be inspected using ptrace. +// +// Stopped threads can be inspected and continued, but cannot be +// meaningfully waited on. They can be sent signals, but the signals +// will be queued until they are running again. +// +// Zombie threads cannot be inspected, continued, or sent signals (and +// therefore they cannot be stopped), but they must be waited on. +// +// Terminal threads no longer exist in the OS and thus you can't do +// anything with them. +type threadState string + +const ( + running threadState = "Running" + singleStepping threadState = "SingleStepping" // Transient + stopping threadState = "Stopping" // Transient + stopped threadState = "Stopped" + stoppedBreakpoint threadState = "StoppedBreakpoint" + stoppedSignal threadState = "StoppedSignal" + stoppedThreadCreate threadState = "StoppedThreadCreate" + stoppedExiting threadState = "StoppedExiting" + exiting threadState = "Exiting" // Transient (except main thread) + exited threadState = "Exited" + detached threadState = "Detached" +) + +func (ts threadState) isRunning() bool { + return ts == running || ts == singleStepping || ts == stopping +} + +func (ts threadState) isStopped() bool { + return ts == stopped || ts == stoppedBreakpoint || ts == stoppedSignal || ts == stoppedThreadCreate || ts == stoppedExiting +} + +func (ts threadState) isZombie() bool { return ts == exiting } + +func (ts threadState) isTerminal() bool { return ts == exited || ts == detached } + +func (ts threadState) String() string { return string(ts) } + +/* + * Basic types + */ + +// A breakpoint stores information about a single breakpoint, +// including its program counter, the overwritten text if the +// breakpoint is installed. +type breakpoint struct { + pc uintptr + olddata []byte +} + +func (bp *breakpoint) String() string { + if bp == nil { + return "<nil>" + } + return fmt.Sprintf("%#x", bp.pc) +} + +// bpinst386 is the breakpoint instruction used on 386 and amd64. +var bpinst386 = []byte{0xcc} + +// A debugEvent represents a reason a thread stopped or a wait error. +type debugEvent struct { + *os.Waitmsg + t *thread + err os.Error +} + +// A debugReq is a request to execute a closure in the monitor thread. +type debugReq struct { + f func() os.Error + res chan os.Error +} + +// A transitionHandler specifies a function to be called when a thread +// changes state and a function to be called when an error occurs in +// the monitor. Both run in the monitor thread. Before the monitor +// invokes a handler, it removes the handler from the handler queue. +// The handler should re-add itself if needed. +type transitionHandler struct { + handle func(*thread, threadState, threadState) + onErr func(os.Error) +} + +// A process is a Linux process, which consists of a set of threads. +// Each running process has one monitor thread, which processes +// messages from the debugEvents, debugReqs, and stopReq channels and +// calls transition handlers. +// +// To send a message to the monitor thread, first receive from the +// ready channel. If the ready channel returns true, the monitor is +// still running and will accept a message. If the ready channel +// returns false, the monitor is not running (the ready channel has +// been closed), and the reason it is not running will be stored in err. +type process struct { + pid int + threads map[int]*thread + breakpoints map[uintptr]*breakpoint + ready chan bool + debugEvents chan *debugEvent + debugReqs chan *debugReq + stopReq chan os.Error + transitionHandlers vector.Vector + err os.Error +} + +// A thread represents a Linux thread in another process that is being +// debugged. Each running thread has an associated goroutine that +// waits for thread updates and sends them to the process monitor. +type thread struct { + tid int + proc *process + // Whether to ignore the next SIGSTOP received by wait. + ignoreNextSigstop bool + + // Thread state. Only modified via setState. + state threadState + // If state == StoppedBreakpoint + breakpoint *breakpoint + // If state == StoppedSignal or state == Exited + signal int + // If state == StoppedThreadCreate + newThread *thread + // If state == Exited + exitStatus int +} + +/* + * Errors + */ + +type badState struct { + thread *thread + message string + state threadState +} + +func (e *badState) String() string { + return fmt.Sprintf("Thread %d %s from state %v", e.thread.tid, e.message, e.state) +} + +type breakpointExistsError Word + +func (e breakpointExistsError) String() string { + return fmt.Sprintf("breakpoint already exists at PC %#x", e) +} + +type noBreakpointError Word + +func (e noBreakpointError) String() string { return fmt.Sprintf("no breakpoint at PC %#x", e) } + +type newThreadError struct { + *os.Waitmsg + wantPid int + wantSig int +} + +func (e *newThreadError) String() string { + return fmt.Sprintf("newThread wait wanted pid %v and signal %v, got %v and %v", e.Pid, e.StopSignal(), e.wantPid, e.wantSig) +} + +type ProcessExited struct{} + +func (p ProcessExited) String() string { return "process exited" } + +/* + * Ptrace wrappers + */ + +func (t *thread) ptracePeekText(addr uintptr, out []byte) (int, os.Error) { + c, err := syscall.PtracePeekText(t.tid, addr, out) + if traceMem { + fmt.Printf("peek(%#x) => %v, %v\n", addr, out, err) + } + return c, os.NewSyscallError("ptrace(PEEKTEXT)", err) +} + +func (t *thread) ptracePokeText(addr uintptr, out []byte) (int, os.Error) { + c, err := syscall.PtracePokeText(t.tid, addr, out) + if traceMem { + fmt.Printf("poke(%#x, %v) => %v\n", addr, out, err) + } + return c, os.NewSyscallError("ptrace(POKETEXT)", err) +} + +func (t *thread) ptraceGetRegs(regs *syscall.PtraceRegs) os.Error { + err := syscall.PtraceGetRegs(t.tid, regs) + return os.NewSyscallError("ptrace(GETREGS)", err) +} + +func (t *thread) ptraceSetRegs(regs *syscall.PtraceRegs) os.Error { + err := syscall.PtraceSetRegs(t.tid, regs) + return os.NewSyscallError("ptrace(SETREGS)", err) +} + +func (t *thread) ptraceSetOptions(options int) os.Error { + err := syscall.PtraceSetOptions(t.tid, options) + return os.NewSyscallError("ptrace(SETOPTIONS)", err) +} + +func (t *thread) ptraceGetEventMsg() (uint, os.Error) { + msg, err := syscall.PtraceGetEventMsg(t.tid) + return msg, os.NewSyscallError("ptrace(GETEVENTMSG)", err) +} + +func (t *thread) ptraceCont() os.Error { + err := syscall.PtraceCont(t.tid, 0) + return os.NewSyscallError("ptrace(CONT)", err) +} + +func (t *thread) ptraceContWithSignal(sig int) os.Error { + err := syscall.PtraceCont(t.tid, sig) + return os.NewSyscallError("ptrace(CONT)", err) +} + +func (t *thread) ptraceStep() os.Error { + err := syscall.PtraceSingleStep(t.tid) + return os.NewSyscallError("ptrace(SINGLESTEP)", err) +} + +func (t *thread) ptraceDetach() os.Error { + err := syscall.PtraceDetach(t.tid) + return os.NewSyscallError("ptrace(DETACH)", err) +} + +/* + * Logging utilties + */ + +var logLock sync.Mutex + +func (t *thread) logTrace(format string, args ...interface{}) { + if !trace { + return + } + logLock.Lock() + defer logLock.Unlock() + fmt.Fprintf(os.Stderr, "Thread %d", t.tid) + if traceIP { + var regs syscall.PtraceRegs + err := t.ptraceGetRegs(®s) + if err == nil { + fmt.Fprintf(os.Stderr, "@%x", regs.PC()) + } + } + fmt.Fprint(os.Stderr, ": ") + fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprint(os.Stderr, "\n") +} + +func (t *thread) warn(format string, args ...interface{}) { + logLock.Lock() + defer logLock.Unlock() + fmt.Fprintf(os.Stderr, "Thread %d: WARNING ", t.tid) + fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprint(os.Stderr, "\n") +} + +func (p *process) logTrace(format string, args ...interface{}) { + if !trace { + return + } + logLock.Lock() + defer logLock.Unlock() + fmt.Fprintf(os.Stderr, "Process %d: ", p.pid) + fmt.Fprintf(os.Stderr, format, args...) + fmt.Fprint(os.Stderr, "\n") +} + +/* + * State utilities + */ + +// someStoppedThread returns a stopped thread from the process. +// Returns nil if no threads are stopped. +// +// Must be called from the monitor thread. +func (p *process) someStoppedThread() *thread { + for _, t := range p.threads { + if t.state.isStopped() { + return t + } + } + return nil +} + +// someRunningThread returns a running thread from the process. +// Returns nil if no threads are running. +// +// Must be called from the monitor thread. +func (p *process) someRunningThread() *thread { + for _, t := range p.threads { + if t.state.isRunning() { + return t + } + } + return nil +} + +/* + * Breakpoint utilities + */ + +// installBreakpoints adds breakpoints to the attached process. +// +// Must be called from the monitor thread. +func (p *process) installBreakpoints() os.Error { + n := 0 + main := p.someStoppedThread() + for _, b := range p.breakpoints { + if b.olddata != nil { + continue + } + + b.olddata = make([]byte, len(bpinst386)) + _, err := main.ptracePeekText(uintptr(b.pc), b.olddata) + if err != nil { + b.olddata = nil + return err + } + + _, err = main.ptracePokeText(uintptr(b.pc), bpinst386) + if err != nil { + b.olddata = nil + return err + } + n++ + } + if n > 0 { + p.logTrace("installed %d/%d breakpoints", n, len(p.breakpoints)) + } + + return nil +} + +// uninstallBreakpoints removes the installed breakpoints from p. +// +// Must be called from the monitor thread. +func (p *process) uninstallBreakpoints() os.Error { + if len(p.threads) == 0 { + return nil + } + n := 0 + main := p.someStoppedThread() + for _, b := range p.breakpoints { + if b.olddata == nil { + continue + } + + _, err := main.ptracePokeText(uintptr(b.pc), b.olddata) + if err != nil { + return err + } + b.olddata = nil + n++ + } + if n > 0 { + p.logTrace("uninstalled %d/%d breakpoints", n, len(p.breakpoints)) + } + + return nil +} + +/* + * Debug event handling + */ + +// wait waits for a wait event from this thread and sends it on the +// debug events channel for this thread's process. This should be +// started in its own goroutine when the attached thread enters a +// running state. The goroutine will exit as soon as it sends a debug +// event. +func (t *thread) wait() { + for { + var ev debugEvent + ev.t = t + t.logTrace("beginning wait") + ev.Waitmsg, ev.err = os.Wait(t.tid, syscall.WALL) + if ev.err == nil && ev.Pid != t.tid { + panic(fmt.Sprint("Wait returned pid ", ev.Pid, " wanted ", t.tid)) + } + if ev.StopSignal() == syscall.SIGSTOP && t.ignoreNextSigstop { + // Spurious SIGSTOP. See Thread.Stop(). + t.ignoreNextSigstop = false + err := t.ptraceCont() + if err == nil { + continue + } + // If we failed to continue, just let + // the stop go through so we can + // update the thread's state. + } + if !<-t.proc.ready { + // The monitor exited + break + } + t.proc.debugEvents <- &ev + break + } +} + +// setState sets this thread's state, starts a wait thread if +// necessary, and invokes state transition handlers. +// +// Must be called from the monitor thread. +func (t *thread) setState(newState threadState) { + oldState := t.state + t.state = newState + t.logTrace("state %v -> %v", oldState, newState) + + if !oldState.isRunning() && (newState.isRunning() || newState.isZombie()) { + // Start waiting on this thread + go t.wait() + } + + // Invoke state change handlers + handlers := t.proc.transitionHandlers + if handlers.Len() == 0 { + return + } + + t.proc.transitionHandlers = nil + for _, h := range handlers { + h := h.(*transitionHandler) + h.handle(t, oldState, newState) + } +} + +// sendSigstop sends a SIGSTOP to this thread. +func (t *thread) sendSigstop() os.Error { + t.logTrace("sending SIGSTOP") + err := syscall.Tgkill(t.proc.pid, t.tid, syscall.SIGSTOP) + return os.NewSyscallError("tgkill", err) +} + +// stopAsync sends SIGSTOP to all threads in state 'running'. +// +// Must be called from the monitor thread. +func (p *process) stopAsync() os.Error { + for _, t := range p.threads { + if t.state == running { + err := t.sendSigstop() + if err != nil { + return err + } + t.setState(stopping) + } + } + return nil +} + +// doTrap handles SIGTRAP debug events with a cause of 0. These can +// be caused either by an installed breakpoint, a breakpoint in the +// program text, or by single stepping. +// +// TODO(austin) I think we also get this on an execve syscall. +func (ev *debugEvent) doTrap() (threadState, os.Error) { + t := ev.t + + if t.state == singleStepping { + return stopped, nil + } + + // Hit a breakpoint. Linux leaves the program counter after + // the breakpoint. If this is an installed breakpoint, we + // need to back the PC up to the breakpoint PC. + var regs syscall.PtraceRegs + err := t.ptraceGetRegs(®s) + if err != nil { + return stopped, err + } + + b, ok := t.proc.breakpoints[uintptr(regs.PC())-uintptr(len(bpinst386))] + if !ok { + // We must have hit a breakpoint that was actually in + // the program. Leave the IP where it is so we don't + // re-execute the breakpoint instruction. Expose the + // fact that we stopped with a SIGTRAP. + return stoppedSignal, nil + } + + t.breakpoint = b + t.logTrace("at breakpoint %v, backing up PC from %#x", b, regs.PC()) + + regs.SetPC(uint64(b.pc)) + err = t.ptraceSetRegs(®s) + if err != nil { + return stopped, err + } + return stoppedBreakpoint, nil +} + +// doPtraceClone handles SIGTRAP debug events with a PTRACE_EVENT_CLONE +// cause. It initializes the new thread, adds it to the process, and +// returns the appropriate thread state for the existing thread. +func (ev *debugEvent) doPtraceClone() (threadState, os.Error) { + t := ev.t + + // Get the TID of the new thread + tid, err := t.ptraceGetEventMsg() + if err != nil { + return stopped, err + } + + nt, err := t.proc.newThread(int(tid), syscall.SIGSTOP, true) + if err != nil { + return stopped, err + } + + // Remember the thread + t.newThread = nt + + return stoppedThreadCreate, nil +} + +// doPtraceExit handles SIGTRAP debug events with a PTRACE_EVENT_EXIT +// cause. It sets up the thread's state, but does not remove it from +// the process. A later WIFEXITED debug event will remove it from the +// process. +func (ev *debugEvent) doPtraceExit() (threadState, os.Error) { + t := ev.t + + // Get exit status + exitStatus, err := t.ptraceGetEventMsg() + if err != nil { + return stopped, err + } + ws := syscall.WaitStatus(exitStatus) + t.logTrace("exited with %v", ws) + switch { + case ws.Exited(): + t.exitStatus = ws.ExitStatus() + case ws.Signaled(): + t.signal = ws.Signal() + } + + // We still need to continue this thread and wait on this + // thread's WIFEXITED event. We'll delete it then. + return stoppedExiting, nil +} + +// process handles a debug event. It modifies any thread or process +// state as necessary, uninstalls breakpoints if necessary, and stops +// any running threads. +func (ev *debugEvent) process() os.Error { + if ev.err != nil { + return ev.err + } + + t := ev.t + t.exitStatus = -1 + t.signal = -1 + + // Decode wait status. + var state threadState + switch { + case ev.Stopped(): + state = stoppedSignal + t.signal = ev.StopSignal() + t.logTrace("stopped with %v", ev) + if ev.StopSignal() == syscall.SIGTRAP { + // What caused the debug trap? + var err os.Error + switch cause := ev.TrapCause(); cause { + case 0: + // Breakpoint or single stepping + state, err = ev.doTrap() + + case syscall.PTRACE_EVENT_CLONE: + state, err = ev.doPtraceClone() + + case syscall.PTRACE_EVENT_EXIT: + state, err = ev.doPtraceExit() + + default: + t.warn("Unknown trap cause %d", cause) + } + + if err != nil { + t.setState(stopped) + t.warn("failed to handle trap %v: %v", ev, err) + } + } + + case ev.Exited(): + state = exited + t.proc.threads[t.tid] = nil, false + t.logTrace("exited %v", ev) + // We should have gotten the exit status in + // PTRACE_EVENT_EXIT, but just in case. + t.exitStatus = ev.ExitStatus() + + case ev.Signaled(): + state = exited + t.proc.threads[t.tid] = nil, false + t.logTrace("signaled %v", ev) + // Again, this should be redundant. + t.signal = ev.Signal() + + default: + panic(fmt.Sprintf("Unexpected wait status %v", ev.Waitmsg)) + } + + // If we sent a SIGSTOP to the thread (indicated by state + // Stopping), we might have raced with a different type of + // stop. If we didn't get the stop we expected, then the + // SIGSTOP we sent is now queued up, so we should ignore the + // next one we get. + if t.state == stopping && ev.StopSignal() != syscall.SIGSTOP { + t.ignoreNextSigstop = true + } + + // TODO(austin) If we're in state stopping and get a SIGSTOP, + // set state stopped instead of stoppedSignal. + + t.setState(state) + + if t.proc.someRunningThread() == nil { + // Nothing is running, uninstall breakpoints + return t.proc.uninstallBreakpoints() + } + // Stop any other running threads + return t.proc.stopAsync() +} + +// onStop adds a handler for state transitions from running to +// non-running states. The handler will be called from the monitor +// thread. +// +// Must be called from the monitor thread. +func (t *thread) onStop(handle func(), onErr func(os.Error)) { + // TODO(austin) This is rather inefficient for things like + // stepping all threads during a continue. Maybe move + // transitionHandlers to the thread, or have both per-thread + // and per-process transition handlers. + h := &transitionHandler{nil, onErr} + h.handle = func(st *thread, old, new threadState) { + if t == st && old.isRunning() && !new.isRunning() { + handle() + } else { + t.proc.transitionHandlers.Push(h) + } + } + t.proc.transitionHandlers.Push(h) +} + +/* + * Event monitor + */ + +// monitor handles debug events and debug requests for p, exiting when +// there are no threads left in p. +func (p *process) monitor() { + var err os.Error + + // Linux requires that all ptrace calls come from the thread + // that originally attached. Prevent the Go scheduler from + // migrating us to other OS threads. + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + hadThreads := false + for err == nil { + p.ready <- true + select { + case event := <-p.debugEvents: + err = event.process() + + case req := <-p.debugReqs: + req.res <- req.f() + + case err = <-p.stopReq: + break + } + + if len(p.threads) == 0 { + if err == nil && hadThreads { + p.logTrace("no more threads; monitor exiting") + err = ProcessExited{} + } + } else { + hadThreads = true + } + } + + // Abort waiting handlers + // TODO(austin) How do I stop the wait threads? + for _, h := range p.transitionHandlers { + h := h.(*transitionHandler) + h.onErr(err) + } + + // Indicate that the monitor cannot receive any more messages + p.err = err + close(p.ready) +} + +// do executes f in the monitor thread (and, thus, atomically with +// respect to thread state changes). f must not block. +// +// Must NOT be called from the monitor thread. +func (p *process) do(f func() os.Error) os.Error { + if !<-p.ready { + return p.err + } + req := &debugReq{f, make(chan os.Error)} + p.debugReqs <- req + return <-req.res +} + +// stopMonitor stops the monitor with the given error. If the monitor +// is already stopped, does nothing. +func (p *process) stopMonitor(err os.Error) { + if err == nil { + panic("cannot stop the monitor with no error") + } + if <-p.ready { + p.stopReq <- err + } +} + +/* + * Public thread interface + */ + +func (t *thread) Regs() (Regs, os.Error) { + var regs syscall.PtraceRegs + + err := t.proc.do(func() os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot get registers", t.state} + } + return t.ptraceGetRegs(®s) + }) + if err != nil { + return nil, err + } + + setter := func(r *syscall.PtraceRegs) os.Error { + return t.proc.do(func() os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot get registers", t.state} + } + return t.ptraceSetRegs(r) + }) + } + return newRegs(®s, setter), nil +} + +func (t *thread) Peek(addr Word, out []byte) (int, os.Error) { + var c int + + err := t.proc.do(func() os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot peek text", t.state} + } + + var err os.Error + c, err = t.ptracePeekText(uintptr(addr), out) + return err + }) + + return c, err +} + +func (t *thread) Poke(addr Word, out []byte) (int, os.Error) { + var c int + + err := t.proc.do(func() os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot poke text", t.state} + } + + var err os.Error + c, err = t.ptracePokeText(uintptr(addr), out) + return err + }) + + return c, err +} + +// stepAsync starts this thread single stepping. When the single step +// is complete, it will send nil on the given channel. If an error +// occurs while setting up the single step, it returns that error. If +// an error occurs while waiting for the single step to complete, it +// sends that error on the channel. +func (t *thread) stepAsync(ready chan os.Error) os.Error { + if err := t.ptraceStep(); err != nil { + return err + } + t.setState(singleStepping) + t.onStop(func() { ready <- nil }, + func(err os.Error) { ready <- err }) + return nil +} + +func (t *thread) Step() os.Error { + t.logTrace("Step {") + defer t.logTrace("}") + + ready := make(chan os.Error) + + err := t.proc.do(func() os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot single step", t.state} + } + return t.stepAsync(ready) + }) + if err != nil { + return err + } + + err = <-ready + return err +} + +// TODO(austin) We should probably get this via C's strsignal. +var sigNames = [...]string{ + "SIGEXIT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", + "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", + "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", + "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGPOLL", + "SIGPWR", "SIGSYS", +} + +// sigName returns the symbolic name for the given signal number. If +// the signal number is invalid, returns "<invalid>". +func sigName(signal int) string { + if signal < 0 || signal >= len(sigNames) { + return "<invalid>" + } + return sigNames[signal] +} + +func (t *thread) Stopped() (Cause, os.Error) { + var c Cause + err := t.proc.do(func() os.Error { + switch t.state { + case stopped: + c = Stopped{} + + case stoppedBreakpoint: + c = Breakpoint(t.breakpoint.pc) + + case stoppedSignal: + c = Signal(sigName(t.signal)) + + case stoppedThreadCreate: + c = &ThreadCreate{t.newThread} + + case stoppedExiting, exiting, exited: + if t.signal == -1 { + c = &ThreadExit{t.exitStatus, ""} + } else { + c = &ThreadExit{t.exitStatus, sigName(t.signal)} + } + + default: + return &badState{t, "cannot get stop cause", t.state} + } + return nil + }) + if err != nil { + return nil, err + } + + return c, nil +} + +func (p *process) Threads() []Thread { + var res []Thread + + p.do(func() os.Error { + res = make([]Thread, len(p.threads)) + i := 0 + for _, t := range p.threads { + // Exclude zombie threads. + st := t.state + if st == exiting || st == exited || st == detached { + continue + } + + res[i] = t + i++ + } + res = res[0:i] + return nil + }) + return res +} + +func (p *process) AddBreakpoint(pc Word) os.Error { + return p.do(func() os.Error { + if t := p.someRunningThread(); t != nil { + return &badState{t, "cannot add breakpoint", t.state} + } + if _, ok := p.breakpoints[uintptr(pc)]; ok { + return breakpointExistsError(pc) + } + p.breakpoints[uintptr(pc)] = &breakpoint{pc: uintptr(pc)} + return nil + }) +} + +func (p *process) RemoveBreakpoint(pc Word) os.Error { + return p.do(func() os.Error { + if t := p.someRunningThread(); t != nil { + return &badState{t, "cannot remove breakpoint", t.state} + } + if _, ok := p.breakpoints[uintptr(pc)]; !ok { + return noBreakpointError(pc) + } + p.breakpoints[uintptr(pc)] = nil, false + return nil + }) +} + +func (p *process) Continue() os.Error { + // Single step any threads that are stopped at breakpoints so + // we can reinstall breakpoints. + var ready chan os.Error + count := 0 + + err := p.do(func() os.Error { + // We make the ready channel big enough to hold all + // ready message so we don't jam up the monitor if we + // stop listening (e.g., if there's an error). + ready = make(chan os.Error, len(p.threads)) + + for _, t := range p.threads { + if !t.state.isStopped() { + continue + } + + // We use the breakpoint map directly here + // instead of checking the stop cause because + // it could have been stopped at a breakpoint + // for some other reason, or the breakpoint + // could have been added since it was stopped. + var regs syscall.PtraceRegs + err := t.ptraceGetRegs(®s) + if err != nil { + return err + } + if b, ok := p.breakpoints[uintptr(regs.PC())]; ok { + t.logTrace("stepping over breakpoint %v", b) + if err := t.stepAsync(ready); err != nil { + return err + } + count++ + } + } + return nil + }) + if err != nil { + p.stopMonitor(err) + return err + } + + // Wait for single stepping threads + for count > 0 { + err = <-ready + if err != nil { + p.stopMonitor(err) + return err + } + count-- + } + + // Continue all threads + err = p.do(func() os.Error { + if err := p.installBreakpoints(); err != nil { + return err + } + + for _, t := range p.threads { + var err os.Error + switch { + case !t.state.isStopped(): + continue + + case t.state == stoppedSignal && t.signal != syscall.SIGSTOP && t.signal != syscall.SIGTRAP: + t.logTrace("continuing with signal %d", t.signal) + err = t.ptraceContWithSignal(t.signal) + + default: + t.logTrace("continuing") + err = t.ptraceCont() + } + if err != nil { + return err + } + if t.state == stoppedExiting { + t.setState(exiting) + } else { + t.setState(running) + } + } + return nil + }) + if err != nil { + // TODO(austin) Do we need to stop the monitor with + // this error atomically with the do-routine above? + p.stopMonitor(err) + return err + } + + return nil +} + +func (p *process) WaitStop() os.Error { + // We need a non-blocking ready channel for the case where all + // threads are already stopped. + ready := make(chan os.Error, 1) + + err := p.do(func() os.Error { + // Are all of the threads already stopped? + if p.someRunningThread() == nil { + ready <- nil + return nil + } + + // Monitor state transitions + h := &transitionHandler{} + h.handle = func(st *thread, old, new threadState) { + if !new.isRunning() { + if p.someRunningThread() == nil { + ready <- nil + return + } + } + p.transitionHandlers.Push(h) + } + h.onErr = func(err os.Error) { ready <- err } + p.transitionHandlers.Push(h) + return nil + }) + if err != nil { + return err + } + + return <-ready +} + +func (p *process) Stop() os.Error { + err := p.do(func() os.Error { return p.stopAsync() }) + if err != nil { + return err + } + + return p.WaitStop() +} + +func (p *process) Detach() os.Error { + if err := p.Stop(); err != nil { + return err + } + + err := p.do(func() os.Error { + if err := p.uninstallBreakpoints(); err != nil { + return err + } + + for pid, t := range p.threads { + if t.state.isStopped() { + // We can't detach from zombies. + if err := t.ptraceDetach(); err != nil { + return err + } + } + t.setState(detached) + p.threads[pid] = nil, false + } + return nil + }) + // TODO(austin) Wait for monitor thread to exit? + return err +} + +// newThread creates a new thread object and waits for its initial +// signal. If cloned is true, this thread was cloned from a thread we +// are already attached to. +// +// Must be run from the monitor thread. +func (p *process) newThread(tid int, signal int, cloned bool) (*thread, os.Error) { + t := &thread{tid: tid, proc: p, state: stopped} + + // Get the signal from the thread + // TODO(austin) Thread might already be stopped if we're attaching. + w, err := os.Wait(tid, syscall.WALL) + if err != nil { + return nil, err + } + if w.Pid != tid || w.StopSignal() != signal { + return nil, &newThreadError{w, tid, signal} + } + + if !cloned { + err = t.ptraceSetOptions(syscall.PTRACE_O_TRACECLONE | syscall.PTRACE_O_TRACEEXIT) + if err != nil { + return nil, err + } + } + + p.threads[tid] = t + + return t, nil +} + +// attachThread attaches a running thread to the process. +// +// Must NOT be run from the monitor thread. +func (p *process) attachThread(tid int) (*thread, os.Error) { + p.logTrace("attaching to thread %d", tid) + var thr *thread + err := p.do(func() os.Error { + errno := syscall.PtraceAttach(tid) + if errno != 0 { + return os.NewSyscallError("ptrace(ATTACH)", errno) + } + + var err os.Error + thr, err = p.newThread(tid, syscall.SIGSTOP, false) + return err + }) + return thr, err +} + +// attachAllThreads attaches to all threads in a process. +func (p *process) attachAllThreads() os.Error { + taskPath := "/proc/" + strconv.Itoa(p.pid) + "/task" + taskDir, err := os.Open(taskPath, 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), + } + + 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 +} diff --git a/libgo/go/debug/proc/proc_rtems.go b/libgo/go/debug/proc/proc_rtems.go new file mode 100644 index 000000000..5311a63ba --- /dev/null +++ b/libgo/go/debug/proc/proc_rtems.go @@ -0,0 +1,17 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import "os" + +// Process tracing is not supported on RTEMS yet. + +func Attach(pid int) (Process, os.Error) { + return nil, os.NewError("debug/proc not implemented on RTEMS") +} + +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) { + return Attach(0) +} diff --git a/libgo/go/debug/proc/proc_solaris.go b/libgo/go/debug/proc/proc_solaris.go new file mode 100644 index 000000000..a72c59237 --- /dev/null +++ b/libgo/go/debug/proc/proc_solaris.go @@ -0,0 +1,17 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import "os" + +// Process tracing is not supported on Solaris yet. + +func Attach(pid int) (Process, os.Error) { + return nil, os.NewError("debug/proc not implemented on Solaris") +} + +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) { + return Attach(0) +} diff --git a/libgo/go/debug/proc/proc_windows.go b/libgo/go/debug/proc/proc_windows.go new file mode 100644 index 000000000..dc22faef8 --- /dev/null +++ b/libgo/go/debug/proc/proc_windows.go @@ -0,0 +1,17 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import "os" + +// Process tracing is not supported on windows yet. + +func Attach(pid int) (Process, os.Error) { + return nil, os.NewError("debug/proc not implemented on windows") +} + +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) { + return Attach(0) +} diff --git a/libgo/go/debug/proc/ptrace-nptl.txt b/libgo/go/debug/proc/ptrace-nptl.txt new file mode 100644 index 000000000..62cbf7700 --- /dev/null +++ b/libgo/go/debug/proc/ptrace-nptl.txt @@ -0,0 +1,132 @@ +// 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. Furthermore, any ptrace command on a +non-waitable zombie, including PTRACE_DETACH, will return ESRCH. + +== 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/libgo/go/debug/proc/regs_darwin_386.go b/libgo/go/debug/proc/regs_darwin_386.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_darwin_386.go @@ -0,0 +1,5 @@ +// 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 diff --git a/libgo/go/debug/proc/regs_darwin_amd64.go b/libgo/go/debug/proc/regs_darwin_amd64.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_darwin_amd64.go @@ -0,0 +1,5 @@ +// 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 diff --git a/libgo/go/debug/proc/regs_freebsd_386.go b/libgo/go/debug/proc/regs_freebsd_386.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_freebsd_386.go @@ -0,0 +1,5 @@ +// 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 diff --git a/libgo/go/debug/proc/regs_freebsd_amd64.go b/libgo/go/debug/proc/regs_freebsd_amd64.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_freebsd_amd64.go @@ -0,0 +1,5 @@ +// 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 diff --git a/libgo/go/debug/proc/regs_linux_386.go b/libgo/go/debug/proc/regs_linux_386.go new file mode 100644 index 000000000..b4a9769db --- /dev/null +++ b/libgo/go/debug/proc/regs_linux_386.go @@ -0,0 +1,143 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import ( + "os" + "strconv" + "syscall" +) + +type _386Regs struct { + syscall.PtraceRegs + setter func(*syscall.PtraceRegs) os.Error +} + +var names = []string{ + "eax", + "ebx", + "ecx", + "edx", + "esi", + "edi", + "ebp", + "esp", + "eip", + "eflags", + "cs", + "ss", + "ds", + "es", + "fs", + "gs", +} + +func (r *_386Regs) PC() Word { return Word(r.Eip) } + +func (r *_386Regs) SetPC(val Word) os.Error { + r.Eip = int32(val) + return r.setter(&r.PtraceRegs) +} + +func (r *_386Regs) Link() Word { + // TODO(austin) + panic("No link register") +} + +func (r *_386Regs) SetLink(val Word) os.Error { panic("No link register") } + +func (r *_386Regs) SP() Word { return Word(r.Esp) } + +func (r *_386Regs) SetSP(val Word) os.Error { + r.Esp = int32(val) + return r.setter(&r.PtraceRegs) +} + +func (r *_386Regs) Names() []string { return names } + +func (r *_386Regs) Get(i int) Word { + switch i { + case 0: + return Word(uint32(r.Eax)) + case 1: + return Word(uint32(r.Ebx)) + case 2: + return Word(uint32(r.Ecx)) + case 3: + return Word(uint32(r.Edx)) + case 4: + return Word(uint32(r.Esi)) + case 5: + return Word(uint32(r.Edi)) + case 6: + return Word(uint32(r.Ebp)) + case 7: + return Word(uint32(r.Esp)) + case 8: + return Word(uint32(r.Eip)) + case 9: + return Word(uint32(r.Eflags)) + case 10: + return Word(r.Xcs) + case 11: + return Word(r.Xss) + case 12: + return Word(r.Xds) + case 13: + return Word(r.Xes) + case 14: + return Word(r.Xfs) + case 15: + return Word(r.Xgs) + } + panic("invalid register index " + strconv.Itoa(i)) +} + +func (r *_386Regs) Set(i int, val Word) os.Error { + switch i { + case 0: + r.Eax = int32(val) + case 1: + r.Ebx = int32(val) + case 2: + r.Ecx = int32(val) + case 3: + r.Edx = int32(val) + case 4: + r.Esi = int32(val) + case 5: + r.Edi = int32(val) + case 6: + r.Ebp = int32(val) + case 7: + r.Esp = int32(val) + case 8: + r.Eip = int32(val) + case 9: + r.Eflags = int32(val) + case 10: + r.Xcs = int32(val) + case 11: + r.Xss = int32(val) + case 12: + r.Xds = int32(val) + case 13: + r.Xes = int32(val) + case 14: + r.Xfs = int32(val) + case 15: + r.Xgs = int32(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 := _386Regs{} + res.PtraceRegs = *regs + res.setter = setter + return &res +} diff --git a/libgo/go/debug/proc/regs_linux_amd64.go b/libgo/go/debug/proc/regs_linux_amd64.go new file mode 100644 index 000000000..381be29b1 --- /dev/null +++ b/libgo/go/debug/proc/regs_linux_amd64.go @@ -0,0 +1,191 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import ( + "os" + "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[0:] } + +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 +} diff --git a/libgo/go/debug/proc/regs_linux_arm.go b/libgo/go/debug/proc/regs_linux_arm.go new file mode 100644 index 000000000..ec78cbcf2 --- /dev/null +++ b/libgo/go/debug/proc/regs_linux_arm.go @@ -0,0 +1,39 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proc + +import ( + "os" + "syscall" +) + +// TODO(kaib): add support + +type armRegs struct{} + +func (r *armRegs) PC() Word { return Word(0) } + +func (r *armRegs) SetPC(val Word) os.Error { return nil } + +func (r *armRegs) Link() Word { return Word(0) } + +func (r *armRegs) SetLink(val Word) os.Error { return nil } + +func (r *armRegs) SP() Word { return Word(0) } + +func (r *armRegs) SetSP(val Word) os.Error { return nil } + +func (r *armRegs) Names() []string { return nil } + +func (r *armRegs) Get(i int) Word { return Word(0) } + +func (r *armRegs) Set(i int, val Word) os.Error { + return nil +} + +func newRegs(regs *syscall.PtraceRegs, setter func(*syscall.PtraceRegs) os.Error) Regs { + res := armRegs{} + return &res +} diff --git a/libgo/go/debug/proc/regs_windows_386.go b/libgo/go/debug/proc/regs_windows_386.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_windows_386.go @@ -0,0 +1,5 @@ +// 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 diff --git a/libgo/go/debug/proc/regs_windows_amd64.go b/libgo/go/debug/proc/regs_windows_amd64.go new file mode 100644 index 000000000..60c9ac719 --- /dev/null +++ b/libgo/go/debug/proc/regs_windows_amd64.go @@ -0,0 +1,5 @@ +// 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 |