12 "github.com/Sirupsen/logrus"
13 "github.com/iotbzh/xds-agent/lib/xaapiv1"
14 common "github.com/iotbzh/xds-common/golib"
15 sio_client "github.com/sebd71/go-socket.io-client"
32 httpCli *common.HTTPClient
33 ioSock *sio_client.Client
35 projects []xaapiv1.ProjectConfig
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
54 xGdbPid: strconv.Itoa(os.Getpid()),
58 // SetConfig set additional config fields
59 func (g *GdbXds) SetConfig(name string, value interface{}) error {
62 g.uri = value.(string)
64 g.prjID = value.(string)
66 g.sdkID = value.(string)
68 g.rPath = value.(string)
70 g.listPrj = value.(bool)
72 return fmt.Errorf("Unknown %s field", name)
77 // Init initializes gdb XDS
78 func (g *GdbXds) Init() (int, error) {
80 // Reset command ID (also used to enable sending of signals)
83 // Define HTTP and WS url
85 if !strings.HasPrefix(g.uri, "http://") {
86 baseURL = "http://" + g.uri
90 g.log.Infoln("Connect HTTP client on ", baseURL)
91 conf := common.HTTPClientConfig{
93 HeaderClientKeyName: "Xds-Agent-Sid",
96 LogLevel: common.HTTPLogLevelWarning,
98 c, err := common.HTTPNewClient(baseURL, conf)
100 errmsg := err.Error()
101 if m, err := regexp.MatchString("Get http.?://", errmsg); m && err == nil {
102 i := strings.LastIndex(errmsg, ":")
103 errmsg = "Cannot connection to " + baseURL + errmsg[i:]
105 return int(syscallEBADE), fmt.Errorf(errmsg)
108 g.httpCli.SetLogLevel(g.log.Level.String())
109 g.log.Infoln("HTTP session ID:", g.httpCli.GetClientID())
111 // First call to check that xds-agent and server are alive
112 ver := xaapiv1.XDSVersion{}
113 if err := g.httpCli.Get("/version", &ver); err != nil {
114 return int(syscallEBADE), err
116 g.log.Infoln("XDS agent & server version:", ver)
118 // SEB Check that server is connected
119 // FIXME: add multi-servers support
121 // Get XDS projects list
123 if err := g.httpCli.HTTPGet("/projects", &data); err != nil {
124 return int(syscallEBADE), err
127 g.log.Infof("Result of /projects: %v", string(data[:]))
128 g.projects = []xaapiv1.ProjectConfig{}
129 errMar := json.Unmarshal(data, &g.projects)
131 g.log.Errorf("Cannot decode projects configuration: %s", errMar.Error())
134 // Check mandatory args
135 if g.prjID == "" || g.listPrj {
136 return g.printProjectsList()
139 // Create io Websocket client
140 g.log.Infoln("Connecting IO.socket client on ", baseURL)
142 opts := &sio_client.Options{
143 Transport: "websocket",
144 Header: make(map[string][]string),
146 opts.Header["XDS-AGENT-SID"] = []string{c.GetClientID()}
148 iosk, err := sio_client.NewClient(baseURL, opts)
150 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
151 return int(syscall.ECONNABORTED), fmt.Errorf(e)
155 iosk.On("error", func(err error) {
156 if g.cbOnError != nil {
161 iosk.On("disconnection", func(err error) {
162 if g.cbOnDisconnect != nil {
163 g.cbOnDisconnect(err)
167 iosk.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
169 g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
173 iosk.On(xaapiv1.ExecInferiorOutEvent, func(ev xaapiv1.ExecOutMsg) {
174 if g.cbInferiorRead != nil {
175 g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
179 iosk.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
180 if g.cbOnExit != nil {
181 g.cbOnExit(ev.Code, ev.Error)
188 // Close frees allocated objects and close opened connections
189 func (g *GdbXds) Close() error {
190 g.cbOnDisconnect = nil
194 g.cbInferiorRead = nil
200 // Start sends a request to start remotely gdb within xds-server
201 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
203 var project *xaapiv1.ProjectConfig
205 // Retrieve the project definition
206 for _, f := range g.projects {
207 // check as prefix to support short/partial id name
208 if strings.HasPrefix(f.ID, g.prjID) {
214 // Auto setup rPath if needed
215 if g.rPath == "" && project != nil {
216 cwd, err := os.Getwd()
218 fldRp := project.ClientPath
219 if !strings.HasPrefix(fldRp, "/") {
222 log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
223 if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
224 g.rPath = strings.Trim(sp[1], "/")
225 g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
230 // Enable workaround about inferior output with gdbserver connection
231 // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
232 _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
234 args := xaapiv1.ExecArgs{
242 TTYGdbserverFix: !gdbserverNoFix,
243 CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
246 g.log.Infof("POST %s/exec %v", g.uri, args)
247 res := xaapiv1.ExecResult{}
248 err = g.httpCli.Post("/exec", args, &res)
250 return int(syscall.EAGAIN), err
253 return int(syscallEBADE), fmt.Errorf("null CmdID")
260 // Cmd returns the command name
261 func (g *GdbXds) Cmd() string {
265 // Args returns the list of arguments
266 func (g *GdbXds) Args() []string {
270 // Env returns the list of environment variables
271 func (g *GdbXds) Env() []string {
275 // OnError is called on a WebSocket error
276 func (g *GdbXds) OnError(f func(error)) {
280 // OnDisconnect is called when WebSocket disconnection
281 func (g *GdbXds) OnDisconnect(f func(error)) {
285 // OnExit calls when exit event is received
286 func (g *GdbXds) OnExit(f func(code int, err error)) {
290 // Read calls when a message/string event is received on stdout or stderr
291 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
295 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
296 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
300 // Write writes message/string into gdb stdin
301 func (g *GdbXds) Write(args ...interface{}) error {
302 return g.ioSock.Emit(xaapiv1.ExecInEvent, args...)
305 // SendSignal is used to send a signal to remote process/gdb
306 func (g *GdbXds) SendSignal(sig os.Signal) error {
308 return fmt.Errorf("cmdID not set")
311 sigArg := xaapiv1.ExecSignalArgs{
313 Signal: sig.String(),
315 g.log.Debugf("POST /signal %v", sigArg)
316 return g.httpCli.Post("/signal", sigArg, nil)
319 //***** Private functions *****
321 func (g *GdbXds) printProjectsList() (int, error) {
323 if len(g.projects) > 0 {
324 msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
325 msg += " ID\t\t\t\t | Label"
326 for _, f := range g.projects {
327 msg += fmt.Sprintf("\n %s\t | %s", f.ID, f.Label)
328 if f.DefaultSdk != "" {
329 msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
335 // FIXME : support multiple servers
336 sdks := []xaapiv1.SDK{}
337 if err := g.httpCli.Get("/servers/0/sdks", &sdks); err != nil {
338 return int(syscallEBADE), err
340 msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
341 msg += " ID\t\t\t\t\t | NAME\n"
342 for _, s := range sdks {
343 msg += fmt.Sprintf(" %s\t | %s\n", s.ID, s.Name)
346 if len(g.projects) > 0 && len(sdks) > 0 {
347 msg += fmt.Sprintf("\n")
348 msg += fmt.Sprintf("For example: \n")
349 msg += fmt.Sprintf(" XDS_PROJECT_ID=%q XDS_SDK_ID=%q %s -x myGdbConf.ini\n",
350 g.projects[0].ID, sdks[0].ID, AppName)
353 return 0, fmt.Errorf(msg)