Don't try to send signal while gdb is not started.
[src/xds/xds-gdb.git] / gdb-xds.go
1 package main
2
3 import (
4         "encoding/json"
5         "fmt"
6         "net/http"
7         "os"
8         "strings"
9         "syscall"
10
11         "github.com/Sirupsen/logrus"
12         common "github.com/iotbzh/xds-common/golib"
13         "github.com/iotbzh/xds-server/lib/apiv1"
14         "github.com/iotbzh/xds-server/lib/crosssdk"
15         "github.com/iotbzh/xds-server/lib/folder"
16         sio_client "github.com/zhouhui8915/go-socket.io-client"
17 )
18
19 // GdbXds -
20 type GdbXds struct {
21         log     *logrus.Logger
22         ccmd    string
23         aargs   []string
24         eenv    []string
25         uri     string
26         prjID   string
27         sdkID   string
28         rPath   string
29         listPrj bool
30         cmdID   string
31
32         httpCli *common.HTTPClient
33         ioSock  *sio_client.Client
34
35         folders []folder.FolderConfig
36
37         // callbacks
38         cbOnError      func(error)
39         cbOnDisconnect func(error)
40         cbRead         func(timestamp, stdout, stderr string)
41         cbInferiorRead func(timestamp, stdout, stderr string)
42         cbOnExit       func(code int, err error)
43 }
44
45 // NewGdbXds creates a new instance of GdbXds
46 func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
47         return &GdbXds{
48                 log:     log,
49                 ccmd:    "exec $GDB", // var set by environment-setup-xxx script
50                 aargs:   args,
51                 eenv:    env,
52                 httpCli: nil,
53                 ioSock:  nil,
54         }
55 }
56
57 // SetConfig set additional config fields
58 func (g *GdbXds) SetConfig(name string, value interface{}) error {
59         switch name {
60         case "uri":
61                 g.uri = value.(string)
62         case "prjID":
63                 g.prjID = value.(string)
64         case "sdkID":
65                 g.sdkID = value.(string)
66         case "rPath":
67                 g.rPath = value.(string)
68         case "listProject":
69                 g.listPrj = value.(bool)
70         default:
71                 return fmt.Errorf("Unknown %s field", name)
72         }
73         return nil
74 }
75
76 // Init initializes gdb XDS
77 func (g *GdbXds) Init() (int, error) {
78
79         // Reset command ID (also used to enable sending of signals)
80         g.cmdID = ""
81
82         // Define HTTP and WS url
83         baseURL := g.uri
84         if !strings.HasPrefix(g.uri, "http://") {
85                 baseURL = "http://" + g.uri
86         }
87
88         // Create HTTP client
89         g.log.Infoln("Connect HTTP client on ", baseURL)
90         conf := common.HTTPClientConfig{
91                 URLPrefix:           "/api/v1",
92                 HeaderClientKeyName: "XDS-SID",
93                 CsrfDisable:         true,
94         }
95         c, err := common.HTTPNewClient(baseURL, conf)
96         if err != nil {
97                 return int(syscallEBADE), err
98         }
99         g.httpCli = c
100
101         // First call to check that xds-server is alive
102         var data []byte
103         if err := c.HTTPGet("/folders", &data); err != nil {
104                 return int(syscallEBADE), err
105         }
106         g.log.Infof("Result of /folders: %v", string(data[:]))
107         g.folders = []folder.FolderConfig{}
108         errMar := json.Unmarshal(data, &g.folders)
109         if errMar != nil {
110                 g.log.Errorf("Cannot decode folders configuration: %s", errMar.Error())
111         }
112
113         // Check mandatory args
114         if g.prjID == "" || g.listPrj {
115                 return g.printProjectsList()
116         }
117
118         // Create io Websocket client
119         g.log.Infoln("Connecting IO.socket client on ", baseURL)
120
121         opts := &sio_client.Options{
122                 Transport: "websocket",
123                 Header:    make(map[string][]string),
124         }
125         opts.Header["XDS-SID"] = []string{c.GetClientID()}
126
127         iosk, err := sio_client.NewClient(baseURL, opts)
128         if err != nil {
129                 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
130                 return int(syscall.ECONNABORTED), fmt.Errorf(e)
131         }
132         g.ioSock = iosk
133
134         iosk.On("error", func(err error) {
135                 if g.cbOnError != nil {
136                         g.cbOnError(err)
137                 }
138         })
139
140         iosk.On("disconnection", func(err error) {
141                 if g.cbOnDisconnect != nil {
142                         g.cbOnDisconnect(err)
143                 }
144         })
145
146         iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
147                 if g.cbRead != nil {
148                         g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
149                 }
150         })
151
152         iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) {
153                 if g.cbInferiorRead != nil {
154                         g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
155                 }
156         })
157
158         iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
159                 if g.cbOnExit != nil {
160                         g.cbOnExit(ev.Code, ev.Error)
161                 }
162         })
163
164         return 0, nil
165 }
166
167 func (g *GdbXds) Close() error {
168         g.cbOnDisconnect = nil
169         g.cbOnError = nil
170         g.cbOnExit = nil
171         g.cbRead = nil
172         g.cbInferiorRead = nil
173         g.cmdID = ""
174
175         return nil
176 }
177
178 // Start sends a request to start remotely gdb within xds-server
179 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
180         var body []byte
181         var err error
182         var folder *folder.FolderConfig
183
184         // Retrieve the folder definition
185         for _, f := range g.folders {
186                 if f.ID == g.prjID {
187                         folder = &f
188                         break
189                 }
190         }
191
192         // Auto setup rPath if needed
193         if g.rPath == "" && folder != nil {
194                 cwd, err := os.Getwd()
195                 if err == nil {
196                         fldRp := folder.ClientPath
197                         if !strings.HasPrefix(fldRp, "/") {
198                                 fldRp = "/" + fldRp
199                         }
200                         log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
201                         if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
202                                 g.rPath = strings.Trim(sp[1], "/")
203                                 g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
204                         }
205                 }
206         }
207
208         // Enable workaround about inferior output with gdbserver connection
209         // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
210         _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
211
212         args := apiv1.ExecArgs{
213                 ID:              g.prjID,
214                 SdkID:           g.sdkID,
215                 Cmd:             g.ccmd,
216                 Args:            g.aargs,
217                 Env:             g.eenv,
218                 RPath:           g.rPath,
219                 TTY:             inferiorTTY,
220                 TTYGdbserverFix: !gdbserverNoFix,
221                 CmdTimeout:      -1, // no timeout, end when stdin close or command exited normally
222         }
223         body, err = json.Marshal(args)
224         if err != nil {
225                 return int(syscallEBADE), err
226         }
227
228         g.log.Infof("POST %s/exec %v", g.uri, string(body))
229         var res *http.Response
230         var found bool
231         res, err = g.httpCli.HTTPPostWithRes("/exec", string(body))
232         if err != nil {
233                 return int(syscall.EAGAIN), err
234         }
235         dRes := make(map[string]interface{})
236         json.Unmarshal(g.httpCli.ResponseToBArray(res), &dRes)
237         if _, found = dRes["cmdID"]; !found {
238                 return int(syscallEBADE), err
239         }
240         g.cmdID = dRes["cmdID"].(string)
241
242         return 0, nil
243 }
244
245 // Cmd returns the command name
246 func (g *GdbXds) Cmd() string {
247         return g.ccmd
248 }
249
250 // Args returns the list of arguments
251 func (g *GdbXds) Args() []string {
252         return g.aargs
253 }
254
255 // Env returns the list of environment variables
256 func (g *GdbXds) Env() []string {
257         return g.eenv
258 }
259
260 // OnError is called on a WebSocket error
261 func (g *GdbXds) OnError(f func(error)) {
262         g.cbOnError = f
263 }
264
265 // OnDisconnect is called when WebSocket disconnection
266 func (g *GdbXds) OnDisconnect(f func(error)) {
267         g.cbOnDisconnect = f
268 }
269
270 // OnExit calls when exit event is received
271 func (g *GdbXds) OnExit(f func(code int, err error)) {
272         g.cbOnExit = f
273 }
274
275 // Read calls when a message/string event is received on stdout or stderr
276 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
277         g.cbRead = f
278 }
279
280 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
281 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
282         g.cbInferiorRead = f
283 }
284
285 // Write writes message/string into gdb stdin
286 func (g *GdbXds) Write(args ...interface{}) error {
287         return g.ioSock.Emit(apiv1.ExecInEvent, args...)
288 }
289
290 // SendSignal is used to send a signal to remote process/gdb
291 func (g *GdbXds) SendSignal(sig os.Signal) error {
292         if g.cmdID == "" {
293                 return fmt.Errorf("cmdID not set")
294         }
295
296         var body []byte
297         body, err := json.Marshal(apiv1.ExecSignalArgs{
298                 CmdID:  g.cmdID,
299                 Signal: sig.String(),
300         })
301         if err != nil {
302                 g.log.Errorf(err.Error())
303         }
304         g.log.Debugf("POST /signal %s", string(body))
305         return g.httpCli.HTTPPost("/signal", string(body))
306 }
307
308 //***** Private functions *****
309
310 func (g *GdbXds) printProjectsList() (int, error) {
311         msg := ""
312         if len(g.folders) > 0 {
313                 msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
314                 msg += "  ID\t\t\t\t | Label"
315                 for _, f := range g.folders {
316                         msg += fmt.Sprintf("\n  %s\t | %s", f.ID, f.Label)
317                         if f.DefaultSdk != "" {
318                                 msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
319                         }
320                 }
321                 msg += "\n"
322         }
323
324         var data []byte
325         if err := g.httpCli.HTTPGet("/sdks", &data); err != nil {
326                 return int(syscallEBADE), err
327         }
328         g.log.Infof("Result of /sdks: %v", string(data[:]))
329
330         sdks := []crosssdk.SDK{}
331         errMar := json.Unmarshal(data, &sdks)
332         if errMar == nil {
333                 msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
334                 msg += "  ID\t\t\t\t\t | NAME\n"
335                 for _, s := range sdks {
336                         msg += fmt.Sprintf("  %s\t | %s\n", s.ID, s.Name)
337                 }
338         }
339
340         if len(g.folders) > 0 && len(sdks) > 0 {
341                 msg += fmt.Sprintf("\n")
342                 msg += fmt.Sprintf("For example: \n")
343                 msg += fmt.Sprintf("  XDS_PROJECT_ID=%q XDS_SDK_ID=%q  %s -x myGdbConf.ini\n",
344                         g.folders[0].ID, sdks[0].ID, AppName)
345         }
346
347         return 0, fmt.Errorf(msg)
348 }