// 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.Uint64Value).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(); }