blob: d7092ded336db99ff9f58dec39addb53f3414f6a [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/proc"
"fmt"
"os"
)
/*
* Hooks and events
*/
// An EventHandler is a function that takes an event and returns a
// response to that event and possibly an error. If an event handler
// returns an error, the process stops and no other handlers for that
// event are executed.
type EventHandler func(e Event) (EventAction, os.Error)
// An EventAction is an event handler's response to an event. If all
// of an event's handlers execute without returning errors, their
// results are combined as follows: If any handler returned
// EAContinue, then the process resumes (without returning from
// WaitStop); otherwise, if any handler returned EAStop, the process
// remains stopped; otherwise, if all handlers returned EADefault, the
// process resumes. A handler may return EARemoveSelf bit-wise or'd
// with any other action to indicate that the handler should be
// removed from the hook.
type EventAction int
const (
EARemoveSelf EventAction = 0x100
EADefault EventAction = iota
EAStop
EAContinue
)
// A EventHook allows event handlers to be added and removed.
type EventHook interface {
AddHandler(EventHandler)
RemoveHandler(EventHandler)
NumHandler() int
handle(e Event) (EventAction, os.Error)
String() string
}
// EventHook is almost, but not quite, suitable for user-defined
// events. If we want user-defined events, make EventHook a struct,
// special-case adding and removing handlers in breakpoint hooks, and
// provide a public interface for posting events to hooks.
type Event interface {
Process() *Process
Goroutine() *Goroutine
String() string
}
type commonHook struct {
// Head of handler chain
head *handler
// Number of non-internal handlers
len int
}
type handler struct {
eh EventHandler
// True if this handler must be run before user-defined
// handlers in order to ensure correctness.
internal bool
// True if this handler has been removed from the chain.
removed bool
next *handler
}
func (h *commonHook) AddHandler(eh EventHandler) {
h.addHandler(eh, false)
}
func (h *commonHook) addHandler(eh EventHandler, internal bool) {
// Ensure uniqueness of handlers
h.RemoveHandler(eh)
if !internal {
h.len++
}
// Add internal handlers to the beginning
if internal || h.head == nil {
h.head = &handler{eh, internal, false, h.head}
return
}
// Add handler after internal handlers
// TODO(austin) This should probably go on the end instead
prev := h.head
for prev.next != nil && prev.internal {
prev = prev.next
}
prev.next = &handler{eh, internal, false, prev.next}
}
func (h *commonHook) RemoveHandler(eh EventHandler) {
plink := &h.head
for l := *plink; l != nil; plink, l = &l.next, l.next {
if l.eh == eh {
if !l.internal {
h.len--
}
l.removed = true
*plink = l.next
break
}
}
}
func (h *commonHook) NumHandler() int { return h.len }
func (h *commonHook) handle(e Event) (EventAction, os.Error) {
action := EADefault
plink := &h.head
for l := *plink; l != nil; plink, l = &l.next, l.next {
if l.removed {
continue
}
a, err := l.eh(e)
if a&EARemoveSelf == EARemoveSelf {
if !l.internal {
h.len--
}
l.removed = true
*plink = l.next
a &^= EARemoveSelf
}
if err != nil {
return EAStop, err
}
if a > action {
action = a
}
}
return action, nil
}
type commonEvent struct {
// The process of this event
p *Process
// The goroutine of this event.
t *Goroutine
}
func (e *commonEvent) Process() *Process { return e.p }
func (e *commonEvent) Goroutine() *Goroutine { return e.t }
/*
* Standard event handlers
*/
// EventPrint is a standard event handler that prints events as they
// occur. It will not cause the process to stop.
func EventPrint(ev Event) (EventAction, os.Error) {
// TODO(austin) Include process name here?
fmt.Fprintf(os.Stderr, "*** %v\n", ev.String())
return EADefault, nil
}
// EventStop is a standard event handler that causes the process to stop.
func EventStop(ev Event) (EventAction, os.Error) {
return EAStop, nil
}
/*
* Breakpoints
*/
type breakpointHook struct {
commonHook
p *Process
pc proc.Word
}
// A Breakpoint event occurs when a process reaches a particular
// program counter. When this event is handled, the current goroutine
// will be the goroutine that reached the program counter.
type Breakpoint struct {
commonEvent
osThread proc.Thread
pc proc.Word
}
func (h *breakpointHook) AddHandler(eh EventHandler) {
h.addHandler(eh, false)
}
func (h *breakpointHook) addHandler(eh EventHandler, internal bool) {
// We register breakpoint events lazily to avoid holding
// references to breakpoints without handlers. Be sure to use
// the "canonical" breakpoint if there is one.
if cur, ok := h.p.breakpointHooks[h.pc]; ok {
h = cur
}
oldhead := h.head
h.commonHook.addHandler(eh, internal)
if oldhead == nil && h.head != nil {
h.p.proc.AddBreakpoint(h.pc)
h.p.breakpointHooks[h.pc] = h
}
}
func (h *breakpointHook) RemoveHandler(eh EventHandler) {
oldhead := h.head
h.commonHook.RemoveHandler(eh)
if oldhead != nil && h.head == nil {
h.p.proc.RemoveBreakpoint(h.pc)
h.p.breakpointHooks[h.pc] = nil, false
}
}
func (h *breakpointHook) String() string {
// TODO(austin) Include process name?
// TODO(austin) Use line:pc or at least sym+%#x
return fmt.Sprintf("breakpoint at %#x", h.pc)
}
func (b *Breakpoint) PC() proc.Word { return b.pc }
func (b *Breakpoint) String() string {
// TODO(austin) Include process name and goroutine
// TODO(austin) Use line:pc or at least sym+%#x
return fmt.Sprintf("breakpoint at %#x", b.pc)
}
/*
* Goroutine create/exit
*/
type goroutineCreateHook struct {
commonHook
}
func (h *goroutineCreateHook) String() string { return "goroutine create" }
// A GoroutineCreate event occurs when a process creates a new
// goroutine. When this event is handled, the current goroutine will
// be the newly created goroutine.
type GoroutineCreate struct {
commonEvent
parent *Goroutine
}
// Parent returns the goroutine that created this goroutine. May be
// nil if this event is the creation of the first goroutine.
func (e *GoroutineCreate) Parent() *Goroutine { return e.parent }
func (e *GoroutineCreate) String() string {
// TODO(austin) Include process name
if e.parent == nil {
return fmt.Sprintf("%v created", e.t)
}
return fmt.Sprintf("%v created by %v", e.t, e.parent)
}
type goroutineExitHook struct {
commonHook
}
func (h *goroutineExitHook) String() string { return "goroutine exit" }
// A GoroutineExit event occurs when a Go goroutine exits.
type GoroutineExit struct {
commonEvent
}
func (e *GoroutineExit) String() string {
// TODO(austin) Include process name
//return fmt.Sprintf("%v exited", e.t);
// For debugging purposes
return fmt.Sprintf("goroutine %#x exited", e.t.g.addr().base)
}