From: Russ Cox Date: Tue, 1 Sep 2009 18:51:05 +0000 (-0700) Subject: import debug/proc from usr/austin/ptrace X-Git-Tag: weekly.2009-11-06~684 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=dd87082ab80059a0c4d04ca6605f15ce19ac1a89;p=gostls13.git import debug/proc from usr/austin/ptrace R=austin DELTA=1892 (1892 added, 0 deleted, 0 changed) OCL=34183 CL=34197 --- diff --git a/src/pkg/debug/proc/Makefile b/src/pkg/debug/proc/Makefile new file mode 100644 index 0000000000..988c495000 --- /dev/null +++ b/src/pkg/debug/proc/Makefile @@ -0,0 +1,13 @@ +# 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. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=ptrace +GOFILES=\ + proc.go\ + proc_linux.go\ + regs_$(GOOS)_$(GOARCH).go\ + +include $(GOROOT)/src/Make.pkg diff --git a/src/pkg/debug/proc/proc.go b/src/pkg/debug/proc/proc.go new file mode 100644 index 0000000000..023f4775c0 --- /dev/null +++ b/src/pkg/debug/proc/proc.go @@ -0,0 +1,232 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ptrace 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 + +import ( + "os"; + "strconv"; +) + +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/src/pkg/debug/proc/proc_darwin.go b/src/pkg/debug/proc/proc_darwin.go new file mode 100644 index 0000000000..2c7b4231f5 --- /dev/null +++ b/src/pkg/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/src/pkg/debug/proc/proc_linux.go b/src/pkg/debug/proc/proc_linux.go new file mode 100644 index 0000000000..88269100fd --- /dev/null +++ b/src/pkg/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 + +import ( + "container/vector"; + "fmt"; + "io"; + "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 ""; + } + return fmt.Sprintf("%#x", bp.pc); +} + +// bpinst386 is the breakpoint instruction used on 386 and amd64. +var bpinst386 = []byte{0xcc}; + +// A debugEvent represents a reason a thread stopped or a wait error. +type debugEvent struct { + *os.Waitmsg; + t *thread; + err os.Error; +} + +// A debugReq is a request to execute a closure in the monitor thread. +type debugReq struct { + f func () os.Error; + res chan os.Error; +} + +// A transitionHandler specifies a function to be called when a thread +// changes state and a function to be called when an error occurs in +// the monitor. Both run in the monitor thread. Before the monitor +// invokes a handler, it removes the handler from the handler queue. +// The handler should re-add itself if needed. +type transitionHandler struct { + handle func (*thread, threadState, threadState); + onErr func (os.Error); +} + +// A process is a Linux process, which consists of a set of threads. +// Each running process has one monitor thread, which processes +// messages from the debugEvents, debugReqs, and stopReq channels and +// calls transition handlers. +type process struct { + pid int; + threads map[int] *thread; + breakpoints map[uintptr] *breakpoint; + debugEvents chan *debugEvent; + debugReqs chan *debugReq; + stopReq chan os.Error; + transitionHandlers *vector.Vector; +} + +// A thread represents a Linux thread in another process that is being +// debugged. Each running thread has an associated goroutine that +// waits for thread updates and sends them to the process monitor. +type thread struct { + tid int; + proc *process; + // Whether to ignore the next SIGSTOP received by wait. + ignoreNextSigstop bool; + + // Thread state. Only modified via setState. + state threadState; + // If state == StoppedBreakpoint + breakpoint *breakpoint; + // If state == StoppedSignal or state == Exited + signal int; + // If state == StoppedThreadCreate + newThread *thread; + // If state == Exited + exitStatus int; +} + +/* + * Errors + */ + +type badState struct { + thread *thread; + message string; + state threadState; +} + +func (e *badState) String() string { + return fmt.Sprintf("Thread %d %s from state %v", e.thread.tid, e.message, e.state); +} + +type breakpointExistsError Word + +func (e breakpointExistsError) String() string { + return fmt.Sprintf("breakpoint already exists at PC %#x", e); +} + +type noBreakpointError Word + +func (e noBreakpointError) String() string { + return fmt.Sprintf("no breakpoint at PC %#x", e); +} + +type newThreadError struct { + *os.Waitmsg; + wantPid int; + wantSig int; +} + +func (e *newThreadError) String() string { + return fmt.Sprintf("newThread wait wanted pid %v and signal %v, got %v and %v", e.Pid, e.StopSignal(), e.wantPid, e.wantSig); +} + +/* + * Ptrace wrappers + */ + +func (t *thread) ptracePeekText(addr uintptr, out []byte) (int, os.Error) { + c, err := syscall.PtracePeekText(t.tid, addr, out); + 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(®s); + if err == nil { + fmt.Fprintf(os.Stderr, "@%x", regs.Rip); + } + } + fmt.Fprint(os.Stderr, ": "); + fmt.Fprintf(os.Stderr, format, args); + fmt.Fprint(os.Stderr, "\n"); +} + +func (t *thread) warn(format string, args ...) { + logLock.Lock(); + defer logLock.Unlock(); + fmt.Fprintf(os.Stderr, "Thread %d: WARNING ", t.tid); + fmt.Fprintf(os.Stderr, format, args); + fmt.Fprint(os.Stderr, "\n"); +} + +func (p *process) logTrace(format string, args ...) { + if !trace { + return; + } + logLock.Lock(); + defer logLock.Unlock(); + fmt.Fprintf(os.Stderr, "Process %d: ", p.pid); + fmt.Fprintf(os.Stderr, format, args); + fmt.Fprint(os.Stderr, "\n"); +} + +/* + * State utilities + */ + +// someStoppedThread returns a stopped thread from the process. +// Returns nil if no threads are stopped. +// +// Must be called from the monitor thread. +func (p *process) someStoppedThread() *thread { + for _, t := range p.threads { + if t.state.isStopped() { + return t; + } + } + return nil; +} + +// someRunningThread returns a running thread from the process. +// Returns nil if no threads are running. +// +// Must be called from the monitor thread. +func (p *process) someRunningThread() *thread { + for _, t := range p.threads { + if t.state.isRunning() { + return t; + } + } + return nil; +} + +/* + * Breakpoint utilities + */ + +// installBreakpoints adds breakpoints to the attached process. +// +// Must be called from the monitor thread. +func (p *process) installBreakpoints() os.Error { + n := 0; + main := p.someStoppedThread(); + for _, b := range p.breakpoints { + if b.olddata != nil { + continue; + } + + b.olddata = make([]byte, len(bpinst386)); + _, err := main.ptracePeekText(uintptr(b.pc), b.olddata); + if err != nil { + b.olddata = nil; + return err; + } + + _, err = main.ptracePokeText(uintptr(b.pc), bpinst386); + if err != nil { + b.olddata = nil; + return err; + } + n++; + } + if n > 0 { + p.logTrace("installed %d/%d breakpoints", n, len(p.breakpoints)); + } + + return nil; +} + +// uninstallBreakpoints removes the installed breakpoints from p. +// +// Must be called from the monitor thread. +func (p *process) uninstallBreakpoints() os.Error { + n := 0; + main := p.someStoppedThread(); + for _, b := range p.breakpoints { + if b.olddata == nil { + continue; + } + + _, err := main.ptracePokeText(uintptr(b.pc), b.olddata); + if err != nil { + return err; + } + b.olddata = nil; + n++; + } + if n > 0 { + p.logTrace("uninstalled %d/%d breakpoints", n, len(p.breakpoints)); + } + + return nil; +} + +/* + * Debug event handling + */ + +// wait waits for a wait event from this thread and sends it on the +// debug events channel for this thread's process. This should be +// started in its own goroutine when the attached thread enters a +// running state. The goroutine will exit as soon as it sends a debug +// event. +func (t *thread) wait() { + for { + var err os.Error; + var ev debugEvent; + ev.t = t; + t.logTrace("beginning wait"); + ev.Waitmsg, ev.err = os.Wait(t.tid, syscall.WALL); + if ev.err == nil && ev.Pid != t.tid { + panic("Wait returned pid ", ev.Pid, " wanted ", t.tid); + } + if ev.StopSignal() == syscall.SIGSTOP && t.ignoreNextSigstop { + // Spurious SIGSTOP. See Thread.Stop(). + t.ignoreNextSigstop = false; + err := t.ptraceCont(); + if err == nil { + continue; + } + // If we failed to continue, just let + // the stop go through so we can + // update the thread's state. + } + t.proc.debugEvents <- &ev; + break; + } +} + +// setState sets this thread's state, starts a wait thread if +// necessary, and invokes state transition handlers. +// +// Must be called from the monitor thread. +func (t *thread) setState(new threadState) { + old := t.state; + t.state = new; + t.logTrace("state %v -> %v", old, new); + + if !old.isRunning() && (new.isRunning() || new.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 = vector.New(0); + for _, h := range handlers.Data() { + h := h.(*transitionHandler); + h.handle(t, old, new); + } +} + +// sendSigstop sends a SIGSTOP to this thread. +func (t *thread) sendSigstop() os.Error { + t.logTrace("sending SIGSTOP"); + err := syscall.Tgkill(t.proc.pid, t.tid, syscall.SIGSTOP); + return os.NewSyscallError("tgkill", err); +} + +// stopAsync sends SIGSTOP to all threads in state 'running'. +// +// Must be called from the monitor thread. +func (p *process) stopAsync() os.Error { + for _, t := range p.threads { + if t.state == running { + err := t.sendSigstop(); + if err != nil { + return err; + } + t.setState(stopping); + } + } + return nil; +} + +// doTrap handles SIGTRAP debug events with a cause of 0. These can +// be caused either by an installed breakpoint, a breakpoint in the +// program text, or by single stepping. +// +// TODO(austin) I think we also get this on an execve syscall. +func (ev *debugEvent) doTrap() (threadState, os.Error) { + t := ev.t; + + if t.state == singleStepping { + return stopped, nil; + } + + // Hit a breakpoint. Linux leaves the program counter after + // the breakpoint. If this is an installed breakpoint, we + // need to back the PC up to the breakpoint PC. + var regs syscall.PtraceRegs; + err := t.ptraceGetRegs(®s); + if err != nil { + return stopped, err; + } + + b, ok := t.proc.breakpoints[uintptr(regs.Rip)-uintptr(len(bpinst386))]; + if !ok { + // We must have hit a breakpoint that was actually in + // the program. Leave the IP where it is so we don't + // re-execute the breakpoint instruction. Expose the + // fact that we stopped with a SIGTRAP. + return stoppedSignal, nil; + } + + t.breakpoint = b; + t.logTrace("at breakpoint %v, backing up PC from %#x", b, regs.Rip); + + regs.Rip = uint64(b.pc); + err = t.ptraceSetRegs(®s); + if err != nil { + return stopped, err; + } + return stoppedBreakpoint, nil; +} + +// doPtraceClone handles SIGTRAP debug events with a PTRACE_EVENT_CLONE +// cause. It initializes the new thread, adds it to the process, and +// returns the appropriate thread state for the existing thread. +func (ev *debugEvent) doPtraceClone() (threadState, os.Error) { + t := ev.t; + + // Get the TID of the new thread + tid, err := t.ptraceGetEventMsg(); + if err != nil { + return stopped, err; + } + + nt, err := t.proc.newThread(int(tid), syscall.SIGSTOP, true); + if err != nil { + return stopped, err; + } + + // Remember the thread + t.newThread = nt; + + return stoppedThreadCreate, nil; +} + +// doPtraceExit handles SIGTRAP debug events with a PTRACE_EVENT_EXIT +// cause. It sets up the thread's state, but does not remove it from +// the process. A later WIFEXITED debug event will remove it from the +// process. +func (ev *debugEvent) doPtraceExit() (threadState, os.Error) { + t := ev.t; + + // Get exit status + exitStatus, err := t.ptraceGetEventMsg(); + if err != nil { + return stopped, err; + } + ws := syscall.WaitStatus(exitStatus); + t.logTrace("exited with %v", ws); + switch { + case ws.Exited(): + t.exitStatus = ws.ExitStatus(); + case ws.Signaled(): + t.signal = ws.Signal(); + } + + // We still need to continue this thread and wait on this + // thread's WIFEXITED event. We'll delete it then. + return stoppedExiting, nil; +} + +// process handles a debug event. It modifies any thread or process +// state as necessary, uninstalls breakpoints if necessary, and stops +// any running threads. +func (ev *debugEvent) process() os.Error { + if ev.err != nil { + return ev.err; + } + + t := ev.t; + t.exitStatus = -1; + t.signal = -1; + + // Decode wait status. + var state threadState; + switch { + case ev.Stopped(): + state = stoppedSignal; + t.signal = ev.StopSignal(); + t.logTrace("stopped with %v", ev); + if ev.StopSignal() == syscall.SIGTRAP { + // What caused the debug trap? + var err os.Error; + switch cause := ev.TrapCause(); cause { + case 0: + // Breakpoint or single stepping + state, err = ev.doTrap(); + + case syscall.PTRACE_EVENT_CLONE: + state, err = ev.doPtraceClone(); + + case syscall.PTRACE_EVENT_EXIT: + state, err = ev.doPtraceExit(); + + default: + t.warn("Unknown trap cause %d", cause); + } + + if err != nil { + t.setState(stopped); + t.warn("failed to handle trap %v: %v", ev, err); + } + } + + case ev.Exited(): + state = exited; + t.proc.threads[t.tid] = nil, false; + t.logTrace("exited %v", ev); + // We should have gotten the exit status in + // PTRACE_EVENT_EXIT, but just in case. + t.exitStatus = ev.ExitStatus(); + + case ev.Signaled(): + state = exited; + t.proc.threads[t.tid] = nil, false; + t.logTrace("signaled %v", ev); + // Again, this should be redundant. + t.signal = ev.Signal(); + + default: + panic(fmt.Sprintf("Unexpected wait status %v", ev.Waitmsg)); + } + + // If we sent a SIGSTOP to the thread (indicated by state + // Stopping), we might have raced with a different type of + // stop. If we didn't get the stop we expected, then the + // SIGSTOP we sent is now queued up, so we should ignore the + // next one we get. + if t.state == stopping && ev.StopSignal() != syscall.SIGSTOP { + t.ignoreNextSigstop = true; + } + + // TODO(austin) If we're in state stopping and get a SIGSTOP, + // set state stopped instead of stoppedSignal. + + t.setState(state); + + if t.proc.someRunningThread() == nil { + // Nothing is running, uninstall breakpoints + return t.proc.uninstallBreakpoints(); + } + // Stop any other running threads + return t.proc.stopAsync(); +} + +// onStop adds a handler for state transitions from running to +// non-running states. The handler will be called from the monitor +// thread. +// +// Must be called from the monitor thread. +func (t *thread) onStop(handle func (), onErr func (os.Error)) { + // TODO(austin) This is rather inefficient for things like + // stepping all threads during a continue. Maybe move + // transitionHandlers to the thread, or have both per-thread + // and per-process transition handlers. + h := &transitionHandler{nil, onErr}; + h.handle = func (st *thread, old, new threadState) { + if t == st && old.isRunning() && !new.isRunning() { + handle(); + } else { + t.proc.transitionHandlers.Push(h); + } + }; + t.proc.transitionHandlers.Push(h); +} + +/* + * Event monitor + */ + +// monitor handles debug events and debug requests for p, exiting when +// there are no threads left in p. +// +// TODO(austin) When an unrecoverable error occurs, abort the monitor +// and record this error so all future calls to do will return it +// immediately. +func (p *process) monitor() { + var err os.Error; + + // Linux requires that all ptrace calls come from the thread + // that originally attached. Prevent the Go scheduler from + // migrating us to other OS threads. + runtime.LockOSThread(); + defer runtime.UnlockOSThread(); + + hadThreads := false; + for { + select { + case event := <-p.debugEvents: + err = event.process(); + if err != nil { + break; + } + + case req := <-p.debugReqs: + req.res <- req.f(); + + case err = <-p.stopReq: + break; + } + + if len(p.threads) == 0 { + if hadThreads { + p.logTrace("no more threads; monitor exiting"); + // TODO(austin) Use a real error do + // future operations will fail + err = nil; + break; + } + } else { + hadThreads = true; + } + } + + // Abort waiting handlers + for _, h := range p.transitionHandlers.Data() { + h := h.(*transitionHandler); + h.onErr(err); + } + + // TODO(austin) How do I stop the wait threads? + if err != nil { + panic(err.String()); + } +} + +// do executes f in the monitor thread (and, thus, atomically with +// respect to thread state changes). f must not block. +// +// Must NOT be called from the monitor thread. +func (p *process) do(f func () os.Error) os.Error { + // TODO(austin) If monitor is stopped, return error. + req := &debugReq{f, make(chan os.Error)}; + p.debugReqs <- req; + return <-req.res; +} + +// stopMonitor stops the monitor with the given error. If the monitor +// is already stopped, does nothing. +func (p *process) stopMonitor(err os.Error) { + doNotBlock := p.stopReq <- err; + // TODO(austin) Wait until monitor has exited? +} + +/* + * Public thread interface + */ + +func (t *thread) Regs() (Regs, os.Error) { + var regs syscall.PtraceRegs; + + err := t.proc.do(func () os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot get registers", t.state}; + } + return t.ptraceGetRegs(®s); + }); + if err != nil { + return nil, err; + } + + setter := func (r *syscall.PtraceRegs) os.Error { + return t.proc.do(func () os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot get registers", t.state}; + } + return t.ptraceSetRegs(r); + }); + }; + return newRegs(®s, setter), nil; +} + +func (t *thread) Peek(addr Word, out []byte) (int, os.Error) { + var c int; + + err := t.proc.do(func () os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot peek text", t.state}; + } + + var err os.Error; + c, err = t.ptracePeekText(uintptr(addr), out); + return err; + }); + + return c, err; +} + +func (t *thread) Poke(addr Word, out []byte) (int, os.Error) { + var c int; + + err := t.proc.do(func () os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot poke text", t.state}; + } + + var err os.Error; + c, err = t.ptracePokeText(uintptr(addr), out); + return err; + }); + + return c, err; +} + +// stepAsync starts this thread single stepping. When the single step +// is complete, it will send nil on the given channel. If an error +// occurs while setting up the single step, it returns that error. If +// an error occurs while waiting for the single step to complete, it +// sends that error on the channel. +func (t *thread) stepAsync(ready chan os.Error) os.Error { + if err := t.ptraceStep(); err != nil { + return err; + } + t.setState(singleStepping); + t.onStop(func () { + ready <- nil; + }, + func (err os.Error) { + ready <- err; + }); + return nil; +} + +func (t *thread) Step() os.Error { + t.logTrace("Step {"); + defer t.logTrace("}"); + + ready := make(chan os.Error); + + err := t.proc.do(func () os.Error { + if !t.state.isStopped() { + return &badState{t, "cannot single step", t.state}; + } + return t.stepAsync(ready); + }); + if err != nil { + return err; + } + + err = <-ready; + return err; +} + +// TODO(austin) We should probably get this via C's strsignal. +var sigNames = [...]string { + "SIGEXIT", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", + "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", + "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", + "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", + "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", + "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGPOLL", + "SIGPWR", "SIGSYS" +} + +// sigName returns the symbolic name for the given signal number. If +// the signal number is invalid, returns "". +func sigName(signal int) string { + if signal < 0 || signal >= len(sigNames) { + return ""; + } + return sigNames[signal]; +} + +func (t *thread) Stopped() (Cause, os.Error) { + var c Cause; + err := t.proc.do(func() os.Error { + switch t.state { + case stopped: + c = Stopped{}; + + case stoppedBreakpoint: + c = Breakpoint(t.breakpoint.pc); + + case stoppedSignal: + c = Signal(sigName(t.signal)); + + case stoppedThreadCreate: + c = &ThreadCreate{t.newThread}; + + case stoppedExiting, exiting, exited: + if t.signal == -1 { + c = &ThreadExit{t.exitStatus, ""}; + } else { + c = &ThreadExit{t.exitStatus, sigName(t.signal)}; + } + + default: + return &badState{t, "cannot get stop cause", t.state}; + } + return nil; + }); + if err != nil { + return nil, err; + } + + return c, nil; +} + +func (p *process) Threads() []Thread { + var res []Thread; + + p.do(func () os.Error { + res = make([]Thread, len(p.threads)); + i := 0; + for _, t := range p.threads { + // Exclude zombie threads. + st := t.state; + if st == exiting || st == exited || st == detached { + continue; + } + + res[i] = t; + i++; + } + res = res[0:i]; + return nil; + }); + return res; +} + +func (p *process) AddBreakpoint(pc Word) os.Error { + return p.do(func () os.Error { + if t := p.someRunningThread(); t != nil { + return &badState{t, "cannot add breakpoint", t.state}; + } + if _, ok := p.breakpoints[uintptr(pc)]; ok { + return breakpointExistsError(pc); + } + p.breakpoints[uintptr(pc)] = &breakpoint{pc: uintptr(pc)}; + return nil; + }); +} + +func (p *process) RemoveBreakpoint(pc Word) os.Error { + return p.do(func () os.Error { + if t := p.someRunningThread(); t != nil { + return &badState{t, "cannot remove breakpoint", t.state}; + } + if _, ok := p.breakpoints[uintptr(pc)]; !ok { + return noBreakpointError(pc); + } + p.breakpoints[uintptr(pc)] = nil, false; + return nil; + }); +} + +func (p *process) Continue() os.Error { + // Single step any threads that are stopped at breakpoints so + // we can reinstall breakpoints. + var ready chan os.Error; + count := 0; + + err := p.do(func () os.Error { + // We make the ready channel big enough to hold all + // ready message so we don't jam up the monitor if we + // stop listening (e.g., if there's an error). + ready = make(chan os.Error, len(p.threads)); + + for _, t := range p.threads { + if !t.state.isStopped() { + continue; + } + + // We use the breakpoint map directly here + // instead of checking the stop cause because + // it could have been stopped at a breakpoint + // for some other reason, or the breakpoint + // could have been added since it was stopped. + var regs syscall.PtraceRegs; + err := t.ptraceGetRegs(®s); + if err != nil { + return err; + } + if b, ok := p.breakpoints[uintptr(regs.Rip)]; ok { + t.logTrace("stepping over breakpoint %v", b); + if err := t.stepAsync(ready); err != nil { + return err; + } + count++; + } + } + return nil; + }); + if err != nil { + p.stopMonitor(err); + return err; + } + + // Wait for single stepping threads + for count > 0 { + err = <-ready; + if err != nil { + p.stopMonitor(err); + return err; + } + count--; + } + + // Continue all threads + err = p.do(func () os.Error { + if err := p.installBreakpoints(); err != nil { + return err; + } + + for _, t := range p.threads { + var err os.Error; + switch { + case !t.state.isStopped(): + continue; + + case t.state == stoppedSignal && t.signal != syscall.SIGSTOP && t.signal != syscall.SIGTRAP: + t.logTrace("continuing with signal %d", t.signal); + err = t.ptraceContWithSignal(t.signal); + + default: + t.logTrace("continuing"); + err = t.ptraceCont(); + } + if err != nil { + return err; + } + 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; + } + + t, err := p.attachThread(tid); + if err != nil { + // There could have been a race, or + // this process could be a zobmie. + statFile, err2 := io.ReadFile(taskPath + "/" + tidStr + "/stat"); + if err2 != nil { + switch err2 := err2.(type) { + case *os.PathError: + if err2.Error == os.ENOENT { + // Raced with thread exit + p.logTrace("raced with thread %d exit", tid); + continue; + } + } + // Return the original error + return err; + } + + statParts := strings.Split(string(statFile), " ", 4); + if len(statParts) > 2 && statParts[2] == "Z" { + // tid is a zombie + p.logTrace("thread %d is a zombie", tid); + continue; + } + + // Return the original error + return err; + } + again = true; + } + } + + return nil; +} + +// newProcess creates a new process object and starts its monitor thread. +func newProcess(pid int) *process { + p := &process{ + pid: pid, + threads: make(map[int] *thread), + breakpoints: make(map[uintptr] *breakpoint), + debugEvents: make(chan *debugEvent), + debugReqs: make(chan *debugReq), + stopReq: make(chan os.Error), + transitionHandlers: vector.New(0) + }; + + go p.monitor(); + + return p; +} + +// Attach attaches to process pid and stops all of its threads. +func Attach(pid int) (Process, os.Error) { + p := newProcess(pid); + + // Attach to all threads + err := p.attachAllThreads(); + if err != nil { + p.Detach(); + // TODO(austin) Detach stopped the monitor already + //p.stopMonitor(err); + return nil, err; + } + + return p, nil; +} + +// ForkExec forks the current process and execs argv0, stopping the +// new process after the exec syscall. See os.ForkExec for additional +// details. +func ForkExec(argv0 string, argv []string, envv []string, dir string, fd []*os.File) (Process, os.Error) +{ + p := newProcess(-1); + + // Create array of integer (system) fds. + intfd := make([]int, len(fd)); + for i, f := range fd { + if f == nil { + intfd[i] = -1; + } else { + intfd[i] = f.Fd(); + } + } + + // Fork from the monitor thread so we get the right tracer pid. + err := p.do(func () os.Error { + pid, errno := syscall.PtraceForkExec(argv0, argv, envv, dir, intfd); + if errno != 0 { + return &os.PathError{"fork/exec", argv0, os.Errno(errno)}; + } + p.pid = pid; + + // The process will raise SIGTRAP when it reaches execve. + t, err := p.newThread(pid, syscall.SIGTRAP, false); + return err; + }); + if err != nil { + p.stopMonitor(err); + return nil, err; + } + + return p, nil; +} diff --git a/src/pkg/debug/proc/ptrace-nptl.txt b/src/pkg/debug/proc/ptrace-nptl.txt new file mode 100644 index 0000000000..62cbf77003 --- /dev/null +++ b/src/pkg/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/src/pkg/debug/proc/regs_darwin_386.go b/src/pkg/debug/proc/regs_darwin_386.go new file mode 100644 index 0000000000..47a7fa8111 --- /dev/null +++ b/src/pkg/debug/proc/regs_darwin_386.go @@ -0,0 +1,11 @@ +// 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 "syscall" + +func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs { + panic("newRegs unimplemented on darwin/386"); +} diff --git a/src/pkg/debug/proc/regs_darwin_amd64.go b/src/pkg/debug/proc/regs_darwin_amd64.go new file mode 100644 index 0000000000..b0d5b52a3e --- /dev/null +++ b/src/pkg/debug/proc/regs_darwin_amd64.go @@ -0,0 +1,11 @@ +// 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 "syscall" + +func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs { + panic("newRegs unimplemented on darwin/amd64"); +} diff --git a/src/pkg/debug/proc/regs_linux_386.go b/src/pkg/debug/proc/regs_linux_386.go new file mode 100644 index 0000000000..89de83d2e9 --- /dev/null +++ b/src/pkg/debug/proc/regs_linux_386.go @@ -0,0 +1,11 @@ +// 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 "syscall" + +func newRegs(regs *syscall.PtraceRegs, setter func (*syscall.PtraceRegs) os.Error) Regs { + panic("newRegs unimplemented on linux/386"); +} diff --git a/src/pkg/debug/proc/regs_linux_amd64.go b/src/pkg/debug/proc/regs_linux_amd64.go new file mode 100644 index 0000000000..ef2a0050e5 --- /dev/null +++ b/src/pkg/debug/proc/regs_linux_amd64.go @@ -0,0 +1,149 @@ +// 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; +} + +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; +}