| // 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) | |
| 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, " ", -1) | |
| 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) | |
| } |