mirror of
https://github.com/golang/go
synced 2024-09-25 09:20:18 -06:00
Implement Go threads. Implement a general event system
including breakpoints and Go thread create/exit. R=rsc APPROVED=rsc DELTA=751 (729 added, 6 deleted, 16 changed) OCL=34345 CL=34351
This commit is contained in:
parent
55ba20ec2d
commit
db27b5bcb1
294
usr/austin/ogle/event.go
Normal file
294
usr/austin/ogle/event.go
Normal file
@ -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 (
|
||||
"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;
|
||||
Thread() *Thread;
|
||||
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 thread of this event.
|
||||
t *Thread;
|
||||
}
|
||||
|
||||
func (e *commonEvent) Process() *Process {
|
||||
return e.p;
|
||||
}
|
||||
|
||||
func (e *commonEvent) Thread() *Thread {
|
||||
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 thread
|
||||
// will be the thread 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 thread
|
||||
// TODO(austin) Use line:pc or at least sym+%#x
|
||||
return fmt.Sprintf("breakpoint at %#x", b.pc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Thread create/exit
|
||||
*/
|
||||
|
||||
type threadCreateHook struct {
|
||||
commonHook;
|
||||
}
|
||||
|
||||
func (h *threadCreateHook) String() string {
|
||||
return "thread create";
|
||||
}
|
||||
|
||||
// A ThreadCreate event occurs when a process creates a new Go thread.
|
||||
// When this event is handled, the current thread will be the newly
|
||||
// created thread.
|
||||
type ThreadCreate struct {
|
||||
commonEvent;
|
||||
parent *Thread;
|
||||
}
|
||||
|
||||
// Parent returns the thread that created this thread. May be nil if
|
||||
// this event is the creation of the first thread.
|
||||
func (e *ThreadCreate) Parent() *Thread {
|
||||
return e.parent;
|
||||
}
|
||||
|
||||
func (e *ThreadCreate) 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 threadExitHook struct {
|
||||
commonHook;
|
||||
}
|
||||
|
||||
func (h *threadExitHook) String() string {
|
||||
return "thread exit";
|
||||
}
|
||||
|
||||
// A ThreadExit event occurs when a Go thread exits.
|
||||
type ThreadExit struct {
|
||||
commonEvent;
|
||||
}
|
||||
|
||||
func (e *ThreadExit) String() string {
|
||||
// TODO(austin) Include process name
|
||||
//return fmt.Sprintf("%v exited", e.t);
|
||||
// For debugging purposes
|
||||
return fmt.Sprintf("thread %#x exited", e.t.g.addr().base);
|
||||
}
|
@ -47,7 +47,7 @@ func NewFrame(g remoteStruct) *Frame {
|
||||
// figure out if it's on an OS thread or not. However, this
|
||||
// is difficult because the state isn't updated atomically
|
||||
// with scheduling changes.
|
||||
for _, t := range p.Threads() {
|
||||
for _, t := range p.proc.Threads() {
|
||||
regs, err := t.Regs();
|
||||
if err != nil {
|
||||
// TODO(austin) What to do?
|
||||
@ -182,6 +182,8 @@ func (f *Frame) Outer() *Frame {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// TODO(austin) Register this frame for shoot-down.
|
||||
|
||||
f.outer = prepareFrame(pc, sp, f.stk, f);
|
||||
return f.outer;
|
||||
}
|
||||
|
@ -6,9 +6,11 @@ package ogle
|
||||
|
||||
import (
|
||||
"eval";
|
||||
"fmt";
|
||||
"log";
|
||||
"os";
|
||||
"ptrace";
|
||||
"reflect";
|
||||
"os";
|
||||
"sym";
|
||||
)
|
||||
|
||||
@ -37,17 +39,33 @@ func (e ProcessNotStopped) String() string {
|
||||
return "process not stopped";
|
||||
}
|
||||
|
||||
// An UnknownThread error is an internal error representing an
|
||||
// unrecognized G structure pointer.
|
||||
type UnknownThread struct {
|
||||
OSThread ptrace.Thread;
|
||||
GoThread ptrace.Word;
|
||||
}
|
||||
|
||||
func (e UnknownThread) String() string {
|
||||
return fmt.Sprintf("internal error: unknown thread (G %#x)", e.GoThread);
|
||||
}
|
||||
|
||||
// A NoCurrentThread error occurs when no thread is currently selected
|
||||
// in a process (or when there are no threads in a process).
|
||||
type NoCurrentThread struct {}
|
||||
|
||||
func (e NoCurrentThread) String() string {
|
||||
return "no current thread";
|
||||
}
|
||||
|
||||
// A Process represents a remote attached process.
|
||||
type Process struct {
|
||||
Arch;
|
||||
ptrace.Process;
|
||||
proc ptrace.Process;
|
||||
|
||||
// The symbol table of this process
|
||||
syms *sym.GoSymTable;
|
||||
|
||||
// Current frame, or nil if the current thread is not stopped
|
||||
frame *Frame;
|
||||
|
||||
// A possibly-stopped OS thread, or nil
|
||||
threadCache ptrace.Thread;
|
||||
|
||||
@ -62,24 +80,76 @@ type Process struct {
|
||||
|
||||
// Globals from the sys package (or from no package)
|
||||
sys struct {
|
||||
lessstack, goexit, newproc, deferproc *sym.TextSym;
|
||||
lessstack, goexit, newproc, deferproc, newprocreadylocked *sym.TextSym;
|
||||
allg remotePtr;
|
||||
g0 remoteStruct;
|
||||
};
|
||||
|
||||
// Event queue
|
||||
posted []Event;
|
||||
pending []Event;
|
||||
event Event;
|
||||
|
||||
// Event hooks
|
||||
breakpointHooks map[ptrace.Word] *breakpointHook;
|
||||
threadCreateHook *threadCreateHook;
|
||||
threadExitHook *threadExitHook;
|
||||
|
||||
// Current thread, or nil if there are no threads
|
||||
curThread *Thread;
|
||||
|
||||
// Threads by the address of their G structure
|
||||
threads map[ptrace.Word] *Thread;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process creation
|
||||
*/
|
||||
|
||||
// NewProcess constructs a new remote process around a ptrace'd
|
||||
// process, an architecture, and a symbol table.
|
||||
func NewProcess(proc ptrace.Process, arch Arch, syms *sym.GoSymTable) *Process {
|
||||
func NewProcess(proc ptrace.Process, arch Arch, syms *sym.GoSymTable) (*Process, os.Error) {
|
||||
p := &Process{
|
||||
Arch: arch,
|
||||
Process: proc,
|
||||
proc: proc,
|
||||
syms: syms,
|
||||
types: make(map[ptrace.Word] *remoteType),
|
||||
breakpointHooks: make(map[ptrace.Word] *breakpointHook),
|
||||
threadCreateHook: new(threadCreateHook),
|
||||
threadExitHook: new(threadExitHook),
|
||||
threads: make(map[ptrace.Word] *Thread),
|
||||
};
|
||||
|
||||
// TODO(austin) Set p.frame if proc is stopped
|
||||
|
||||
// Fill in remote runtime
|
||||
p.bootstrap();
|
||||
return p;
|
||||
|
||||
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 threads
|
||||
p.threads[p.sys.g0.addr().base] = &Thread{p.sys.g0, nil, false};
|
||||
g := p.sys.allg.Get();
|
||||
for g != nil {
|
||||
gs := g.(remoteStruct);
|
||||
fmt.Printf("*** Found thread at %#x\n", gs.addr().base);
|
||||
p.threads[gs.addr().base] = &Thread{gs, nil, false};
|
||||
g = gs.Field(p.f.G.Alllink).(remotePtr).Get();
|
||||
}
|
||||
p.selectSomeThread();
|
||||
|
||||
// Create internal breakpoints to catch new and exited threads
|
||||
p.OnBreakpoint(ptrace.Word(p.sys.newprocreadylocked.Entry())).(*breakpointHook).addHandler(readylockedBP, true);
|
||||
p.OnBreakpoint(ptrace.Word(p.sys.goexit.Entry())).(*breakpointHook).addHandler(goexitBP, true);
|
||||
|
||||
return p, nil;
|
||||
}
|
||||
|
||||
// NewProcessElf constructs a new remote process around a ptrace'd
|
||||
@ -99,7 +169,7 @@ func NewProcessElf(proc ptrace.Process, elf *sym.Elf) (*Process, os.Error) {
|
||||
default:
|
||||
return nil, UnknownArchitecture(elf.Machine);
|
||||
}
|
||||
return NewProcess(proc, arch, syms), nil;
|
||||
return NewProcess(proc, arch, syms);
|
||||
}
|
||||
|
||||
// bootstrap constructs the runtime structure of a remote process.
|
||||
@ -154,16 +224,39 @@ func (p *Process) bootstrap() {
|
||||
p.sys.goexit = globalFn("goexit");
|
||||
p.sys.newproc = globalFn("sys·newproc");
|
||||
p.sys.deferproc = globalFn("sys·deferproc");
|
||||
p.sys.newprocreadylocked = globalFn("newprocreadylocked");
|
||||
if allg := p.syms.SymFromName("allg"); allg != nil {
|
||||
p.sys.allg = remotePtr{remote{ptrace.Word(allg.Common().Value), p}, p.runtime.G};
|
||||
}
|
||||
if g0 := p.syms.SymFromName("g0"); g0 != nil {
|
||||
p.sys.g0 = p.runtime.G.mk(remote{ptrace.Word(g0.Common().Value), p}).(remoteStruct);
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Process) someStoppedThread() ptrace.Thread {
|
||||
func (p *Process) selectSomeThread() {
|
||||
// Once we have friendly thread ID's, there might be a more
|
||||
// reasonable behavior for this.
|
||||
p.curThread = nil;
|
||||
for _, t := range p.threads {
|
||||
if !t.isG0() {
|
||||
p.curThread = t;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process memory
|
||||
*/
|
||||
|
||||
func (p *Process) someStoppedOSThread() ptrace.Thread {
|
||||
if p.threadCache != nil {
|
||||
if _, err := p.threadCache.Stopped(); err == nil {
|
||||
return p.threadCache;
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range p.Threads() {
|
||||
for _, t := range p.proc.Threads() {
|
||||
if _, err := t.Stopped(); err == nil {
|
||||
p.threadCache = t;
|
||||
return t;
|
||||
@ -173,7 +266,7 @@ func (p *Process) someStoppedThread() ptrace.Thread {
|
||||
}
|
||||
|
||||
func (p *Process) Peek(addr ptrace.Word, out []byte) (int, os.Error) {
|
||||
thr := p.someStoppedThread();
|
||||
thr := p.someStoppedOSThread();
|
||||
if thr == nil {
|
||||
return 0, ProcessNotStopped{};
|
||||
}
|
||||
@ -181,7 +274,7 @@ func (p *Process) Peek(addr ptrace.Word, out []byte) (int, os.Error) {
|
||||
}
|
||||
|
||||
func (p *Process) Poke(addr ptrace.Word, b []byte) (int, os.Error) {
|
||||
thr := p.someStoppedThread();
|
||||
thr := p.someStoppedOSThread();
|
||||
if thr == nil {
|
||||
return 0, ProcessNotStopped{};
|
||||
}
|
||||
@ -191,3 +284,225 @@ func (p *Process) Poke(addr ptrace.Word, b []byte) (int, os.Error) {
|
||||
func (p *Process) peekUintptr(addr ptrace.Word) ptrace.Word {
|
||||
return ptrace.Word(mkUintptr(remote{addr, p}).(remoteUint).Get());
|
||||
}
|
||||
|
||||
/*
|
||||
* Events
|
||||
*/
|
||||
|
||||
// OnBreakpoint returns the hook that is run when the program reaches
|
||||
// the given program counter.
|
||||
func (p *Process) OnBreakpoint(pc ptrace.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};
|
||||
}
|
||||
|
||||
// OnThreadCreate returns the hook that is run when a Go thread is created.
|
||||
func (p *Process) OnThreadCreate() EventHook {
|
||||
return p.threadCreateHook;
|
||||
}
|
||||
|
||||
// OnThreadExit returns the hook
|
||||
func (p *Process) OnThreadExit() EventHook {
|
||||
return p.threadExitHook;
|
||||
}
|
||||
|
||||
// osThreadToThread looks up the Go thread running on an OS thread.
|
||||
func (p *Process) osThreadToThread(t ptrace.Thread) (*Thread, os.Error) {
|
||||
regs, err := t.Regs();
|
||||
if err != nil {
|
||||
return nil, err;
|
||||
}
|
||||
g := p.G(regs);
|
||||
gt, ok := p.threads[g];
|
||||
if !ok {
|
||||
return nil, UnknownThread{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 ptrace.Breakpoint:
|
||||
nev++;
|
||||
case ptrace.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 ptrace.Breakpoint:
|
||||
gt, err := p.osThreadToThread(t);
|
||||
if err != nil {
|
||||
return nil, err;
|
||||
}
|
||||
events[i] = &Breakpoint{commonEvent{p, gt}, t, ptrace.Word(c)};
|
||||
i++;
|
||||
case ptrace.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) {
|
||||
n := len(p.posted);
|
||||
m := n*2;
|
||||
if m == 0 {
|
||||
m = 4;
|
||||
}
|
||||
posted := make([]Event, n+1, m);
|
||||
for i, p := range p.posted {
|
||||
posted[i] = p;
|
||||
}
|
||||
posted[n] = ev;
|
||||
p.posted = posted;
|
||||
}
|
||||
|
||||
// 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:len(p.posted)];
|
||||
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:len(p.pending)];
|
||||
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.curThread = ev.Thread();
|
||||
action, err = hook.handle(ev);
|
||||
|
||||
case *ThreadCreate:
|
||||
p.curThread = ev.Thread();
|
||||
action, err = p.threadCreateHook.handle(ev);
|
||||
|
||||
case *ThreadExit:
|
||||
action, err = p.threadExitHook.handle(ev);
|
||||
|
||||
default:
|
||||
log.Crashf("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
|
||||
// asynchrony 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 _, t := range p.threads {
|
||||
t.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.curThread == nil {
|
||||
return NoCurrentThread{};
|
||||
}
|
||||
return p.curThread.Out();
|
||||
}
|
||||
|
||||
// In selects the frame called by the current frame.
|
||||
func (p *Process) In() os.Error {
|
||||
if p.curThread == nil {
|
||||
return NoCurrentThread{};
|
||||
}
|
||||
return p.curThread.In();
|
||||
}
|
||||
|
@ -110,20 +110,24 @@ type rt1ArrayType struct {
|
||||
* See $GOROOT/src/pkg/runtime/runtime.h
|
||||
*/
|
||||
|
||||
// Fields beginning with _ are only for padding
|
||||
|
||||
type rt1Stktop struct {
|
||||
stackguard uintptr;
|
||||
stackbase *rt1Stktop;
|
||||
gobuf rt1Gobuf;
|
||||
_args uint32;
|
||||
_fp uintptr;
|
||||
}
|
||||
|
||||
type rt1Gobuf struct {
|
||||
sp uintptr;
|
||||
pc uintptr;
|
||||
g *rt1G;
|
||||
r0 uintptr;
|
||||
}
|
||||
|
||||
type rt1G struct {
|
||||
// Fields beginning with _ are only for padding
|
||||
_stackguard uintptr;
|
||||
stackbase *rt1Stktop;
|
||||
_defer uintptr;
|
||||
@ -133,6 +137,7 @@ type rt1G struct {
|
||||
alllink *rt1G;
|
||||
_param uintptr;
|
||||
status int16;
|
||||
// Incomplete
|
||||
}
|
||||
|
||||
var rt1GStatus = runtimeGStatus{
|
||||
@ -193,7 +198,7 @@ type runtimeIndexes struct {
|
||||
Sp, Pc, G int;
|
||||
};
|
||||
G struct {
|
||||
Stackbase, Sched, Status int;
|
||||
Stackbase, Sched, Status, Alllink int;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ func newManualType(t eval.Type, arch Arch) *remoteType {
|
||||
basicType(eval.Uint8Type, mkUint8, 1, 0);
|
||||
basicType(eval.Uint32Type, mkUint32, 4, 0);
|
||||
basicType(eval.UintptrType, mkUintptr, arch.PtrSize(), 0);
|
||||
basicType(eval.Int16Type, mkInt16, 2, 0);
|
||||
basicType(eval.Int32Type, mkInt32, 4, 0);
|
||||
basicType(eval.IntType, mkInt, arch.IntSize(), 0);
|
||||
basicType(eval.StringType, mkString, arch.PtrSize() + arch.IntSize(), arch.PtrSize());
|
||||
|
114
usr/austin/ogle/thread.go
Normal file
114
usr/austin/ogle/thread.go
Normal file
@ -0,0 +1,114 @@
|
||||
// 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";
|
||||
)
|
||||
|
||||
// A Thread represents a Go thread.
|
||||
type Thread struct {
|
||||
g remoteStruct;
|
||||
frame *Frame;
|
||||
dead bool;
|
||||
}
|
||||
|
||||
func (t *Thread) String() string {
|
||||
if t.dead {
|
||||
return "<dead thread>";
|
||||
}
|
||||
// TODO(austin) Give threads friendly ID's
|
||||
return fmt.Sprintf("thread %#x", t.g.addr().base);
|
||||
}
|
||||
|
||||
// isG0 returns true if this thread if the internal idle thread
|
||||
func (t *Thread) isG0() bool {
|
||||
return t.g.addr().base == t.g.r.p.sys.g0.addr().base;
|
||||
}
|
||||
|
||||
func (t *Thread) resetFrame() {
|
||||
// TODO(austin) NewFrame can abort
|
||||
// TODO(austin) Reuse any live part of the current frame stack
|
||||
// so existing references to Frame's keep working.
|
||||
t.frame = NewFrame(t.g);
|
||||
}
|
||||
|
||||
// Out selects the caller frame of the current frame.
|
||||
func (t *Thread) Out() os.Error {
|
||||
// TODO(austin) Outer can abort
|
||||
f := t.frame.Outer();
|
||||
if f != nil {
|
||||
t.frame = f;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// In selects the frame called by the current frame.
|
||||
func (t *Thread) In() os.Error {
|
||||
f := t.frame.Inner();
|
||||
if f != nil {
|
||||
t.frame = f;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
func readylockedBP(ev Event) (EventAction, os.Error) {
|
||||
b := ev.(*Breakpoint);
|
||||
p := b.Process();
|
||||
|
||||
// The new g is the only argument to this function, so the
|
||||
// stack will have the return address, then the G*.
|
||||
regs, err := b.osThread.Regs();
|
||||
if err != nil {
|
||||
return EAStop, err;
|
||||
}
|
||||
sp := regs.SP();
|
||||
addr := sp + ptrace.Word(p.PtrSize());
|
||||
arg := remotePtr{remote{addr, p}, p.runtime.G};
|
||||
g := arg.Get();
|
||||
if g == nil {
|
||||
return EAStop, UnknownThread{b.osThread, 0};
|
||||
}
|
||||
gs := g.(remoteStruct);
|
||||
t := &Thread{gs, nil, false};
|
||||
p.threads[gs.addr().base] = t;
|
||||
|
||||
// Enqueue thread creation event
|
||||
parent := b.Thread();
|
||||
if parent.isG0() {
|
||||
parent = nil;
|
||||
}
|
||||
p.postEvent(&ThreadCreate{commonEvent{p, t}, parent});
|
||||
|
||||
// If we don't have any thread selected, select this one
|
||||
if p.curThread == nil {
|
||||
p.curThread = t;
|
||||
}
|
||||
|
||||
return EADefault, nil;
|
||||
}
|
||||
|
||||
func goexitBP(ev Event) (EventAction, os.Error) {
|
||||
b := ev.(*Breakpoint);
|
||||
p := b.Process();
|
||||
|
||||
t := b.Thread();
|
||||
t.dead = true;
|
||||
|
||||
addr := t.g.addr().base;
|
||||
p.threads[addr] = nil, false;
|
||||
|
||||
// Enqueue thread exit event
|
||||
p.postEvent(&ThreadExit{commonEvent{p, t}});
|
||||
|
||||
// If we just exited our selected thread, selected another
|
||||
if p.curThread == t {
|
||||
p.selectSomeThread();
|
||||
}
|
||||
|
||||
return EADefault, nil;
|
||||
}
|
Loading…
Reference in New Issue
Block a user