| // 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):] |
| } |
| } |
| 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:]); |
| 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); |
| } |