diff options
-rw-r--r-- | src/pkg/runtime/proc.c | 6 | ||||
-rw-r--r-- | usr/austin/eval/bridge.go | 14 | ||||
-rw-r--r-- | usr/austin/ogle/Makefile | 27 | ||||
-rw-r--r-- | usr/austin/ogle/cmd.go | 379 | ||||
-rw-r--r-- | usr/austin/ogle/goroutine.go | 1 | ||||
-rw-r--r-- | usr/austin/ogle/main.go | 11 | ||||
-rw-r--r-- | usr/austin/ogle/process.go | 18 |
7 files changed, 443 insertions, 13 deletions
diff --git a/src/pkg/runtime/proc.c b/src/pkg/runtime/proc.c index 06859b09c..590c277dd 100644 --- a/src/pkg/runtime/proc.c +++ b/src/pkg/runtime/proc.c @@ -265,12 +265,18 @@ readylocked(G *g) matchmg(); } +static void +nop(void) +{ +} + // Same as readylocked but a different symbol so that // debuggers can set a breakpoint here and catch all // new goroutines. static void newprocreadylocked(G *g) { + nop(); // avoid inlining in 6l readylocked(g); } diff --git a/usr/austin/eval/bridge.go b/usr/austin/eval/bridge.go index 41674860e..da2dd52a9 100644 --- a/usr/austin/eval/bridge.go +++ b/usr/austin/eval/bridge.go @@ -117,8 +117,10 @@ func TypeFromNative(t reflect.Type) Type { } if nt != nil { - nt.Complete(et); - et = nt; + if _, ok := et.(*NamedType); !ok { + nt.Complete(et); + et = nt; + } } nativeTypes[et] = t; @@ -137,7 +139,7 @@ func TypeOfNative(v interface {}) Type { */ type nativeFunc struct { - fn func([]Value, []Value); + fn func(*Thread, []Value, []Value); in, out int; } @@ -147,14 +149,14 @@ func (f *nativeFunc) NewFrame() *Frame { } func (f *nativeFunc) Call(t *Thread) { - f.fn(t.f.Vars[0:f.in], t.f.Vars[f.in:f.in+f.out]); + f.fn(t, t.f.Vars[0:f.in], t.f.Vars[f.in:f.in+f.out]); } // FuncFromNative creates an interpreter function from a native // function that takes its in and out arguments as slices of // interpreter Value's. While somewhat inconvenient, this avoids // value marshalling. -func FuncFromNative(fn func([]Value, []Value), t *FuncType) FuncValue { +func FuncFromNative(fn func(*Thread, []Value, []Value), t *FuncType) FuncValue { return &funcV{&nativeFunc{fn, len(t.In), len(t.Out)}}; } @@ -162,7 +164,7 @@ func FuncFromNative(fn func([]Value, []Value), t *FuncType) FuncValue { // function type from a function pointer using reflection. Typically, // the type will be given as a nil pointer to a function with the // desired signature. -func FuncFromNativeTyped(fn func([]Value, []Value), t interface{}) (*FuncType, FuncValue) { +func FuncFromNativeTyped(fn func(*Thread, []Value, []Value), t interface{}) (*FuncType, FuncValue) { ft := TypeOfNative(t).(*FuncType); return ft, FuncFromNative(fn, ft); } diff --git a/usr/austin/ogle/Makefile b/usr/austin/ogle/Makefile new file mode 100644 index 000000000..a169d063b --- /dev/null +++ b/usr/austin/ogle/Makefile @@ -0,0 +1,27 @@ +# 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. + +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=ogle +GOFILES=\ + abort.go\ + arch.go\ + cmd.go\ + event.go\ + frame.go\ + goroutine.go\ + rruntime.go\ + rtype.go\ + rvalue.go\ + process.go\ + vars.go\ + +include $(GOROOT)/src/Make.pkg + +main.6: main.go + $(GC) -I_obj $< + +ogle: main.6 package + $(LD) -L_obj -o $@ $< diff --git a/usr/austin/ogle/cmd.go b/usr/austin/ogle/cmd.go new file mode 100644 index 000000000..e24094244 --- /dev/null +++ b/usr/austin/ogle/cmd.go @@ -0,0 +1,379 @@ +// 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 ( + "bufio"; + "eval"; + "fmt"; + "go/scanner"; + "go/token"; + "os"; + "ptrace"; + "strconv"; + "strings"; + "sym"; +) + +var world *eval.World; +var curProc *Process + +func Main() { + world = eval.NewWorld(); + defineFuncs(); + r := bufio.NewReader(os.Stdin); + for { + print("; "); + line, err := r.ReadSlice('\n'); + if err != nil { + break; + } + + // Try line as a command + cmd, rest := getCmd(line); + if cmd != nil { + err := cmd.handler(rest); + if err != nil { + scanner.PrintError(os.Stderr, err); + } + continue; + } + + // Try line as code + code, err := world.Compile(string(line)); + if err != nil { + scanner.PrintError(os.Stderr, err); + continue; + } + v, err := code.Run(); + if err != nil { + fmt.Fprintf(os.Stderr, err.String()); + continue; + } + if v != nil { + println(v.String()); + } + } +} + +// newScanner creates a new scanner that scans that given input bytes. +func newScanner(input []byte) (*scanner.Scanner, *scanner.ErrorVector) { + sc := new(scanner.Scanner); + ev := new(scanner.ErrorVector); + ev.Init(); + sc.Init("input", input, ev, 0); + + return sc, ev; +} + +/* + * Commands + */ + +// A UsageError occurs when a command is called with illegal arguments. +type UsageError string; + +func (e UsageError) String() string { + return string(e); +} + +// A cmd represents a single command with a handler. +type cmd struct { + cmd string; + handler func([]byte) os.Error; +} + +var cmds = []cmd { + cmd{"load", cmdLoad}, + cmd{"bt", cmdBt}, +} + +// getCmd attempts to parse an input line as a registered command. If +// successful, it returns the command and the bytes remaining after +// the command, which should be passed to the command. +func getCmd(line []byte) (*cmd, []byte) { + sc, ev := newScanner(line); + pos, tok, lit := sc.Scan(); + if sc.ErrorCount != 0 || tok != token.IDENT { + return nil, nil; + } + + slit := string(lit); + for i := range cmds { + if cmds[i].cmd == slit { + return &cmds[i], line[pos.Offset + len(lit):len(line)]; + } + } + return nil, nil; +} + +// cmdLoad starts or attaches to a process. Its form is similar to +// import: +// +// load [sym] "path" [;] +// +// sym specifies the name to give to the process. If not given, the +// name is derived from the path of the process. If ".", then the +// packages from the remote process are defined into the current +// namespace. If given, this symbol is defined as a package +// containing the process' packages. +// +// path gives the path of the process to start or attach to. If it is +// "pid:<num>", then attach to the given PID. Otherwise, treat it as +// a file path and space-separated arguments and start a new process. +// +// load always sets the current process to the loaded process. +func cmdLoad(args []byte) os.Error { + ident, path, err := parseLoad(args); + if err != nil { + return err; + } + if curProc != nil { + return UsageError("multiple processes not implemented"); + } + if ident != "." { + return UsageError("process identifiers not implemented"); + } + + // Parse argument and start or attach to process + var fname string; + var proc ptrace.Process; + if len(path) >= 4 && path[0:4] == "pid:" { + pid, err := strconv.Atoi(path[4:len(path)]); + if err != nil { + return err; + } + fname, err = os.Readlink(fmt.Sprintf("/proc/%d/exe", pid)); + if err != nil { + return err; + } + proc, err = ptrace.Attach(pid); + if err != nil { + return err; + } + println("Attached to", pid); + } else { + parts := strings.Split(path, " ", 0); + if len(parts) == 0 { + fname = ""; + } else { + fname = parts[0]; + } + proc, err = ptrace.ForkExec(fname, parts, os.Environ(), "", []*os.File{os.Stdin, os.Stdout, os.Stderr}); + if err != nil { + return err; + } + println("Started", path); + // TODO(austin) If we fail after this point, kill proc + // before detaching. + } + + // Get symbols + f, err := os.Open(fname, os.O_RDONLY, 0); + if err != nil { + proc.Detach(); + return err; + } + defer f.Close(); + elf, err := sym.NewElf(f); + if err != nil { + proc.Detach(); + return err; + } + curProc, err = NewProcessElf(proc, elf); + if err != nil { + proc.Detach(); + return err; + } + + // Prepare new process + curProc.OnGoroutineCreate().AddHandler(EventPrint); + curProc.OnGoroutineExit().AddHandler(EventPrint); + + err = curProc.populateWorld(world); + if err != nil { + proc.Detach(); + return err; + } + + return nil; +} + +func parseLoad(args []byte) (ident string, path string, err os.Error) { + err = UsageError("Usage: load [sym] \"path\""); + sc, ev := newScanner(args); + + var toks [4]token.Token; + var lits [4][]byte; + for i := range toks { + var pos token.Position; + pos, toks[i], lits[i] = sc.Scan(); + } + if sc.ErrorCount != 0 { + err = ev.GetError(scanner.NoMultiples); + return; + } + + i := 0; + switch toks[i] { + case token.PERIOD, token.IDENT: + ident = string(lits[i]); + i++; + } + + if toks[i] != token.STRING { + return; + } + path, uerr := strconv.Unquote(string(lits[i])); + if uerr != nil { + err = uerr; + return; + } + i++; + + if toks[i] == token.SEMICOLON { + i++; + } + if toks[i] != token.EOF { + return; + } + + return ident, path, nil; +} + +// cmdBt prints a backtrace for the current goroutine. It takes no +// arguments. +func cmdBt(args []byte) os.Error { + err := parseNoArgs(args, "Usage: bt"); + if err != nil { + return err; + } + + if curProc == nil || curProc.curGoroutine == nil { + return NoCurrentGoroutine{}; + } + + f := curProc.curGoroutine.frame; + if f == nil { + fmt.Println("No frames on stack"); + return nil; + } + + for f.Inner() != nil { + f = f.Inner(); + } + + for i := 0; i < 100; i++ { + if f == curProc.curGoroutine.frame { + fmt.Printf("=> "); + } else { + fmt.Printf(" "); + } + fmt.Printf("%8x %v\n", f.pc, f); + f, err = f.Outer(); + if err != nil { + return err; + } + if f == nil { + return nil; + } + } + + fmt.Println("..."); + return nil; +} + +func parseNoArgs(args []byte, usage string) os.Error { + sc, ev := newScanner(args); + pos, tok, lit := sc.Scan(); + if sc.ErrorCount != 0 { + return ev.GetError(scanner.NoMultiples); + } + if tok != token.EOF { + return UsageError(usage); + } + return nil; +} + +/* + * Functions + */ + +// defineFuncs populates world with the built-in functions. +func defineFuncs() { + t, v := eval.FuncFromNativeTyped(fnOut, fnOutSig); + world.DefineConst("Out", t, v); + t, v = eval.FuncFromNativeTyped(fnContWait, fnContWaitSig); + world.DefineConst("ContWait", t, v); + t, v = eval.FuncFromNativeTyped(fnBpSet, fnBpSetSig); + world.DefineConst("BpSet", t, v); +} + +// printCurFrame prints the current stack frame, as it would appear in +// a backtrace. +func printCurFrame() { + if curProc == nil || curProc.curGoroutine == nil { + return; + } + f := curProc.curGoroutine.frame; + if f == nil { + return; + } + fmt.Printf("=> %8x %v\n", f.pc, f); +} + +// fnOut moves the current frame to the caller of the current frame. +func fnOutSig() {} +func fnOut(t *eval.Thread, args []eval.Value, res []eval.Value) { + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + err := curProc.Out(); + if err != nil { + t.Abort(err); + } + // TODO(austin) Only in the command form + printCurFrame(); +} + +// fnContWait continues the current process and waits for a stopping event. +func fnContWaitSig() {} +func fnContWait(t *eval.Thread, args []eval.Value, res []eval.Value) { + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + err := curProc.ContWait(); + if err != nil { + t.Abort(err); + } + // TODO(austin) Only in the command form + ev := curProc.Event(); + if ev != nil { + fmt.Printf("%v\n", ev); + } + printCurFrame(); +} + +// fnBpSet sets a breakpoint at the entry to the named function. +func fnBpSetSig(string) {} +func fnBpSet(t *eval.Thread, args []eval.Value, res []eval.Value) { + // TODO(austin) This probably shouldn't take a symbol name. + // Perhaps it should take an interface that provides PC's. + // Functions and instructions can implement that interface and + // we can have something to translate file:line pairs. + if curProc == nil { + t.Abort(NoCurrentGoroutine{}); + } + name := args[0].(eval.StringValue).Get(t); + s := curProc.syms.SymFromName(name); + if s == nil { + t.Abort(UsageError("symbol " + name + " not defined")); + } + fn, ok := s.(*sym.TextSym); + if !ok { + t.Abort(UsageError("symbol " + name + " is not a function")); + } + curProc.OnBreakpoint(ptrace.Word(fn.Entry())).AddHandler(EventStop); +} diff --git a/usr/austin/ogle/goroutine.go b/usr/austin/ogle/goroutine.go index b3cc827b7..2dc3d7ec7 100644 --- a/usr/austin/ogle/goroutine.go +++ b/usr/austin/ogle/goroutine.go @@ -41,7 +41,6 @@ func (t *Goroutine) resetFrame() (err os.Error) { // Out selects the caller frame of the current frame. func (t *Goroutine) Out() os.Error { - // TODO(austin) Outer can abort f, err := t.frame.Outer(); if f != nil { t.frame = f; diff --git a/usr/austin/ogle/main.go b/usr/austin/ogle/main.go new file mode 100644 index 000000000..7d94d38cc --- /dev/null +++ b/usr/austin/ogle/main.go @@ -0,0 +1,11 @@ +// 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 main + +import "ogle" + +func main() { + ogle.Main(); +} diff --git a/usr/austin/ogle/process.go b/usr/austin/ogle/process.go index 7e4f3ac3d..e51fb1528 100644 --- a/usr/austin/ogle/process.go +++ b/usr/austin/ogle/process.go @@ -149,12 +149,18 @@ func NewProcess(proc ptrace.Process, arch Arch, syms *sym.GoSymTable) (*Process, if err != nil { return nil, err; } - p.selectSomeGoroutine(); // Create internal breakpoints to catch new and exited goroutines p.OnBreakpoint(ptrace.Word(p.sys.newprocreadylocked.Entry())).(*breakpointHook).addHandler(readylockedBP, true); p.OnBreakpoint(ptrace.Word(p.sys.goexit.Entry())).(*breakpointHook).addHandler(goexitBP, true); + // Select current frames + for _, g := range p.goroutines { + g.resetFrame(); + } + + p.selectSomeGoroutine(); + return p, nil; } @@ -243,9 +249,9 @@ func (p *Process) selectSomeGoroutine() { // Once we have friendly goroutine ID's, there might be a more // reasonable behavior for this. p.curGoroutine = nil; - for _, t := range p.goroutines { - if !t.isG0() { - p.curGoroutine = t; + for _, g := range p.goroutines { + if !g.isG0() && g.frame != nil { + p.curGoroutine = g; return; } } @@ -486,8 +492,8 @@ func (p *Process) ContWait() os.Error { if err != nil { return err; } - for _, t := range p.goroutines { - t.resetFrame(); + for _, g := range p.goroutines { + g.resetFrame(); } p.pending, err = p.causesToEvents(); if err != nil { |