diff options
Diffstat (limited to 'src/pkg/exp/ogle/event.go')
-rw-r--r-- | src/pkg/exp/ogle/event.go | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/pkg/exp/ogle/event.go b/src/pkg/exp/ogle/event.go new file mode 100644 index 000000000..9dc7a8445 --- /dev/null +++ b/src/pkg/exp/ogle/event.go @@ -0,0 +1,294 @@ +// 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); +} |