mirror of
https://github.com/golang/go
synced 2024-11-25 10:17:57 -07:00
Rudimentary command shell for Ogle. Hack to prevent linker
from inlining newprocreadylocked. Fix type bridge's handling of basic types. Include interpreter's Thread in bridged native function calls. ; load . "6.out" Started 6.out ; BpSet("main·merge") ; ContWait() breakpoint at 0x400800 => 400800 main·merge /home/austin/src-go1/usr/austin/ptrace/test/sort.go:19 ; bt => 400800 main·merge /home/austin/src-go1/usr/austin/ptrace/test/sort.go:19 400b6a main·mergeSort+0x1be /home/austin/src-go1/usr/austin/ptrace/test/sort.go:34 448313 goexit /home/austin/src-go1/src/pkg/runtime/proc.c:133 ; main.merge.a {1} ; load . "pid:25753" Attached to 25753 ; bt => 479ddf syscall·Syscall+0x24 /home/austin/src-go1/src/pkg/syscall/asm_linux_amd64.s:24 47c011 syscall·Read+0x5d /home/austin/src-go1/src/pkg/syscall/zsyscall_linux_amd64.go:368 4119e5 os·*File·Read+0x5f /home/austin/src-go1/src/pkg/os/file.go:122 427bf3 bufio·*Reader·fill+0x116 /home/austin/src-go1/src/pkg/bufio/bufio.go:105 428361 bufio·*Reader·ReadSlice+0x195 /home/austin/src-go1/src/pkg/bufio/bufio.go:244 40204a ogle·Main+0x94 /home/austin/src-go1/usr/austin/ogle/cmd.go:226 40080f main·main+0xf /home/austin/src-go1/usr/austin/ogle/main.go:6 41c4b8 mainstart+0xf /home/austin/src-go1/src/pkg/runtime/amd64/asm.s:55 41531f goexit /home/austin/src-go1/src/pkg/runtime/proc.c:133 R=rsc APPROVED=rsc DELTA=433 (420 added, 2 deleted, 11 changed) OCL=34410 CL=34782
This commit is contained in:
parent
20583b5874
commit
ad9c6f7700
@ -265,12 +265,18 @@ readylocked(G *g)
|
||||
matchmg();
|
||||
}
|
||||
|
||||
static void
|
||||
nop(void)
|
||||
{
|
||||
}
|
||||
|
||||
// Same as readylocked but a different symbol so that
|
||||
// debuggers can set a breakpoint here and catch all
|
||||
// new goroutines.
|
||||
static void
|
||||
newprocreadylocked(G *g)
|
||||
{
|
||||
nop(); // avoid inlining in 6l
|
||||
readylocked(g);
|
||||
}
|
||||
|
||||
|
@ -117,8 +117,10 @@ func TypeFromNative(t reflect.Type) Type {
|
||||
}
|
||||
|
||||
if nt != nil {
|
||||
nt.Complete(et);
|
||||
et = nt;
|
||||
if _, ok := et.(*NamedType); !ok {
|
||||
nt.Complete(et);
|
||||
et = nt;
|
||||
}
|
||||
}
|
||||
|
||||
nativeTypes[et] = t;
|
||||
@ -137,7 +139,7 @@ func TypeOfNative(v interface {}) Type {
|
||||
*/
|
||||
|
||||
type nativeFunc struct {
|
||||
fn func([]Value, []Value);
|
||||
fn func(*Thread, []Value, []Value);
|
||||
in, out int;
|
||||
}
|
||||
|
||||
@ -147,14 +149,14 @@ func (f *nativeFunc) NewFrame() *Frame {
|
||||
}
|
||||
|
||||
func (f *nativeFunc) Call(t *Thread) {
|
||||
f.fn(t.f.Vars[0:f.in], t.f.Vars[f.in:f.in+f.out]);
|
||||
f.fn(t, t.f.Vars[0:f.in], t.f.Vars[f.in:f.in+f.out]);
|
||||
}
|
||||
|
||||
// FuncFromNative creates an interpreter function from a native
|
||||
// function that takes its in and out arguments as slices of
|
||||
// interpreter Value's. While somewhat inconvenient, this avoids
|
||||
// value marshalling.
|
||||
func FuncFromNative(fn func([]Value, []Value), t *FuncType) FuncValue {
|
||||
func FuncFromNative(fn func(*Thread, []Value, []Value), t *FuncType) FuncValue {
|
||||
return &funcV{&nativeFunc{fn, len(t.In), len(t.Out)}};
|
||||
}
|
||||
|
||||
@ -162,7 +164,7 @@ func FuncFromNative(fn func([]Value, []Value), t *FuncType) FuncValue {
|
||||
// function type from a function pointer using reflection. Typically,
|
||||
// the type will be given as a nil pointer to a function with the
|
||||
// desired signature.
|
||||
func FuncFromNativeTyped(fn func([]Value, []Value), t interface{}) (*FuncType, FuncValue) {
|
||||
func FuncFromNativeTyped(fn func(*Thread, []Value, []Value), t interface{}) (*FuncType, FuncValue) {
|
||||
ft := TypeOfNative(t).(*FuncType);
|
||||
return ft, FuncFromNative(fn, ft);
|
||||
}
|
||||
|
27
usr/austin/ogle/Makefile
Normal file
27
usr/austin/ogle/Makefile
Normal file
@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
|
||||
include $(GOROOT)/src/Make.$(GOARCH)
|
||||
|
||||
TARG=ogle
|
||||
GOFILES=\
|
||||
abort.go\
|
||||
arch.go\
|
||||
cmd.go\
|
||||
event.go\
|
||||
frame.go\
|
||||
goroutine.go\
|
||||
rruntime.go\
|
||||
rtype.go\
|
||||
rvalue.go\
|
||||
process.go\
|
||||
vars.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
|
||||
main.6: main.go
|
||||
$(GC) -I_obj $<
|
||||
|
||||
ogle: main.6 package
|
||||
$(LD) -L_obj -o $@ $<
|
379
usr/austin/ogle/cmd.go
Normal file
379
usr/austin/ogle/cmd.go
Normal file
@ -0,0 +1,379 @@
|
||||
// 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";
|
||||
"eval";
|
||||
"fmt";
|
||||
"go/scanner";
|
||||
"go/token";
|
||||
"os";
|
||||
"ptrace";
|
||||
"strconv";
|
||||
"strings";
|
||||
"sym";
|
||||
)
|
||||
|
||||
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, ev := 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 proc ptrace.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;
|
||||
}
|
||||
proc, err = ptrace.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];
|
||||
}
|
||||
proc, err = ptrace.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 proc
|
||||
// before detaching.
|
||||
}
|
||||
|
||||
// Get symbols
|
||||
f, err := os.Open(fname, os.O_RDONLY, 0);
|
||||
if err != nil {
|
||||
proc.Detach();
|
||||
return err;
|
||||
}
|
||||
defer f.Close();
|
||||
elf, err := sym.NewElf(f);
|
||||
if err != nil {
|
||||
proc.Detach();
|
||||
return err;
|
||||
}
|
||||
curProc, err = NewProcessElf(proc, elf);
|
||||
if err != nil {
|
||||
proc.Detach();
|
||||
return err;
|
||||
}
|
||||
|
||||
// Prepare new process
|
||||
curProc.OnGoroutineCreate().AddHandler(EventPrint);
|
||||
curProc.OnGoroutineExit().AddHandler(EventPrint);
|
||||
|
||||
err = curProc.populateWorld(world);
|
||||
if err != nil {
|
||||
proc.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 {
|
||||
var pos token.Position;
|
||||
pos, 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);
|
||||
pos, tok, lit := 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);
|
||||
s := curProc.syms.SymFromName(name);
|
||||
if s == nil {
|
||||
t.Abort(UsageError("symbol " + name + " not defined"));
|
||||
}
|
||||
fn, ok := s.(*sym.TextSym);
|
||||
if !ok {
|
||||
t.Abort(UsageError("symbol " + name + " is not a function"));
|
||||
}
|
||||
curProc.OnBreakpoint(ptrace.Word(fn.Entry())).AddHandler(EventStop);
|
||||
}
|
@ -41,7 +41,6 @@ func (t *Goroutine) resetFrame() (err os.Error) {
|
||||
|
||||
// Out selects the caller frame of the current frame.
|
||||
func (t *Goroutine) Out() os.Error {
|
||||
// TODO(austin) Outer can abort
|
||||
f, err := t.frame.Outer();
|
||||
if f != nil {
|
||||
t.frame = f;
|
||||
|
11
usr/austin/ogle/main.go
Normal file
11
usr/austin/ogle/main.go
Normal file
@ -0,0 +1,11 @@
|
||||
// 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 main
|
||||
|
||||
import "ogle"
|
||||
|
||||
func main() {
|
||||
ogle.Main();
|
||||
}
|
@ -149,12 +149,18 @@ func NewProcess(proc ptrace.Process, arch Arch, syms *sym.GoSymTable) (*Process,
|
||||
if err != nil {
|
||||
return nil, err;
|
||||
}
|
||||
p.selectSomeGoroutine();
|
||||
|
||||
// Create internal breakpoints to catch new and exited goroutines
|
||||
p.OnBreakpoint(ptrace.Word(p.sys.newprocreadylocked.Entry())).(*breakpointHook).addHandler(readylockedBP, true);
|
||||
p.OnBreakpoint(ptrace.Word(p.sys.goexit.Entry())).(*breakpointHook).addHandler(goexitBP, true);
|
||||
|
||||
// Select current frames
|
||||
for _, g := range p.goroutines {
|
||||
g.resetFrame();
|
||||
}
|
||||
|
||||
p.selectSomeGoroutine();
|
||||
|
||||
return p, nil;
|
||||
}
|
||||
|
||||
@ -243,9 +249,9 @@ func (p *Process) selectSomeGoroutine() {
|
||||
// Once we have friendly goroutine ID's, there might be a more
|
||||
// reasonable behavior for this.
|
||||
p.curGoroutine = nil;
|
||||
for _, t := range p.goroutines {
|
||||
if !t.isG0() {
|
||||
p.curGoroutine = t;
|
||||
for _, g := range p.goroutines {
|
||||
if !g.isG0() && g.frame != nil {
|
||||
p.curGoroutine = g;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -486,8 +492,8 @@ func (p *Process) ContWait() os.Error {
|
||||
if err != nil {
|
||||
return err;
|
||||
}
|
||||
for _, t := range p.goroutines {
|
||||
t.resetFrame();
|
||||
for _, g := range p.goroutines {
|
||||
g.resetFrame();
|
||||
}
|
||||
p.pending, err = p.causesToEvents();
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user