summaryrefslogtreecommitdiff
path: root/src/pkg/exp/ogle/cmd.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/exp/ogle/cmd.go')
-rw-r--r--src/pkg/exp/ogle/cmd.go375
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);
+}