| // 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/gosym" |
| "debug/proc" |
| "fmt" |
| "os" |
| ) |
| |
| // A Frame represents a single frame on a remote call stack. |
| type Frame struct { |
| // pc is the PC of the next instruction that will execute in |
| // this frame. For lower frames, this is the instruction |
| // following the CALL instruction. |
| pc, sp, fp proc.Word |
| // The runtime.Stktop of the active stack segment |
| stk remoteStruct |
| // The function this stack frame is in |
| fn *gosym.Func |
| // The path and line of the CALL or current instruction. Note |
| // that this differs slightly from the meaning of Frame.pc. |
| path string |
| line int |
| // The inner and outer frames of this frame. outer is filled |
| // in lazily. |
| inner, outer *Frame |
| } |
| |
| // newFrame returns the top-most Frame of the given g's thread. |
| func newFrame(g remoteStruct) (*Frame, os.Error) { |
| var f *Frame |
| err := try(func(a aborter) { f = aNewFrame(a, g) }) |
| return f, err |
| } |
| |
| func aNewFrame(a aborter, g remoteStruct) *Frame { |
| p := g.r.p |
| var pc, sp proc.Word |
| |
| // Is this G alive? |
| switch g.field(p.f.G.Status).(remoteInt).aGet(a) { |
| case p.runtime.Gidle, p.runtime.Gmoribund, p.runtime.Gdead: |
| return nil |
| } |
| |
| // Find the OS thread for this G |
| |
| // TODO(austin) Ideally, we could look at the G's state and |
| // 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.proc.Threads() { |
| regs, err := t.Regs() |
| if err != nil { |
| // TODO(austin) What to do? |
| continue |
| } |
| thisg := p.G(regs) |
| if thisg == g.addr().base { |
| // Found this G's OS thread |
| pc = regs.PC() |
| sp = regs.SP() |
| |
| // If this thread crashed, try to recover it |
| if pc == 0 { |
| pc = p.peekUintptr(a, pc) |
| sp += 8 |
| } |
| |
| break |
| } |
| } |
| |
| if pc == 0 && sp == 0 { |
| // G is not mapped to an OS thread. Use the |
| // scheduler's stored PC and SP. |
| sched := g.field(p.f.G.Sched).(remoteStruct) |
| pc = proc.Word(sched.field(p.f.Gobuf.Pc).(remoteUint).aGet(a)) |
| sp = proc.Word(sched.field(p.f.Gobuf.Sp).(remoteUint).aGet(a)) |
| } |
| |
| // Get Stktop |
| stk := g.field(p.f.G.Stackbase).(remotePtr).aGet(a).(remoteStruct) |
| |
| return prepareFrame(a, pc, sp, stk, nil) |
| } |
| |
| // prepareFrame creates a Frame from the PC and SP within that frame, |
| // as well as the active stack segment. This function takes care of |
| // traversing stack breaks and unwinding closures. |
| func prepareFrame(a aborter, pc, sp proc.Word, stk remoteStruct, inner *Frame) *Frame { |
| // Based on src/pkg/runtime/amd64/traceback.c:traceback |
| p := stk.r.p |
| top := inner == nil |
| |
| // Get function |
| var path string |
| var line int |
| var fn *gosym.Func |
| |
| for i := 0; i < 100; i++ { |
| // Traverse segmented stack breaks |
| if p.sys.lessstack != nil && pc == proc.Word(p.sys.lessstack.Value) { |
| // Get stk->gobuf.pc |
| pc = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Pc).(remoteUint).aGet(a)) |
| // Get stk->gobuf.sp |
| sp = proc.Word(stk.field(p.f.Stktop.Gobuf).(remoteStruct).field(p.f.Gobuf.Sp).(remoteUint).aGet(a)) |
| // Get stk->stackbase |
| stk = stk.field(p.f.Stktop.Stackbase).(remotePtr).aGet(a).(remoteStruct) |
| continue |
| } |
| |
| // Get the PC of the call instruction |
| callpc := pc |
| if !top && (p.sys.goexit == nil || pc != proc.Word(p.sys.goexit.Value)) { |
| callpc-- |
| } |
| |
| // Look up function |
| path, line, fn = p.syms.PCToLine(uint64(callpc)) |
| if fn != nil { |
| break |
| } |
| |
| // Closure? |
| var buf = make([]byte, p.ClosureSize()) |
| if _, err := p.Peek(pc, buf); err != nil { |
| break |
| } |
| spdelta, ok := p.ParseClosure(buf) |
| if ok { |
| sp += proc.Word(spdelta) |
| pc = p.peekUintptr(a, sp-proc.Word(p.PtrSize())) |
| } |
| } |
| if fn == nil { |
| return nil |
| } |
| |
| // Compute frame pointer |
| var fp proc.Word |
| if fn.FrameSize < p.PtrSize() { |
| fp = sp + proc.Word(p.PtrSize()) |
| } else { |
| fp = sp + proc.Word(fn.FrameSize) |
| } |
| // TODO(austin) To really figure out if we're in the prologue, |
| // we need to disassemble the function and look for the call |
| // to morestack. For now, just special case the entry point. |
| // |
| // TODO(austin) What if we're in the call to morestack in the |
| // prologue? Then top == false. |
| if top && pc == proc.Word(fn.Entry) { |
| // We're in the function prologue, before SP |
| // has been adjusted for the frame. |
| fp -= proc.Word(fn.FrameSize - p.PtrSize()) |
| } |
| |
| return &Frame{pc, sp, fp, stk, fn, path, line, inner, nil} |
| } |
| |
| // Outer returns the Frame that called this Frame, or nil if this is |
| // the outermost frame. |
| func (f *Frame) Outer() (*Frame, os.Error) { |
| var fr *Frame |
| err := try(func(a aborter) { fr = f.aOuter(a) }) |
| return fr, err |
| } |
| |
| func (f *Frame) aOuter(a aborter) *Frame { |
| // Is there a cached outer frame |
| if f.outer != nil { |
| return f.outer |
| } |
| |
| p := f.stk.r.p |
| |
| sp := f.fp |
| if f.fn == p.sys.newproc && f.fn == p.sys.deferproc { |
| // TODO(rsc) The compiler inserts two push/pop's |
| // around calls to go and defer. Russ says this |
| // should get fixed in the compiler, but we account |
| // for it for now. |
| sp += proc.Word(2 * p.PtrSize()) |
| } |
| |
| pc := p.peekUintptr(a, f.fp-proc.Word(p.PtrSize())) |
| if pc < 0x1000 { |
| return nil |
| } |
| |
| // TODO(austin) Register this frame for shoot-down. |
| |
| f.outer = prepareFrame(a, pc, sp, f.stk, f) |
| return f.outer |
| } |
| |
| // Inner returns the Frame called by this Frame, or nil if this is the |
| // innermost frame. |
| func (f *Frame) Inner() *Frame { return f.inner } |
| |
| func (f *Frame) String() string { |
| res := f.fn.Name |
| if f.pc > proc.Word(f.fn.Value) { |
| res += fmt.Sprintf("+%#x", f.pc-proc.Word(f.fn.Entry)) |
| } |
| return res + fmt.Sprintf(" %s:%d", f.path, f.line) |
| } |