diff options
Diffstat (limited to 'usr/austin')
-rw-r--r-- | usr/austin/ogle/event.go | 294 | ||||
-rw-r--r-- | usr/austin/ogle/frame.go | 4 | ||||
-rw-r--r-- | usr/austin/ogle/process.go | 347 | ||||
-rw-r--r-- | usr/austin/ogle/rruntime.go | 9 | ||||
-rw-r--r-- | usr/austin/ogle/rtype.go | 1 | ||||
-rw-r--r-- | usr/austin/ogle/thread.go | 114 |
6 files changed, 750 insertions, 19 deletions
diff --git a/usr/austin/ogle/event.go b/usr/austin/ogle/event.go new file mode 100644 index 000000000..dee1ba58e --- /dev/null +++ b/usr/austin/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 ( + "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); +} diff --git a/usr/austin/ogle/frame.go b/usr/austin/ogle/frame.go index 4a4fd9a43..522c263b1 100644 --- a/usr/austin/ogle/frame.go +++ b/usr/austin/ogle/frame.go @@ -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; } diff --git a/usr/austin/ogle/process.go b/usr/austin/ogle/process.go index 9dc5bc909..f1e7524b4 100644 --- a/usr/austin/ogle/process.go +++ b/usr/austin/ogle/process.go @@ -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) 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; + } + } } -func (p *Process) someStoppedThread() ptrace.Thread { +/* + * 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(); +} diff --git a/usr/austin/ogle/rruntime.go b/usr/austin/ogle/rruntime.go index e0a654691..758f1c708 100644 --- a/usr/austin/ogle/rruntime.go +++ b/usr/austin/ogle/rruntime.go @@ -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; }; } diff --git a/usr/austin/ogle/rtype.go b/usr/austin/ogle/rtype.go index 856594936..5bca923ce 100644 --- a/usr/austin/ogle/rtype.go +++ b/usr/austin/ogle/rtype.go @@ -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()); diff --git a/usr/austin/ogle/thread.go b/usr/austin/ogle/thread.go new file mode 100644 index 000000000..888d01eff --- /dev/null +++ b/usr/austin/ogle/thread.go @@ -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; +} |