blob: 51bf1a5fb7828406fb4a3c15dbce6a1f670feacd [file] [log] [blame]
// Copyright 2014 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 client provides remote access to an ogle proxy.
package client // import "golang.org/x/debug/ogle/program/client"
import (
"errors"
"fmt"
"io"
"net/rpc"
"os"
"os/exec"
"golang.org/x/debug/ogle/program"
"golang.org/x/debug/ogle/program/proxyrpc"
)
var _ program.Program = (*Program)(nil)
var _ program.File = (*File)(nil)
// OgleproxyCmd is the path to the ogleproxy command. It is a variable in case
// the default value, "ogleproxy", is not in the $PATH.
var OgleproxyCmd = "ogleproxy"
// New connects to the specified host using SSH, starts an ogle proxy
// there, and creates a new program from the specified file.
// The program can then be started by the Run method.
func New(host string, textFile string) (*Program, error) {
// TODO: add args.
cmdStrs := []string{"/usr/bin/ssh", host, OgleproxyCmd, "-text", textFile}
if host == "localhost" {
cmdStrs = cmdStrs[2:]
}
cmd := exec.Command(cmdStrs[0], cmdStrs[1:]...)
stdin, toStdin, err := os.Pipe()
if err != nil {
return nil, err
}
fromStdout, stdout, err := os.Pipe()
if err != nil {
return nil, err
}
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = os.Stderr // Stderr from proxy appears on our stderr.
err = cmd.Start()
if err != nil {
return nil, err
}
stdout.Close()
// Read back one line. It must start "OGLE" and we hope says "OK".
msg, err := readLine(fromStdout)
if err != nil {
return nil, err
}
switch msg {
case "OGLE BAD":
// Error is on next line.
msg, err = readLine(fromStdout)
if err == nil {
err = errors.New(msg)
}
return nil, err
case "OGLE OK":
default:
// Communication error.
return nil, fmt.Errorf("unrecognized message %q", msg)
}
program := &Program{
client: rpc.NewClient(&rwc{
ssh: cmd,
r: fromStdout,
w: toStdin,
}),
}
return program, nil
}
// readLine reads one line of text from the reader. It does no buffering.
// The trailing newline is read but not returned.
func readLine(r io.Reader) (string, error) {
b := make([]byte, 0, 10)
var c [1]byte
for {
_, err := io.ReadFull(r, c[:])
if err != nil {
return "", err
}
if c[0] == '\n' {
break
}
b = append(b, c[0])
}
return string(b), nil
}
// rwc creates a single io.ReadWriteCloser from a read side and a write side.
// It also holds the command object so we can wait for SSH to complete.
// It allows us to do RPC over an SSH connection.
type rwc struct {
ssh *exec.Cmd
r *os.File
w *os.File
}
func (rwc *rwc) Read(p []byte) (int, error) {
return rwc.r.Read(p)
}
func (rwc *rwc) Write(p []byte) (int, error) {
return rwc.w.Write(p)
}
func (rwc *rwc) Close() error {
rerr := rwc.r.Close()
werr := rwc.w.Close()
cerr := rwc.ssh.Wait()
if cerr != nil {
// Wait exit status is most important.
return cerr
}
if rerr != nil {
return rerr
}
return werr
}
// Program implements the similarly named ogle interface.
// Through that interface it provides access to a program being
// debugged on a possibly remote machine by communicating
// with an ogle proxy adjacent to the target program.
type Program struct {
client *rpc.Client
}
func (p *Program) Open(name string, mode string) (program.File, error) {
req := proxyrpc.OpenRequest{
Name: name,
Mode: mode,
}
var resp proxyrpc.OpenResponse
err := p.client.Call("Server.Open", &req, &resp)
if err != nil {
return nil, err
}
f := &File{
prog: p,
fd: resp.FD,
}
return f, nil
}
func (p *Program) Run(args ...string) (program.Status, error) {
req := proxyrpc.RunRequest{args}
var resp proxyrpc.RunResponse
err := p.client.Call("Server.Run", &req, &resp)
if err != nil {
return program.Status{}, err
}
return resp.Status, nil
}
func (p *Program) Stop() (program.Status, error) {
panic("unimplemented")
}
func (p *Program) Resume() (program.Status, error) {
req := proxyrpc.ResumeRequest{}
var resp proxyrpc.ResumeResponse
err := p.client.Call("Server.Resume", &req, &resp)
if err != nil {
return program.Status{}, err
}
return resp.Status, nil
}
func (p *Program) Kill() (program.Status, error) {
panic("unimplemented")
}
func (p *Program) Breakpoint(address uint64) ([]uint64, error) {
req := proxyrpc.BreakpointRequest{
Address: address,
}
var resp proxyrpc.BreakpointResponse
err := p.client.Call("Server.Breakpoint", &req, &resp)
return resp.PCs, err
}
func (p *Program) BreakpointAtFunction(name string) ([]uint64, error) {
req := proxyrpc.BreakpointAtFunctionRequest{
Function: name,
}
var resp proxyrpc.BreakpointResponse
err := p.client.Call("Server.BreakpointAtFunction", &req, &resp)
return resp.PCs, err
}
func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) {
req := proxyrpc.BreakpointAtLineRequest{
File: file,
Line: line,
}
var resp proxyrpc.BreakpointResponse
err := p.client.Call("Server.BreakpointAtLine", &req, &resp)
return resp.PCs, err
}
func (p *Program) DeleteBreakpoints(pcs []uint64) error {
req := proxyrpc.DeleteBreakpointsRequest{PCs: pcs}
var resp proxyrpc.DeleteBreakpointsResponse
return p.client.Call("Server.DeleteBreakpoints", &req, &resp)
}
func (p *Program) Eval(expr string) ([]string, error) {
req := proxyrpc.EvalRequest{
Expr: expr,
}
var resp proxyrpc.EvalResponse
err := p.client.Call("Server.Eval", &req, &resp)
return resp.Result, err
}
func (p *Program) Evaluate(e string) (program.Value, error) {
req := proxyrpc.EvaluateRequest{
Expression: e,
}
var resp proxyrpc.EvaluateResponse
err := p.client.Call("Server.Evaluate", &req, &resp)
return resp.Result, err
}
func (p *Program) Frames(count int) ([]program.Frame, error) {
req := proxyrpc.FramesRequest{
Count: count,
}
var resp proxyrpc.FramesResponse
err := p.client.Call("Server.Frames", &req, &resp)
return resp.Frames, err
}
func (p *Program) Goroutines() ([]*program.Goroutine, error) {
req := proxyrpc.GoroutinesRequest{}
var resp proxyrpc.GoroutinesResponse
err := p.client.Call("Server.Goroutines", &req, &resp)
return resp.Goroutines, err
}
func (p *Program) VarByName(name string) (program.Var, error) {
req := proxyrpc.VarByNameRequest{Name: name}
var resp proxyrpc.VarByNameResponse
err := p.client.Call("Server.VarByName", &req, &resp)
return resp.Var, err
}
func (p *Program) Value(v program.Var) (program.Value, error) {
req := proxyrpc.ValueRequest{Var: v}
var resp proxyrpc.ValueResponse
err := p.client.Call("Server.Value", &req, &resp)
return resp.Value, err
}
func (p *Program) MapElement(m program.Map, index uint64) (program.Var, program.Var, error) {
req := proxyrpc.MapElementRequest{Map: m, Index: index}
var resp proxyrpc.MapElementResponse
err := p.client.Call("Server.MapElement", &req, &resp)
return resp.Key, resp.Value, err
}
// File implements the program.File interface, providing access
// to file-like resources associated with the target program.
type File struct {
prog *Program // The Program associated with the file.
fd int // File descriptor.
}
func (f *File) ReadAt(p []byte, offset int64) (int, error) {
req := proxyrpc.ReadAtRequest{
FD: f.fd,
Len: len(p),
Offset: offset,
}
var resp proxyrpc.ReadAtResponse
err := f.prog.client.Call("Server.ReadAt", &req, &resp)
return copy(p, resp.Data), err
}
func (f *File) WriteAt(p []byte, offset int64) (int, error) {
req := proxyrpc.WriteAtRequest{
FD: f.fd,
Data: p,
Offset: offset,
}
var resp proxyrpc.WriteAtResponse
err := f.prog.client.Call("Server.WriteAt", &req, &resp)
return resp.Len, err
}
func (f *File) Close() error {
req := proxyrpc.CloseRequest{
FD: f.fd,
}
var resp proxyrpc.CloseResponse
err := f.prog.client.Call("Server.Close", &req, &resp)
return err
}