// 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) }