2009-09-18 10:11:19 -06:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
package ogle
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio";
|
2009-09-25 10:39:08 -06:00
|
|
|
"debug/elf";
|
2009-09-24 10:07:47 -06:00
|
|
|
"debug/proc";
|
2009-09-18 10:11:19 -06:00
|
|
|
"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) {
|
2009-09-24 09:32:59 -06:00
|
|
|
sc, _ := newScanner(line);
|
2009-09-18 10:11:19 -06:00
|
|
|
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;
|
2009-09-24 10:07:47 -06:00
|
|
|
var tproc proc.Process;
|
2009-09-18 10:11:19 -06:00
|
|
|
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;
|
|
|
|
}
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc, err = proc.Attach(pid);
|
2009-09-18 10:11:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
println("Attached to", pid);
|
|
|
|
} else {
|
|
|
|
parts := strings.Split(path, " ", 0);
|
|
|
|
if len(parts) == 0 {
|
|
|
|
fname = "";
|
|
|
|
} else {
|
|
|
|
fname = parts[0];
|
|
|
|
}
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc, err = proc.ForkExec(fname, parts, os.Environ(), "", []*os.File{os.Stdin, os.Stdout, os.Stderr});
|
2009-09-18 10:11:19 -06:00
|
|
|
if err != nil {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
println("Started", path);
|
2009-09-24 10:07:47 -06:00
|
|
|
// TODO(austin) If we fail after this point, kill tproc
|
2009-09-18 10:11:19 -06:00
|
|
|
// before detaching.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get symbols
|
|
|
|
f, err := os.Open(fname, os.O_RDONLY, 0);
|
|
|
|
if err != nil {
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc.Detach();
|
2009-09-18 10:11:19 -06:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
defer f.Close();
|
2009-09-25 10:39:08 -06:00
|
|
|
elf, err := elf.NewFile(f);
|
2009-09-18 10:11:19 -06:00
|
|
|
if err != nil {
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc.Detach();
|
2009-09-18 10:11:19 -06:00
|
|
|
return err;
|
|
|
|
}
|
2009-09-24 10:07:47 -06:00
|
|
|
curProc, err = NewProcessElf(tproc, elf);
|
2009-09-18 10:11:19 -06:00
|
|
|
if err != nil {
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc.Detach();
|
2009-09-18 10:11:19 -06:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare new process
|
|
|
|
curProc.OnGoroutineCreate().AddHandler(EventPrint);
|
|
|
|
curProc.OnGoroutineExit().AddHandler(EventPrint);
|
|
|
|
|
|
|
|
err = curProc.populateWorld(world);
|
|
|
|
if err != nil {
|
2009-09-24 10:07:47 -06:00
|
|
|
tproc.Detach();
|
2009-09-18 10:11:19 -06:00
|
|
|
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 {
|
2009-09-24 09:32:59 -06:00
|
|
|
_, toks[i], lits[i] = sc.Scan();
|
2009-09-18 10:11:19 -06:00
|
|
|
}
|
|
|
|
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);
|
2009-09-24 09:32:59 -06:00
|
|
|
_, tok, _ := sc.Scan();
|
2009-09-18 10:11:19 -06:00
|
|
|
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);
|
2009-09-25 10:39:08 -06:00
|
|
|
fn := curProc.syms.LookupFunc(name);
|
|
|
|
if fn == nil {
|
|
|
|
t.Abort(UsageError("no such function " + name));
|
2009-09-18 10:11:19 -06:00
|
|
|
}
|
2009-09-25 10:39:08 -06:00
|
|
|
curProc.OnBreakpoint(proc.Word(fn.Entry)).AddHandler(EventStop);
|
2009-09-18 10:11:19 -06:00
|
|
|
}
|