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 // Define HTTP and WS url
81 if !strings.HasPrefix(g.uri, "http://") {
82 baseURL = "http://" + g.uri
86 g.log.Infoln("Connect HTTP client on ", baseURL)
87 conf := common.HTTPClientConfig{
89 HeaderClientKeyName: "XDS-SID",
92 c, err := common.HTTPNewClient(baseURL, conf)
94 return int(syscallEBADE), err
98 // First call to check that xds-server is alive
100 if err := c.HTTPGet("/folders", &data); err != nil {
101 return int(syscallEBADE), err
103 g.log.Infof("Result of /folders: %v", string(data[:]))
104 g.folders = []folder.FolderConfig{}
105 errMar := json.Unmarshal(data, &g.folders)
107 g.log.Errorf("Cannot decode folders configuration: %s", errMar.Error())
110 // Check mandatory args
111 if g.prjID == "" || g.listPrj {
112 return g.printProjectsList()
115 // Create io Websocket client
116 g.log.Infoln("Connecting IO.socket client on ", baseURL)
118 opts := &sio_client.Options{
119 Transport: "websocket",
120 Header: make(map[string][]string),
122 opts.Header["XDS-SID"] = []string{c.GetClientID()}
124 iosk, err := sio_client.NewClient(baseURL, opts)
126 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
127 return int(syscall.ECONNABORTED), fmt.Errorf(e)
131 iosk.On("error", func(err error) {
132 if g.cbOnError != nil {
137 iosk.On("disconnection", func(err error) {
138 if g.cbOnDisconnect != nil {
139 g.cbOnDisconnect(err)
143 iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
145 g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
149 iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) {
150 if g.cbInferiorRead != nil {
151 g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
155 iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
156 if g.cbOnExit != nil {
157 g.cbOnExit(ev.Code, ev.Error)
164 func (g *GdbXds) Close() error {
165 g.cbOnDisconnect = nil
169 g.cbInferiorRead = nil
174 // Start sends a request to start remotely gdb within xds-server
175 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
178 var folder *folder.FolderConfig
180 // Retrieve the folder definition
181 for _, f := range g.folders {
188 // Auto setup rPath if needed
189 if g.rPath == "" && folder != nil {
190 cwd, err := os.Getwd()
192 fldRp := folder.ClientPath
193 if !strings.HasPrefix(fldRp, "/") {
196 log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
197 if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
198 g.rPath = strings.Trim(sp[1], "/")
199 g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
204 // Enable workaround about inferior output with gdbserver connection
205 // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
206 _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
208 args := apiv1.ExecArgs{
216 TTYGdbserverFix: !gdbserverNoFix,
217 CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
219 body, err = json.Marshal(args)
221 return int(syscallEBADE), err
224 g.log.Infof("POST %s/exec %v", g.uri, string(body))
225 var res *http.Response
227 res, err = g.httpCli.HTTPPostWithRes("/exec", string(body))
229 return int(syscall.EAGAIN), err
231 dRes := make(map[string]interface{})
232 json.Unmarshal(g.httpCli.ResponseToBArray(res), &dRes)
233 if _, found = dRes["cmdID"]; !found {
234 return int(syscallEBADE), err
236 g.cmdID = dRes["cmdID"].(string)
241 // Cmd returns the command name
242 func (g *GdbXds) Cmd() string {
246 // Args returns the list of arguments
247 func (g *GdbXds) Args() []string {
251 // Env returns the list of environment variables
252 func (g *GdbXds) Env() []string {
256 // OnError is called on a WebSocket error
257 func (g *GdbXds) OnError(f func(error)) {
261 // OnDisconnect is called when WebSocket disconnection
262 func (g *GdbXds) OnDisconnect(f func(error)) {
266 // OnExit calls when exit event is received
267 func (g *GdbXds) OnExit(f func(code int, err error)) {
271 // Read calls when a message/string event is received on stdout or stderr
272 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
276 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
277 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
281 // Write writes message/string into gdb stdin
282 func (g *GdbXds) Write(args ...interface{}) error {
283 return g.ioSock.Emit(apiv1.ExecInEvent, args...)
286 // SendSignal is used to send a signal to remote process/gdb
287 func (g *GdbXds) SendSignal(sig os.Signal) error {
289 body, err := json.Marshal(apiv1.ExecSignalArgs{
291 Signal: sig.String(),
294 g.log.Errorf(err.Error())
296 g.log.Debugf("POST /signal %s", string(body))
297 return g.httpCli.HTTPPost("/signal", string(body))
300 //***** Private functions *****
302 func (g *GdbXds) printProjectsList() (int, error) {
304 if len(g.folders) > 0 {
305 msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
306 msg += " ID\t\t\t\t | Label"
307 for _, f := range g.folders {
308 msg += fmt.Sprintf("\n %s\t | %s", f.ID, f.Label)
309 if f.DefaultSdk != "" {
310 msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
317 if err := g.httpCli.HTTPGet("/sdks", &data); err != nil {
318 return int(syscallEBADE), err
320 g.log.Infof("Result of /sdks: %v", string(data[:]))
322 sdks := []crosssdk.SDK{}
323 errMar := json.Unmarshal(data, &sdks)
325 msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
326 msg += " ID\t\t\t\t\t | NAME\n"
327 for _, s := range sdks {
328 msg += fmt.Sprintf(" %s\t | %s\n", s.ID, s.Name)
332 if len(g.folders) > 0 && len(sdks) > 0 {
333 msg += fmt.Sprintf("\n")
334 msg += fmt.Sprintf("For example: \n")
335 msg += fmt.Sprintf(" XDS_PROJECT_ID=%q XDS_SDK_ID=%q %s -x myGdbConf.ini\n",
336 g.folders[0].ID, sdks[0].ID, AppName)
339 return 0, fmt.Errorf(msg)