summaryrefslogtreecommitdiff
path: root/src/pkg/exp/ogle/process.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/exp/ogle/process.go')
-rw-r--r--src/pkg/exp/ogle/process.go541
1 files changed, 541 insertions, 0 deletions
diff --git a/src/pkg/exp/ogle/process.go b/src/pkg/exp/ogle/process.go
new file mode 100644
index 000000000..984364f23
--- /dev/null
+++ b/src/pkg/exp/ogle/process.go
@@ -0,0 +1,541 @@
+// 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 ogle
+
+import (
+ "debug/elf";
+ "debug/gosym";
+ "debug/proc";
+ "exp/eval";
+ "fmt";
+ "log";
+ "os";
+ "reflect";
+)
+
+// A FormatError indicates a failure to process information in or
+// about a remote process, such as unexpected or missing information
+// in the object file or runtime structures.
+type FormatError string
+
+func (e FormatError) String() string {
+ return string(e);
+}
+
+// An UnknownArchitecture occurs when trying to load an object file
+// that indicates an architecture not supported by the debugger.
+type UnknownArchitecture elf.Machine
+
+func (e UnknownArchitecture) String() string {
+ return "unknown architecture: " + elf.Machine(e).String();
+}
+
+// A ProcessNotStopped error occurs when attempting to read or write
+// memory or registers of a process that is not stopped.
+type ProcessNotStopped struct {}
+
+func (e ProcessNotStopped) String() string {
+ return "process not stopped";
+}
+
+// An UnknownGoroutine error is an internal error representing an
+// unrecognized G structure pointer.
+type UnknownGoroutine struct {
+ OSThread proc.Thread;
+ Goroutine proc.Word;
+}
+
+func (e UnknownGoroutine) String() string {
+ return fmt.Sprintf("internal error: unknown goroutine (G %#x)", e.Goroutine);
+}
+
+// A NoCurrentGoroutine error occurs when no goroutine is currently
+// selected in a process (or when there are no goroutines in a
+// process).
+type NoCurrentGoroutine struct {}
+
+func (e NoCurrentGoroutine) String() string {
+ return "no current goroutine";
+}
+
+// A Process represents a remote attached process.
+type Process struct {
+ Arch;
+ proc proc.Process;
+
+ // The symbol table of this process
+ syms *gosym.Table;
+
+ // A possibly-stopped OS thread, or nil
+ threadCache proc.Thread;
+
+ // Types parsed from the remote process
+ types map[proc.Word] *remoteType;
+
+ // Types and values from the remote runtime package
+ runtime runtimeValues;
+
+ // Runtime field indexes
+ f runtimeIndexes;
+
+ // Globals from the sys package (or from no package)
+ sys struct {
+ lessstack, goexit, newproc, deferproc, newprocreadylocked *gosym.Func;
+ allg remotePtr;
+ g0 remoteStruct;
+ };
+
+ // Event queue
+ posted []Event;
+ pending []Event;
+ event Event;
+
+ // Event hooks
+ breakpointHooks map[proc.Word] *breakpointHook;
+ goroutineCreateHook *goroutineCreateHook;
+ goroutineExitHook *goroutineExitHook;
+
+ // Current goroutine, or nil if there are no goroutines
+ curGoroutine *Goroutine;
+
+ // Goroutines by the address of their G structure
+ goroutines map[proc.Word] *Goroutine;
+}
+
+/*
+ * Process creation
+ */
+
+// NewProcess constructs a new remote process around a traced
+// process, an architecture, and a symbol table.
+func NewProcess(tproc proc.Process, arch Arch, syms *gosym.Table) (*Process, os.Error) {
+ p := &Process{
+ Arch: arch,
+ proc: tproc,
+ syms: syms,
+ types: make(map[proc.Word] *remoteType),
+ breakpointHooks: make(map[proc.Word] *breakpointHook),
+ goroutineCreateHook: new(goroutineCreateHook),
+ goroutineExitHook: new(goroutineExitHook),
+ goroutines: make(map[proc.Word] *Goroutine),
+ };
+
+ // Fill in remote runtime
+ p.bootstrap();
+
+ switch {
+ case p.sys.allg.addr().base == 0:
+ return nil, FormatError("failed to find runtime symbol 'allg'");
+ case p.sys.g0.addr().base == 0:
+ return nil, FormatError("failed to find runtime symbol 'g0'");
+ case p.sys.newprocreadylocked == nil:
+ return nil, FormatError("failed to find runtime symbol 'newprocreadylocked'");
+ case p.sys.goexit == nil:
+ return nil, FormatError("failed to find runtime symbol 'sys.goexit'");
+ }
+
+ // Get current goroutines
+ p.goroutines[p.sys.g0.addr().base] = &Goroutine{p.sys.g0, nil, false};
+ err := try(func(a aborter) {
+ g := p.sys.allg.aGet(a);
+ for g != nil {
+ gs := g.(remoteStruct);
+ fmt.Printf("*** Found goroutine at %#x\n", gs.addr().base);
+ p.goroutines[gs.addr().base] = &Goroutine{gs, nil, false};
+ g = gs.field(p.f.G.Alllink).(remotePtr).aGet(a);
+ }
+ });
+ if err != nil {
+ return nil, err;
+ }
+
+ // Create internal breakpoints to catch new and exited goroutines
+ p.OnBreakpoint(proc.Word(p.sys.newprocreadylocked.Entry)).(*breakpointHook).addHandler(readylockedBP, true);
+ p.OnBreakpoint(proc.Word(p.sys.goexit.Entry)).(*breakpointHook).addHandler(goexitBP, true);
+
+ // Select current frames
+ for _, g := range p.goroutines {
+ g.resetFrame();
+ }
+
+ p.selectSomeGoroutine();
+
+ return p, nil;
+}
+
+func elfGoSyms(f *elf.File) (*gosym.Table, os.Error) {
+ text := f.Section(".text");
+ symtab := f.Section(".gosymtab");
+ pclntab := f.Section(".gopclntab");
+ if text == nil || symtab == nil || pclntab == nil {
+ return nil, nil;
+ }
+
+ symdat, err := symtab.Data();
+ if err != nil {
+ return nil, err;
+ }
+ pclndat, err := pclntab.Data();
+ if err != nil {
+ return nil, err;
+ }
+
+ pcln := gosym.NewLineTable(pclndat, text.Addr);
+ tab, err := gosym.NewTable(symdat, pcln);
+ if err != nil {
+ return nil, err;
+ }
+
+ return tab, nil;
+}
+
+// NewProcessElf constructs a new remote process around a traced
+// process and the process' ELF object.
+func NewProcessElf(tproc proc.Process, f *elf.File) (*Process, os.Error) {
+ syms, err := elfGoSyms(f);
+ if err != nil {
+ return nil, err;
+ }
+ if syms == nil {
+ return nil, FormatError("Failed to find symbol table");
+ }
+ var arch Arch;
+ switch f.Machine {
+ case elf.EM_X86_64:
+ arch = Amd64;
+ default:
+ return nil, UnknownArchitecture(f.Machine);
+ }
+ return NewProcess(tproc, arch, syms);
+}
+
+// bootstrap constructs the runtime structure of a remote process.
+func (p *Process) bootstrap() {
+ // Manually construct runtime types
+ p.runtime.String = newManualType(eval.TypeOfNative(rt1String{}), p.Arch);
+ p.runtime.Slice = newManualType(eval.TypeOfNative(rt1Slice{}), p.Arch);
+ p.runtime.Eface = newManualType(eval.TypeOfNative(rt1Eface{}), p.Arch);
+
+ p.runtime.Type = newManualType(eval.TypeOfNative(rt1Type{}), p.Arch);
+ p.runtime.CommonType = newManualType(eval.TypeOfNative(rt1CommonType{}), p.Arch);
+ p.runtime.UncommonType = newManualType(eval.TypeOfNative(rt1UncommonType{}), p.Arch);
+ p.runtime.StructField = newManualType(eval.TypeOfNative(rt1StructField{}), p.Arch);
+ p.runtime.StructType = newManualType(eval.TypeOfNative(rt1StructType{}), p.Arch);
+ p.runtime.PtrType = newManualType(eval.TypeOfNative(rt1PtrType{}), p.Arch);
+ p.runtime.ArrayType = newManualType(eval.TypeOfNative(rt1ArrayType{}), p.Arch);
+ p.runtime.SliceType = newManualType(eval.TypeOfNative(rt1SliceType{}), p.Arch);
+
+ p.runtime.Stktop = newManualType(eval.TypeOfNative(rt1Stktop{}), p.Arch);
+ p.runtime.Gobuf = newManualType(eval.TypeOfNative(rt1Gobuf{}), p.Arch);
+ p.runtime.G = newManualType(eval.TypeOfNative(rt1G{}), p.Arch);
+
+ // Get addresses of type.*runtime.XType for discrimination.
+ rtv := reflect.Indirect(reflect.NewValue(&p.runtime)).(*reflect.StructValue);
+ rtvt := rtv.Type().(*reflect.StructType);
+ for i := 0; i < rtv.NumField(); i++ {
+ n := rtvt.Field(i).Name;
+ if n[0] != 'P' || n[1] < 'A' || n[1] > 'Z' {
+ continue;
+ }
+ sym := p.syms.LookupSym("type.*runtime." + n[1:len(n)]);
+ if sym == nil {
+ continue;
+ }
+ rtv.Field(i).(*reflect.Uint64Value).Set(sym.Value);
+ }
+
+ // Get runtime field indexes
+ fillRuntimeIndexes(&p.runtime, &p.f);
+
+ // Fill G status
+ p.runtime.runtimeGStatus = rt1GStatus;
+
+ // Get globals
+ p.sys.lessstack = p.syms.LookupFunc("sys.lessstack");
+ p.sys.goexit = p.syms.LookupFunc("goexit");
+ p.sys.newproc = p.syms.LookupFunc("sys.newproc");
+ p.sys.deferproc = p.syms.LookupFunc("sys.deferproc");
+ p.sys.newprocreadylocked = p.syms.LookupFunc("newprocreadylocked");
+ if allg := p.syms.LookupSym("allg"); allg != nil {
+ p.sys.allg = remotePtr{remote{proc.Word(allg.Value), p}, p.runtime.G};
+ }
+ if g0 := p.syms.LookupSym("g0"); g0 != nil {
+ p.sys.g0 = p.runtime.G.mk(remote{proc.Word(g0.Value), p}).(remoteStruct);
+ }
+}
+
+func (p *Process) selectSomeGoroutine() {
+ // Once we have friendly goroutine ID's, there might be a more
+ // reasonable behavior for this.
+ p.curGoroutine = nil;
+ for _, g := range p.goroutines {
+ if !g.isG0() && g.frame != nil {
+ p.curGoroutine = g;
+ return;
+ }
+ }
+}
+
+/*
+ * Process memory
+ */
+
+func (p *Process) someStoppedOSThread() proc.Thread {
+ if p.threadCache != nil {
+ if _, err := p.threadCache.Stopped(); err == nil {
+ return p.threadCache;
+ }
+ }
+
+ for _, t := range p.proc.Threads() {
+ if _, err := t.Stopped(); err == nil {
+ p.threadCache = t;
+ return t;
+ }
+ }
+ return nil;
+}
+
+func (p *Process) Peek(addr proc.Word, out []byte) (int, os.Error) {
+ thr := p.someStoppedOSThread();
+ if thr == nil {
+ return 0, ProcessNotStopped{};
+ }
+ return thr.Peek(addr, out);
+}
+
+func (p *Process) Poke(addr proc.Word, b []byte) (int, os.Error) {
+ thr := p.someStoppedOSThread();
+ if thr == nil {
+ return 0, ProcessNotStopped{};
+ }
+ return thr.Poke(addr, b);
+}
+
+func (p *Process) peekUintptr(a aborter, addr proc.Word) proc.Word {
+ return proc.Word(mkUintptr(remote{addr, p}).(remoteUint).aGet(a));
+}
+
+/*
+ * Events
+ */
+
+// OnBreakpoint returns the hook that is run when the program reaches
+// the given program counter.
+func (p *Process) OnBreakpoint(pc proc.Word) EventHook {
+ if bp, ok := p.breakpointHooks[pc]; ok {
+ return bp;
+ }
+ // The breakpoint will register itself when a handler is added
+ return &breakpointHook{commonHook{nil, 0}, p, pc};
+}
+
+// OnGoroutineCreate returns the hook that is run when a goroutine is created.
+func (p *Process) OnGoroutineCreate() EventHook {
+ return p.goroutineCreateHook;
+}
+
+// OnGoroutineExit returns the hook that is run when a goroutine exits.
+func (p *Process) OnGoroutineExit() EventHook {
+ return p.goroutineExitHook;
+}
+
+// osThreadToGoroutine looks up the goroutine running on an OS thread.
+func (p *Process) osThreadToGoroutine(t proc.Thread) (*Goroutine, os.Error) {
+ regs, err := t.Regs();
+ if err != nil {
+ return nil, err;
+ }
+ g := p.G(regs);
+ gt, ok := p.goroutines[g];
+ if !ok {
+ return nil, UnknownGoroutine{t, g};
+ }
+ return gt, nil;
+}
+
+// causesToEvents translates the stop causes of the underlying process
+// into an event queue.
+func (p *Process) causesToEvents() ([]Event, os.Error) {
+ // Count causes we're interested in
+ nev := 0;
+ for _, t := range p.proc.Threads() {
+ if c, err := t.Stopped(); err == nil {
+ switch c := c.(type) {
+ case proc.Breakpoint:
+ nev++;
+ case proc.Signal:
+ // TODO(austin)
+ //nev++;
+ }
+ }
+ }
+
+ // Translate causes to events
+ events := make([]Event, nev);
+ i := 0;
+ for _, t := range p.proc.Threads() {
+ if c, err := t.Stopped(); err == nil {
+ switch c := c.(type) {
+ case proc.Breakpoint:
+ gt, err := p.osThreadToGoroutine(t);
+ if err != nil {
+ return nil, err;
+ }
+ events[i] = &Breakpoint{commonEvent{p, gt}, t, proc.Word(c)};
+ i++;
+ case proc.Signal:
+ // TODO(austin)
+ }
+ }
+ }
+
+ return events, nil;
+}
+
+// postEvent appends an event to the posted queue. These events will
+// be processed before any currently pending events.
+func (p *Process) postEvent(ev Event) {
+ n := len(p.posted);
+ m := n*2;
+ if m == 0 {
+ m = 4;
+ }
+ posted := make([]Event, n+1, m);
+ for i, p := range p.posted {
+ posted[i] = p;
+ }
+ posted[n] = ev;
+ p.posted = posted;
+}
+
+// processEvents processes events in the event queue until no events
+// remain, a handler returns EAStop, or a handler returns an error.
+// It returns either EAStop or EAContinue and possibly an error.
+func (p *Process) processEvents() (EventAction, os.Error) {
+ var ev Event;
+ for len(p.posted) > 0 {
+ ev, p.posted = p.posted[0], p.posted[1:len(p.posted)];
+ action, err := p.processEvent(ev);
+ if action == EAStop {
+ return action, err;
+ }
+ }
+
+ for len(p.pending) > 0 {
+ ev, p.pending = p.pending[0], p.pending[1:len(p.pending)];
+ action, err := p.processEvent(ev);
+ if action == EAStop {
+ return action, err;
+ }
+ }
+
+ return EAContinue, nil;
+}
+
+// processEvent processes a single event, without manipulating the
+// event queues. It returns either EAStop or EAContinue and possibly
+// an error.
+func (p *Process) processEvent(ev Event) (EventAction, os.Error) {
+ p.event = ev;
+
+ var action EventAction;
+ var err os.Error;
+ switch ev := p.event.(type) {
+ case *Breakpoint:
+ hook, ok := p.breakpointHooks[ev.pc];
+ if !ok {
+ break;
+ }
+ p.curGoroutine = ev.Goroutine();
+ action, err = hook.handle(ev);
+
+ case *GoroutineCreate:
+ p.curGoroutine = ev.Goroutine();
+ action, err = p.goroutineCreateHook.handle(ev);
+
+ case *GoroutineExit:
+ action, err = p.goroutineExitHook.handle(ev);
+
+ default:
+ log.Crashf("Unknown event type %T in queue", p.event);
+ }
+
+ if err != nil {
+ return EAStop, err;
+ } else if action == EAStop {
+ return EAStop, nil;
+ }
+ return EAContinue, nil;
+}
+
+// Event returns the last event that caused the process to stop. This
+// may return nil if the process has never been stopped by an event.
+//
+// TODO(austin) Return nil if the user calls p.Stop()?
+func (p *Process) Event() Event {
+ return p.event;
+}
+
+/*
+ * Process control
+ */
+
+// TODO(austin) Cont, WaitStop, and Stop. Need to figure out how
+// event handling works with these. Originally I did it only in
+// WaitStop, but if you Cont and there are pending events, then you
+// have to not actually continue and wait until a WaitStop to process
+// them, even if the event handlers will tell you to continue. We
+// could handle them in both Cont and WaitStop to avoid this problem,
+// but it's still weird if an event happens after the Cont and before
+// the WaitStop that the handlers say to continue from. Or we could
+// handle them on a separate thread. Then obviously you get weird
+// asynchrony things, like prints while the user it typing a command,
+// but that's not necessarily a bad thing.
+
+// ContWait resumes process execution and waits for an event to occur
+// that stops the process.
+func (p *Process) ContWait() os.Error {
+ for {
+ a, err := p.processEvents();
+ if err != nil {
+ return err;
+ } else if a == EAStop {
+ break;
+ }
+ err = p.proc.Continue();
+ if err != nil {
+ return err;
+ }
+ err = p.proc.WaitStop();
+ if err != nil {
+ return err;
+ }
+ for _, g := range p.goroutines {
+ g.resetFrame();
+ }
+ p.pending, err = p.causesToEvents();
+ if err != nil {
+ return err;
+ }
+ }
+ return nil;
+}
+
+// Out selects the caller frame of the current frame.
+func (p *Process) Out() os.Error {
+ if p.curGoroutine == nil {
+ return NoCurrentGoroutine{};
+ }
+ return p.curGoroutine.Out();
+}
+
+// In selects the frame called by the current frame.
+func (p *Process) In() os.Error {
+ if p.curGoroutine == nil {
+ return NoCurrentGoroutine{};
+ }
+ return p.curGoroutine.In();
+}