1// Copyright 2019 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package remote provides remote access to a debugproxy server. 16package remote 17 18import ( 19 "fmt" 20 "io" 21 "net/rpc" 22 "os" 23 "os/exec" 24 25 "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug" 26 "cloud.google.com/go/cmd/go-cloud-debug-agent/internal/debug/server/protocol" 27) 28 29var _ debug.Program = (*Program)(nil) 30var _ debug.File = (*File)(nil) 31 32// Program implements the debug.Program interface. 33// Through that interface it provides access to a program being 34// debugged on a possibly remote machine by communicating 35// with a debugproxy adjacent to the target program. 36type Program struct { 37 client *rpc.Client 38} 39 40// DebugproxyCmd is the path to the debugproxy command. It is a variable in case 41// the default value, "debugproxy", is not in the $PATH. 42var DebugproxyCmd = "debugproxy" 43 44// New connects to the specified host using SSH, starts DebugproxyCmd 45// there, and creates a new program from the specified file. 46// The program can then be started by the Run method. 47func New(host string, textFile string) (*Program, error) { 48 // TODO: add args. 49 cmdStrs := []string{"/usr/bin/ssh", host, DebugproxyCmd, "-text", textFile} 50 if host == "localhost" { 51 cmdStrs = cmdStrs[2:] 52 } 53 cmd := exec.Command(cmdStrs[0], cmdStrs[1:]...) 54 stdin, toStdin, err := os.Pipe() 55 if err != nil { 56 return nil, err 57 } 58 fromStdout, stdout, err := os.Pipe() 59 if err != nil { 60 return nil, err 61 } 62 cmd.Stdin = stdin 63 cmd.Stdout = stdout 64 cmd.Stderr = os.Stderr // Stderr from proxy appears on our stderr. 65 err = cmd.Start() 66 if err != nil { 67 return nil, err 68 } 69 stdout.Close() 70 if msg, err := readLine(fromStdout); err != nil { 71 return nil, err 72 } else if msg != "OK" { 73 // Communication error. 74 return nil, fmt.Errorf("unrecognized message %q", msg) 75 } 76 program := &Program{ 77 client: rpc.NewClient(&rwc{ 78 ssh: cmd, 79 r: fromStdout, 80 w: toStdin, 81 }), 82 } 83 return program, nil 84} 85 86// readLine reads one line of text from the reader. It does no buffering. 87// The trailing newline is read but not returned. 88func readLine(r io.Reader) (string, error) { 89 b := make([]byte, 0, 10) 90 var c [1]byte 91 for { 92 _, err := io.ReadFull(r, c[:]) 93 if err != nil { 94 return "", err 95 } 96 if c[0] == '\n' { 97 break 98 } 99 b = append(b, c[0]) 100 } 101 return string(b), nil 102} 103 104// rwc creates a single io.ReadWriteCloser from a read side and a write side. 105// It also holds the command object so we can wait for SSH to complete. 106// It allows us to do RPC over an SSH connection. 107type rwc struct { 108 ssh *exec.Cmd 109 r *os.File 110 w *os.File 111} 112 113func (rwc *rwc) Read(p []byte) (int, error) { 114 return rwc.r.Read(p) 115} 116 117func (rwc *rwc) Write(p []byte) (int, error) { 118 return rwc.w.Write(p) 119} 120 121func (rwc *rwc) Close() error { 122 rerr := rwc.r.Close() 123 werr := rwc.w.Close() 124 cerr := rwc.ssh.Wait() 125 if cerr != nil { 126 // Wait exit status is most important. 127 return cerr 128 } 129 if rerr != nil { 130 return rerr 131 } 132 return werr 133} 134 135func (p *Program) Open(name string, mode string) (debug.File, error) { 136 req := protocol.OpenRequest{ 137 Name: name, 138 Mode: mode, 139 } 140 var resp protocol.OpenResponse 141 err := p.client.Call("Server.Open", &req, &resp) 142 if err != nil { 143 return nil, err 144 } 145 f := &File{ 146 prog: p, 147 fd: resp.FD, 148 } 149 return f, nil 150} 151 152func (p *Program) Run(args ...string) (debug.Status, error) { 153 req := protocol.RunRequest{args} 154 var resp protocol.RunResponse 155 err := p.client.Call("Server.Run", &req, &resp) 156 if err != nil { 157 return debug.Status{}, err 158 } 159 return resp.Status, nil 160} 161 162func (p *Program) Stop() (debug.Status, error) { 163 panic("unimplemented") 164} 165 166func (p *Program) Resume() (debug.Status, error) { 167 req := protocol.ResumeRequest{} 168 var resp protocol.ResumeResponse 169 err := p.client.Call("Server.Resume", &req, &resp) 170 if err != nil { 171 return debug.Status{}, err 172 } 173 return resp.Status, nil 174} 175 176func (p *Program) Kill() (debug.Status, error) { 177 panic("unimplemented") 178} 179 180func (p *Program) Breakpoint(address uint64) ([]uint64, error) { 181 req := protocol.BreakpointRequest{ 182 Address: address, 183 } 184 var resp protocol.BreakpointResponse 185 err := p.client.Call("Server.Breakpoint", &req, &resp) 186 return resp.PCs, err 187} 188 189func (p *Program) BreakpointAtFunction(name string) ([]uint64, error) { 190 req := protocol.BreakpointAtFunctionRequest{ 191 Function: name, 192 } 193 var resp protocol.BreakpointResponse 194 err := p.client.Call("Server.BreakpointAtFunction", &req, &resp) 195 return resp.PCs, err 196} 197 198func (p *Program) BreakpointAtLine(file string, line uint64) ([]uint64, error) { 199 req := protocol.BreakpointAtLineRequest{ 200 File: file, 201 Line: line, 202 } 203 var resp protocol.BreakpointResponse 204 err := p.client.Call("Server.BreakpointAtLine", &req, &resp) 205 return resp.PCs, err 206} 207 208func (p *Program) DeleteBreakpoints(pcs []uint64) error { 209 req := protocol.DeleteBreakpointsRequest{PCs: pcs} 210 var resp protocol.DeleteBreakpointsResponse 211 return p.client.Call("Server.DeleteBreakpoints", &req, &resp) 212} 213 214func (p *Program) Eval(expr string) ([]string, error) { 215 req := protocol.EvalRequest{ 216 Expr: expr, 217 } 218 var resp protocol.EvalResponse 219 err := p.client.Call("Server.Eval", &req, &resp) 220 return resp.Result, err 221} 222 223func (p *Program) Evaluate(e string) (debug.Value, error) { 224 req := protocol.EvaluateRequest{ 225 Expression: e, 226 } 227 var resp protocol.EvaluateResponse 228 err := p.client.Call("Server.Evaluate", &req, &resp) 229 return resp.Result, err 230} 231 232func (p *Program) Frames(count int) ([]debug.Frame, error) { 233 req := protocol.FramesRequest{ 234 Count: count, 235 } 236 var resp protocol.FramesResponse 237 err := p.client.Call("Server.Frames", &req, &resp) 238 return resp.Frames, err 239} 240 241func (p *Program) Goroutines() ([]*debug.Goroutine, error) { 242 req := protocol.GoroutinesRequest{} 243 var resp protocol.GoroutinesResponse 244 err := p.client.Call("Server.Goroutines", &req, &resp) 245 return resp.Goroutines, err 246} 247 248func (p *Program) VarByName(name string) (debug.Var, error) { 249 req := protocol.VarByNameRequest{Name: name} 250 var resp protocol.VarByNameResponse 251 err := p.client.Call("Server.VarByName", &req, &resp) 252 return resp.Var, err 253} 254 255func (p *Program) Value(v debug.Var) (debug.Value, error) { 256 req := protocol.ValueRequest{Var: v} 257 var resp protocol.ValueResponse 258 err := p.client.Call("Server.Value", &req, &resp) 259 return resp.Value, err 260} 261 262func (p *Program) MapElement(m debug.Map, index uint64) (debug.Var, debug.Var, error) { 263 req := protocol.MapElementRequest{Map: m, Index: index} 264 var resp protocol.MapElementResponse 265 err := p.client.Call("Server.MapElement", &req, &resp) 266 return resp.Key, resp.Value, err 267} 268 269// File implements the debug.File interface, providing access 270// to file-like resources associated with the target program. 271type File struct { 272 prog *Program // The Program associated with the file. 273 fd int // File descriptor. 274} 275 276func (f *File) ReadAt(p []byte, offset int64) (int, error) { 277 req := protocol.ReadAtRequest{ 278 FD: f.fd, 279 Len: len(p), 280 Offset: offset, 281 } 282 var resp protocol.ReadAtResponse 283 err := f.prog.client.Call("Server.ReadAt", &req, &resp) 284 return copy(p, resp.Data), err 285} 286 287func (f *File) WriteAt(p []byte, offset int64) (int, error) { 288 req := protocol.WriteAtRequest{ 289 FD: f.fd, 290 Data: p, 291 Offset: offset, 292 } 293 var resp protocol.WriteAtResponse 294 err := f.prog.client.Call("Server.WriteAt", &req, &resp) 295 return resp.Len, err 296} 297 298func (f *File) Close() error { 299 req := protocol.CloseRequest{ 300 FD: f.fd, 301 } 302 var resp protocol.CloseResponse 303 err := f.prog.client.Call("Server.Close", &req, &resp) 304 return err 305} 306