blob: ba056e88bafc021bb4d5a8363dceeaee0d3abbf5 [file] [log] [blame]
// 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 fset = token.NewFileSet()
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(fset, 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)
file := fset.AddFile("input", fset.Base(), len(input))
sc.Init(file, 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{
{"load", cmdLoad},
{"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[fset.Position(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.StartProcess(fname, parts, &os.ProcAttr{Files: []*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]string
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)
}