| // 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/elf" | |
| "debug/gosym" | |
| "debug/proc" | |
| "exp/eval" | |
| "fmt" | |
| "log" | |
| "os" | |
| "reflect" | |
| ) | |
| // A FormatError indicates a failure to process information in or | |
| // about a remote process, such as unexpected or missing information | |
| // in the object file or runtime structures. | |
| type FormatError string | |
| func (e FormatError) String() string { return string(e) } | |
| // An UnknownArchitecture occurs when trying to load an object file | |
| // that indicates an architecture not supported by the debugger. | |
| type UnknownArchitecture elf.Machine | |
| func (e UnknownArchitecture) String() string { | |
| return "unknown architecture: " + elf.Machine(e).String() | |
| } | |
| // A ProcessNotStopped error occurs when attempting to read or write | |
| // memory or registers of a process that is not stopped. | |
| type ProcessNotStopped struct{} | |
| func (e ProcessNotStopped) String() string { return "process not stopped" } | |
| // An UnknownGoroutine error is an internal error representing an | |
| // unrecognized G structure pointer. | |
| type UnknownGoroutine struct { | |
| OSThread proc.Thread | |
| Goroutine proc.Word | |
| } | |
| func (e UnknownGoroutine) String() string { | |
| return fmt.Sprintf("internal error: unknown goroutine (G %#x)", e.Goroutine) | |
| } | |
| // A NoCurrentGoroutine error occurs when no goroutine is currently | |
| // selected in a process (or when there are no goroutines in a | |
| // process). | |
| type NoCurrentGoroutine struct{} | |
| func (e NoCurrentGoroutine) String() string { return "no current goroutine" } | |
| // A Process represents a remote attached process. | |
| type Process struct { | |
| Arch | |
| proc proc.Process | |
| // The symbol table of this process | |
| syms *gosym.Table | |
| // A possibly-stopped OS thread, or nil | |
| threadCache proc.Thread | |
| // Types parsed from the remote process | |
| types map[proc.Word]*remoteType | |
| // Types and values from the remote runtime package | |
| runtime runtimeValues | |
| // Runtime field indexes | |
| f runtimeIndexes | |
| // Globals from the sys package (or from no package) | |
| sys struct { | |
| lessstack, goexit, newproc, deferproc, newprocreadylocked *gosym.Func | |
| allg remotePtr | |
| g0 remoteStruct | |
| } | |
| // Event queue | |
| posted []Event | |
| pending []Event | |
| event Event | |
| // Event hooks | |
| breakpointHooks map[proc.Word]*breakpointHook | |
| goroutineCreateHook *goroutineCreateHook | |
| goroutineExitHook *goroutineExitHook | |
| // Current goroutine, or nil if there are no goroutines | |
| curGoroutine *Goroutine | |
| // Goroutines by the address of their G structure | |
| goroutines map[proc.Word]*Goroutine | |
| } | |
| /* | |
| * Process creation | |
| */ | |
| // NewProcess constructs a new remote process around a traced | |
| // process, an architecture, and a symbol table. | |
| func NewProcess(tproc proc.Process, arch Arch, syms *gosym.Table) (*Process, os.Error) { | |
| p := &Process{ | |
| Arch: arch, | |
| proc: tproc, | |
| syms: syms, | |
| types: make(map[proc.Word]*remoteType), | |
| breakpointHooks: make(map[proc.Word]*breakpointHook), | |
| goroutineCreateHook: new(goroutineCreateHook), | |
| goroutineExitHook: new(goroutineExitHook), | |
| goroutines: make(map[proc.Word]*Goroutine), | |
| } | |
| // Fill in remote runtime | |
| p.bootstrap() | |
| 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 goroutines | |
| p.goroutines[p.sys.g0.addr().base] = &Goroutine{p.sys.g0, nil, false} | |
| err := try(func(a aborter) { | |
| g := p.sys.allg.aGet(a) | |
| for g != nil { | |
| gs := g.(remoteStruct) | |
| fmt.Printf("*** Found goroutine at %#x\n", gs.addr().base) | |
| p.goroutines[gs.addr().base] = &Goroutine{gs, nil, false} | |
| g = gs.field(p.f.G.Alllink).(remotePtr).aGet(a) | |
| } | |
| }) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Create internal breakpoints to catch new and exited goroutines | |
| p.OnBreakpoint(proc.Word(p.sys.newprocreadylocked.Entry)).(*breakpointHook).addHandler(readylockedBP, true) | |
| p.OnBreakpoint(proc.Word(p.sys.goexit.Entry)).(*breakpointHook).addHandler(goexitBP, true) | |
| // Select current frames | |
| for _, g := range p.goroutines { | |
| g.resetFrame() | |
| } | |
| p.selectSomeGoroutine() | |
| return p, nil | |
| } | |
| func elfGoSyms(f *elf.File) (*gosym.Table, os.Error) { | |
| text := f.Section(".text") | |
| symtab := f.Section(".gosymtab") | |
| pclntab := f.Section(".gopclntab") | |
| if text == nil || symtab == nil || pclntab == nil { | |
| return nil, nil | |
| } | |
| symdat, err := symtab.Data() | |
| if err != nil { | |
| return nil, err | |
| } | |
| pclndat, err := pclntab.Data() | |
| if err != nil { | |
| return nil, err | |
| } | |
| pcln := gosym.NewLineTable(pclndat, text.Addr) | |
| tab, err := gosym.NewTable(symdat, pcln) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return tab, nil | |
| } | |
| // NewProcessElf constructs a new remote process around a traced | |
| // process and the process' ELF object. | |
| func NewProcessElf(tproc proc.Process, f *elf.File) (*Process, os.Error) { | |
| syms, err := elfGoSyms(f) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if syms == nil { | |
| return nil, FormatError("Failed to find symbol table") | |
| } | |
| var arch Arch | |
| switch f.Machine { | |
| case elf.EM_X86_64: | |
| arch = Amd64 | |
| default: | |
| return nil, UnknownArchitecture(f.Machine) | |
| } | |
| return NewProcess(tproc, arch, syms) | |
| } | |
| // bootstrap constructs the runtime structure of a remote process. | |
| func (p *Process) bootstrap() { | |
| // Manually construct runtime types | |
| p.runtime.String = newManualType(eval.TypeOfNative(rt1String{}), p.Arch) | |
| p.runtime.Slice = newManualType(eval.TypeOfNative(rt1Slice{}), p.Arch) | |
| p.runtime.Eface = newManualType(eval.TypeOfNative(rt1Eface{}), p.Arch) | |
| p.runtime.Type = newManualType(eval.TypeOfNative(rt1Type{}), p.Arch) | |
| p.runtime.CommonType = newManualType(eval.TypeOfNative(rt1CommonType{}), p.Arch) | |
| p.runtime.UncommonType = newManualType(eval.TypeOfNative(rt1UncommonType{}), p.Arch) | |
| p.runtime.StructField = newManualType(eval.TypeOfNative(rt1StructField{}), p.Arch) | |
| p.runtime.StructType = newManualType(eval.TypeOfNative(rt1StructType{}), p.Arch) | |
| p.runtime.PtrType = newManualType(eval.TypeOfNative(rt1PtrType{}), p.Arch) | |
| p.runtime.ArrayType = newManualType(eval.TypeOfNative(rt1ArrayType{}), p.Arch) | |
| p.runtime.SliceType = newManualType(eval.TypeOfNative(rt1SliceType{}), p.Arch) | |
| p.runtime.Stktop = newManualType(eval.TypeOfNative(rt1Stktop{}), p.Arch) | |
| p.runtime.Gobuf = newManualType(eval.TypeOfNative(rt1Gobuf{}), p.Arch) | |
| p.runtime.G = newManualType(eval.TypeOfNative(rt1G{}), p.Arch) | |
| // Get addresses of type.*runtime.XType for discrimination. | |
| rtv := reflect.Indirect(reflect.NewValue(&p.runtime)).(*reflect.StructValue) | |
| rtvt := rtv.Type().(*reflect.StructType) | |
| for i := 0; i < rtv.NumField(); i++ { | |
| n := rtvt.Field(i).Name | |
| if n[0] != 'P' || n[1] < 'A' || n[1] > 'Z' { | |
| continue | |
| } | |
| sym := p.syms.LookupSym("type.*runtime." + n[1:]) | |
| if sym == nil { | |
| continue | |
| } | |
| rtv.Field(i).(*reflect.UintValue).Set(sym.Value) | |
| } | |
| // Get runtime field indexes | |
| fillRuntimeIndexes(&p.runtime, &p.f) | |
| // Fill G status | |
| p.runtime.runtimeGStatus = rt1GStatus | |
| // Get globals | |
| p.sys.lessstack = p.syms.LookupFunc("sys.lessstack") | |
| p.sys.goexit = p.syms.LookupFunc("goexit") | |
| p.sys.newproc = p.syms.LookupFunc("sys.newproc") | |
| p.sys.deferproc = p.syms.LookupFunc("sys.deferproc") | |
| p.sys.newprocreadylocked = p.syms.LookupFunc("newprocreadylocked") | |
| if allg := p.syms.LookupSym("allg"); allg != nil { | |
| p.sys.allg = remotePtr{remote{proc.Word(allg.Value), p}, p.runtime.G} | |
| } | |
| if g0 := p.syms.LookupSym("g0"); g0 != nil { | |
| p.sys.g0 = p.runtime.G.mk(remote{proc.Word(g0.Value), p}).(remoteStruct) | |
| } | |
| } | |
| func (p *Process) selectSomeGoroutine() { | |
| // Once we have friendly goroutine ID's, there might be a more | |
| // reasonable behavior for this. | |
| p.curGoroutine = nil | |
| for _, g := range p.goroutines { | |
| if !g.isG0() && g.frame != nil { | |
| p.curGoroutine = g | |
| return | |
| } | |
| } | |
| } | |
| /* | |
| * Process memory | |
| */ | |
| func (p *Process) someStoppedOSThread() proc.Thread { | |
| if p.threadCache != nil { | |
| if _, err := p.threadCache.Stopped(); err == nil { | |
| return p.threadCache | |
| } | |
| } | |
| for _, t := range p.proc.Threads() { | |
| if _, err := t.Stopped(); err == nil { | |
| p.threadCache = t | |
| return t | |
| } | |
| } | |
| return nil | |
| } | |
| func (p *Process) Peek(addr proc.Word, out []byte) (int, os.Error) { | |
| thr := p.someStoppedOSThread() | |
| if thr == nil { | |
| return 0, ProcessNotStopped{} | |
| } | |
| return thr.Peek(addr, out) | |
| } | |
| func (p *Process) Poke(addr proc.Word, b []byte) (int, os.Error) { | |
| thr := p.someStoppedOSThread() | |
| if thr == nil { | |
| return 0, ProcessNotStopped{} | |
| } | |
| return thr.Poke(addr, b) | |
| } | |
| func (p *Process) peekUintptr(a aborter, addr proc.Word) proc.Word { | |
| return proc.Word(mkUintptr(remote{addr, p}).(remoteUint).aGet(a)) | |
| } | |
| /* | |
| * Events | |
| */ | |
| // OnBreakpoint returns the hook that is run when the program reaches | |
| // the given program counter. | |
| func (p *Process) OnBreakpoint(pc proc.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} | |
| } | |
| // OnGoroutineCreate returns the hook that is run when a goroutine is created. | |
| func (p *Process) OnGoroutineCreate() EventHook { | |
| return p.goroutineCreateHook | |
| } | |
| // OnGoroutineExit returns the hook that is run when a goroutine exits. | |
| func (p *Process) OnGoroutineExit() EventHook { return p.goroutineExitHook } | |
| // osThreadToGoroutine looks up the goroutine running on an OS thread. | |
| func (p *Process) osThreadToGoroutine(t proc.Thread) (*Goroutine, os.Error) { | |
| regs, err := t.Regs() | |
| if err != nil { | |
| return nil, err | |
| } | |
| g := p.G(regs) | |
| gt, ok := p.goroutines[g] | |
| if !ok { | |
| return nil, UnknownGoroutine{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 proc.Breakpoint: | |
| nev++ | |
| case proc.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 proc.Breakpoint: | |
| gt, err := p.osThreadToGoroutine(t) | |
| if err != nil { | |
| return nil, err | |
| } | |
| events[i] = &Breakpoint{commonEvent{p, gt}, t, proc.Word(c)} | |
| i++ | |
| case proc.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:] | |
| 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:] | |
| 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.curGoroutine = ev.Goroutine() | |
| action, err = hook.handle(ev) | |
| case *GoroutineCreate: | |
| p.curGoroutine = ev.Goroutine() | |
| action, err = p.goroutineCreateHook.handle(ev) | |
| case *GoroutineExit: | |
| action, err = p.goroutineExitHook.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 | |
| // asynchronous 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 _, g := range p.goroutines { | |
| g.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.curGoroutine == nil { | |
| return NoCurrentGoroutine{} | |
| } | |
| return p.curGoroutine.Out() | |
| } | |
| // In selects the frame called by the current frame. | |
| func (p *Process) In() os.Error { | |
| if p.curGoroutine == nil { | |
| return NoCurrentGoroutine{} | |
| } | |
| return p.curGoroutine.In() | |
| } |