mirror of
https://github.com/golang/go
synced 2024-11-23 01:50:04 -07:00
05522cd51e
R=rsc APPROVED=rsc DELTA=328 (112 added, 110 deleted, 106 changed) OCL=34356 CL=34356
295 lines
6.9 KiB
Go
295 lines
6.9 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 (
|
|
"fmt";
|
|
"os";
|
|
"ptrace";
|
|
)
|
|
|
|
/*
|
|
* 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 ptrace.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 ptrace.Thread;
|
|
pc ptrace.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() ptrace.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);
|
|
}
|