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"
32 httpCli *common.HTTPClient
33 ioSock *sio_client.Client
35 folders []folder.FolderConfig
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)
45 // NewGdbXds creates a new instance of GdbXds
46 func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
49 ccmd: "exec $GDB", // var set by environment-setup-xxx script
57 // SetConfig set additional config fields
58 func (g *GdbXds) SetConfig(name string, value interface{}) error {
61 g.uri = value.(string)
63 g.prjID = value.(string)
65 g.sdkID = value.(string)
67 g.rPath = value.(string)
69 g.listPrj = value.(bool)
71 return fmt.Errorf("Unknown %s field", name)
76 // Init initializes gdb XDS
77 func (g *GdbXds) Init() (int, error) {
79 // Reset command ID (also used to enable sending of signals)
82 // Define HTTP and WS url
84 if !strings.HasPrefix(g.uri, "http://") {
85 baseURL = "http://" + g.uri
89 g.log.Infoln("Connect HTTP client on ", baseURL)
90 conf := common.HTTPClientConfig{
92 HeaderClientKeyName: "XDS-SID",
95 c, err := common.HTTPNewClient(baseURL, conf)
97 return int(syscallEBADE), err
101 // First call to check that xds-server is alive
103 if err := c.HTTPGet("/folders", &data); err != nil {
104 return int(syscallEBADE), err
106 g.log.Infof("Result of /folders: %v", string(data[:]))
107 g.folders = []folder.FolderConfig{}
108 errMar := json.Unmarshal(data, &g.folders)
110 g.log.Errorf("Cannot decode folders configuration: %s", errMar.Error())
113 // Check mandatory args
114 if g.prjID == "" || g.listPrj {
115 return g.printProjectsList()
118 // Create io Websocket client
119 g.log.Infoln("Connecting IO.socket client on ", baseURL)
121 opts := &sio_client.Options{
122 Transport: "websocket",
123 Header: make(map[string][]string),
125 opts.Header["XDS-SID"] = []string{c.GetClientID()}
127 iosk, err := sio_client.NewClient(baseURL, opts)
129 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
130 return int(syscall.ECONNABORTED), fmt.Errorf(e)
134 iosk.On("error", func(err error) {
135 if g.cbOnError != nil {
140 iosk.On("disconnection", func(err error) {
141 if g.cbOnDisconnect != nil {
142 g.cbOnDisconnect(err)
146 iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
148 g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
152 iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) {
153 if g.cbInferiorRead != nil {
154 g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
158 iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
159 if g.cbOnExit != nil {
160 g.cbOnExit(ev.Code, ev.Error)
167 func (g *GdbXds) Close() error {
168 g.cbOnDisconnect = nil
172 g.cbInferiorRead = nil
178 // Start sends a request to start remotely gdb within xds-server
179 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
182 var folder *folder.FolderConfig
184 // Retrieve the folder definition
185 for _, f := range g.folders {
192 // Auto setup rPath if needed
193 if g.rPath == "" && folder != nil {
194 cwd, err := os.Getwd()
196 fldRp := folder.ClientPath
197 if !strings.HasPrefix(fldRp, "/") {
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)
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")
212 args := apiv1.ExecArgs{
220 TTYGdbserverFix: !gdbserverNoFix,
221 CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
223 body, err = json.Marshal(args)
225 return int(syscallEBADE), err
228 g.log.Infof("POST %s/exec %v", g.uri, string(body))
229 var res *http.Response
231 res, err = g.httpCli.HTTPPostWithRes("/exec", string(body))
233 return int(syscall.EAGAIN), err
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
240 g.cmdID = dRes["cmdID"].(string)
245 // Cmd returns the command name
246 func (g *GdbXds) Cmd() string {
250 // Args returns the list of arguments
251 func (g *GdbXds) Args() []string {
255 // Env returns the list of environment variables
256 func (g *GdbXds) Env() []string {
260 // OnError is called on a WebSocket error
261 func (g *GdbXds) OnError(f func(error)) {
265 // OnDisconnect is called when WebSocket disconnection
266 func (g *GdbXds) OnDisconnect(f func(error)) {
270 // OnExit calls when exit event is received
271 func (g *GdbXds) OnExit(f func(code int, err error)) {
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)) {
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)) {
285 // Write writes message/string into gdb stdin
286 func (g *GdbXds) Write(args ...interface{}) error {
287 return g.ioSock.Emit(apiv1.ExecInEvent, args...)
290 // SendSignal is used to send a signal to remote process/gdb
291 func (g *GdbXds) SendSignal(sig os.Signal) error {
293 return fmt.Errorf("cmdID not set")
297 body, err := json.Marshal(apiv1.ExecSignalArgs{
299 Signal: sig.String(),
302 g.log.Errorf(err.Error())
304 g.log.Debugf("POST /signal %s", string(body))
305 return g.httpCli.HTTPPost("/signal", string(body))
308 //***** Private functions *****
310 func (g *GdbXds) printProjectsList() (int, error) {
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)
325 if err := g.httpCli.HTTPGet("/sdks", &data); err != nil {
326 return int(syscallEBADE), err
328 g.log.Infof("Result of /sdks: %v", string(data[:]))
330 sdks := []crosssdk.SDK{}
331 errMar := json.Unmarshal(data, &sdks)
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)
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)
347 return 0, fmt.Errorf(msg)