diff options
Diffstat (limited to 'src/pkg/exp/ogle/cmd.go')
-rw-r--r-- | src/pkg/exp/ogle/cmd.go | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/src/pkg/exp/ogle/cmd.go b/src/pkg/exp/ogle/cmd.go new file mode 100644 index 000000000..f60621343 --- /dev/null +++ b/src/pkg/exp/ogle/cmd.go @@ -0,0 +1,375 @@ +// 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. + +// Ogle is the beginning of a debugger for Go. +package ogle + +import ( + "bufio"; + "debug/elf"; + "debug/proc"; + "exp/eval"; + "fmt"; + "go/scanner"; + "go/token"; + "os"; + "strconv"; + "strings"; +) + +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, _ := 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 tproc proc.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; + } + tproc, err = proc.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]; + } + tproc, err = proc.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 tproc + // before detaching. + } + + // Get symbols + f, err := os.Open(fname, os.O_RDONLY, 0); + if err != nil { + tproc.Detach(); + return err; + } + defer f.Close(); + elf, err := elf.NewFile(f); + if err != nil { + tproc.Detach(); + return err; + } + curProc, err = NewProcessElf(tproc, elf); + if err != nil { + tproc.Detach(); + return err; + } + + // Prepare new process + curProc.OnGoroutineCreate().AddHandler(EventPrint); + curProc.OnGoroutineExit().AddHandler(EventPrint); + + err = curProc.populateWorld(world); + if err != nil { + tproc.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 { + _, 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); + _, tok, _ := 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); + fn := curProc.syms.LookupFunc(name); + if fn == nil { + t.Abort(UsageError("no such function " + name)); + } + curProc.OnBreakpoint(proc.Word(fn.Entry)).AddHandler(EventStop); +} |