1
0
mirror of https://github.com/golang/go synced 2024-11-22 21:00:04 -07:00
go/usr/austin/ogle/process.go
Austin Clements 4211384976 Switch ogle to in-tree gosym package. Delete my private sym
package.  If a Sym is a function symbol, include a reference
to the Func so it's easily accessible when you're traversing
the list of all symbols.  This diff is more interesting than
the proc switch because the gosym interface differs from the
old sym interface.

R=rsc
APPROVED=rsc
DELTA=1957  (34 added, 1868 deleted, 55 changed)
OCL=34969
CL=35008
2009-09-25 09:39:08 -07:00

542 lines
14 KiB
Go

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ogle
import (
"debug/elf";
"debug/gosym";
"debug/proc";
"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();
}