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