blob: 58e830aa68bfcd9595ebed406db3b46e670665fa [file] [log] [blame]
// 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:])
if sym == nil {
continue
}
rtv.Field(i).(*reflect.UintValue).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) {
p.posted = append(p.posted, ev)
}
// 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:]
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:]
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.Panicf("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
// asynchronous 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()
}