|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- // Copyright 2018 Google Inc. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- // Package remote provides remote access to a debugproxy server.
- package remote
-
- import (
- "fmt"
- "io"
- "net/rpc"
- "os"
- "os/exec"
-
- "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug"
- "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/server/protocol"
- )
-
- var _ debug.Program = (*Program)(nil)
- var _ debug.File = (*File)(nil)
-
- // Program implements the debug.Program interface.
- // Through that interface it provides access to a program being
- // debugged on a possibly remote machine by communicating
- // with a debugproxy adjacent to the target program.
- type Program struct {
- client *rpc.Client
- }
-
- // DebugproxyCmd is the path to the debugproxy command. It is a variable in case
- // the default value, "debugproxy", is not in the $PATH.
- var DebugproxyCmd = "debugproxy"
-
- // New connects to the specified host using SSH, starts DebugproxyCmd
- // 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, DebugproxyCmd, "-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()
- if msg, err := readLine(fromStdout); err != nil {
- return nil, err
- } else if msg != "OK" {
- // 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
- }
-
- func (p *Program) Open(name string, mode string) (debug.File, error) {
- req := protocol.OpenRequest{
- Name: name,
- Mode: mode,
- }
- var resp protocol.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) (debug.Status, error) {
- req := protocol.RunRequest{args}
- var resp protocol.RunResponse
- err := p.client.Call("Server.Run", &req, &resp)
- if err != nil {
- return debug.Status{}, err
- }
- return resp.Status, nil
- }
-
- func (p *Program) Stop() (debug.Status, error) {
- panic("unimplemented")
- }
-
- func (p *Program) Resume() (debug.Status, error) {
- req := protocol.ResumeRequest{}
- var resp protocol.ResumeResponse
- err := p.client.Call("Server.Resume", &req, &resp)
- if err != nil {
- return debug.Status{}, err
- }
- return resp.Status, nil
- }
-
- func (p *Program) Kill() (debug.Status, error) {
- panic("unimplemented")
- }
-
- func (p *Program) Breakpoint(address uint64) ([]uint64, error) {
- req := protocol.BreakpointRequest{
- Address: address,
- }
- var resp protocol.BreakpointResponse
- err := p.client.Call("Server.Breakpoint", &req, &resp)
- return resp.PCs, err
- }
-
- func (p *Program) BreakpointAtFunction(name string) ([]uint64, error) {
- req := protocol.BreakpointAtFunctionRequest{
- Function: name,
- }
- var resp protocol.BreakpointResponse
- err := p.client.Call("Server.BreakpointAtFunction", &req, &resp)
- return resp.PCs, err
- }
-
- func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) {
- req := protocol.BreakpointAtLineRequest{
- File: file,
- Line: line,
- }
- var resp protocol.BreakpointResponse
- err := p.client.Call("Server.BreakpointAtLine", &req, &resp)
- return resp.PCs, err
- }
-
- func (p *Program) DeleteBreakpoints(pcs []uint64) error {
- req := protocol.DeleteBreakpointsRequest{PCs: pcs}
- var resp protocol.DeleteBreakpointsResponse
- return p.client.Call("Server.DeleteBreakpoints", &req, &resp)
- }
-
- func (p *Program) Eval(expr string) ([]string, error) {
- req := protocol.EvalRequest{
- Expr: expr,
- }
- var resp protocol.EvalResponse
- err := p.client.Call("Server.Eval", &req, &resp)
- return resp.Result, err
- }
-
- func (p *Program) Evaluate(e string) (debug.Value, error) {
- req := protocol.EvaluateRequest{
- Expression: e,
- }
- var resp protocol.EvaluateResponse
- err := p.client.Call("Server.Evaluate", &req, &resp)
- return resp.Result, err
- }
-
- func (p *Program) Frames(count int) ([]debug.Frame, error) {
- req := protocol.FramesRequest{
- Count: count,
- }
- var resp protocol.FramesResponse
- err := p.client.Call("Server.Frames", &req, &resp)
- return resp.Frames, err
- }
-
- func (p *Program) Goroutines() ([]*debug.Goroutine, error) {
- req := protocol.GoroutinesRequest{}
- var resp protocol.GoroutinesResponse
- err := p.client.Call("Server.Goroutines", &req, &resp)
- return resp.Goroutines, err
- }
-
- func (p *Program) VarByName(name string) (debug.Var, error) {
- req := protocol.VarByNameRequest{Name: name}
- var resp protocol.VarByNameResponse
- err := p.client.Call("Server.VarByName", &req, &resp)
- return resp.Var, err
- }
-
- func (p *Program) Value(v debug.Var) (debug.Value, error) {
- req := protocol.ValueRequest{Var: v}
- var resp protocol.ValueResponse
- err := p.client.Call("Server.Value", &req, &resp)
- return resp.Value, err
- }
-
- func (p *Program) MapElement(m debug.Map, index uint64) (debug.Var, debug.Var, error) {
- req := protocol.MapElementRequest{Map: m, Index: index}
- var resp protocol.MapElementResponse
- err := p.client.Call("Server.MapElement", &req, &resp)
- return resp.Key, resp.Value, err
- }
-
- // File implements the debug.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 := protocol.ReadAtRequest{
- FD: f.fd,
- Len: len(p),
- Offset: offset,
- }
- var resp protocol.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 := protocol.WriteAtRequest{
- FD: f.fd,
- Data: p,
- Offset: offset,
- }
- var resp protocol.WriteAtResponse
- err := f.prog.client.Call("Server.WriteAt", &req, &resp)
- return resp.Len, err
- }
-
- func (f *File) Close() error {
- req := protocol.CloseRequest{
- FD: f.fd,
- }
- var resp protocol.CloseResponse
- err := f.prog.client.Call("Server.Close", &req, &resp)
- return err
- }
|