2 * Copyright (C) 2017 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
30 "github.com/Sirupsen/logrus"
31 "github.com/iotbzh/xds-agent/lib/xaapiv1"
32 common "github.com/iotbzh/xds-common/golib"
33 sio_client "github.com/sebd71/go-socket.io-client"
36 // GdbXds - Implementation of IGDB used to interfacing XDS
50 httpCli *common.HTTPClient
51 ioSock *sio_client.Client
53 projects []xaapiv1.ProjectConfig
57 cbOnDisconnect func(error)
58 cbRead func(timestamp, stdout, stderr string)
59 cbInferiorRead func(timestamp, stdout, stderr string)
60 cbOnExit func(code int, err error)
63 // NewGdbXds creates a new instance of GdbXds
64 func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
67 ccmd: "exec $GDB", // var set by environment-setup-xxx script
72 xGdbPid: strconv.Itoa(os.Getpid()),
76 // SetConfig set additional config fields
77 func (g *GdbXds) SetConfig(name string, value interface{}) error {
80 g.uri = value.(string)
82 g.prjID = value.(string)
84 g.sdkID = value.(string)
86 g.rPath = value.(string)
88 g.listPrj = value.(bool)
90 return fmt.Errorf("Unknown %s field", name)
95 // Init initializes gdb XDS
96 func (g *GdbXds) Init() (int, error) {
98 // Reset command ID (also used to enable sending of signals)
101 // Define HTTP and WS url
103 if !strings.HasPrefix(g.uri, "http://") {
104 baseURL = "http://" + g.uri
107 // Create HTTP client
108 g.log.Infoln("Connect HTTP client on ", baseURL)
109 conf := common.HTTPClientConfig{
110 URLPrefix: "/api/v1",
111 HeaderClientKeyName: "Xds-Agent-Sid",
114 LogLevel: common.HTTPLogLevelWarning,
116 c, err := common.HTTPNewClient(baseURL, conf)
118 errmsg := err.Error()
119 if m, err := regexp.MatchString("Get http.?://", errmsg); m && err == nil {
120 i := strings.LastIndex(errmsg, ":")
121 errmsg = "Cannot connection to " + baseURL + errmsg[i:]
123 return int(syscallEBADE), fmt.Errorf(errmsg)
126 g.httpCli.SetLogLevel(g.log.Level.String())
127 g.log.Infoln("HTTP session ID:", g.httpCli.GetClientID())
129 // First call to check that xds-agent and server are alive
130 ver := xaapiv1.XDSVersion{}
131 if err := g.httpCli.Get("/version", &ver); err != nil {
132 return int(syscallEBADE), err
134 g.log.Infoln("XDS agent & server version:", ver)
136 // SEB Check that server is connected
137 // FIXME: add multi-servers support
139 // Get XDS projects list
141 if err := g.httpCli.HTTPGet("/projects", &data); err != nil {
142 return int(syscallEBADE), err
145 g.log.Infof("Result of /projects: %v", string(data[:]))
146 g.projects = []xaapiv1.ProjectConfig{}
147 errMar := json.Unmarshal(data, &g.projects)
149 g.log.Errorf("Cannot decode projects configuration: %s", errMar.Error())
152 // Check mandatory args
153 if g.prjID == "" || g.listPrj {
154 return g.printProjectsList()
157 // Create io Websocket client
158 g.log.Infoln("Connecting IO.socket client on ", baseURL)
160 opts := &sio_client.Options{
161 Transport: "websocket",
162 Header: make(map[string][]string),
164 opts.Header["XDS-AGENT-SID"] = []string{c.GetClientID()}
166 iosk, err := sio_client.NewClient(baseURL, opts)
168 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
169 return int(syscall.ECONNABORTED), fmt.Errorf(e)
173 iosk.On("error", func(err error) {
174 if g.cbOnError != nil {
179 iosk.On("disconnection", func(err error) {
180 if g.cbOnDisconnect != nil {
181 g.cbOnDisconnect(err)
185 iosk.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
187 g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
191 iosk.On(xaapiv1.ExecInferiorOutEvent, func(ev xaapiv1.ExecOutMsg) {
192 if g.cbInferiorRead != nil {
193 g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
197 iosk.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
198 if g.cbOnExit != nil {
199 g.cbOnExit(ev.Code, ev.Error)
206 // Close frees allocated objects and close opened connections
207 func (g *GdbXds) Close() error {
208 g.cbOnDisconnect = nil
212 g.cbInferiorRead = nil
218 // Start sends a request to start remotely gdb within xds-server
219 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
221 var project *xaapiv1.ProjectConfig
223 // Retrieve the project definition
224 for _, f := range g.projects {
225 // check as prefix to support short/partial id name
226 if strings.HasPrefix(f.ID, g.prjID) {
232 // Auto setup rPath if needed
233 if g.rPath == "" && project != nil {
234 cwd, err := os.Getwd()
236 fldRp := project.ClientPath
237 if !strings.HasPrefix(fldRp, "/") {
240 log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
241 if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
242 g.rPath = strings.Trim(sp[1], "/")
243 g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
248 // Enable workaround about inferior output with gdbserver connection
249 // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
250 _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
252 args := xaapiv1.ExecArgs{
260 TTYGdbserverFix: !gdbserverNoFix,
261 CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
264 g.log.Infof("POST %s/exec %v", g.uri, args)
265 res := xaapiv1.ExecResult{}
266 err = g.httpCli.Post("/exec", args, &res)
268 return int(syscall.EAGAIN), err
271 return int(syscallEBADE), fmt.Errorf("null CmdID")
278 // Cmd returns the command name
279 func (g *GdbXds) Cmd() string {
283 // Args returns the list of arguments
284 func (g *GdbXds) Args() []string {
288 // Env returns the list of environment variables
289 func (g *GdbXds) Env() []string {
293 // OnError is called on a WebSocket error
294 func (g *GdbXds) OnError(f func(error)) {
298 // OnDisconnect is called when WebSocket disconnection
299 func (g *GdbXds) OnDisconnect(f func(error)) {
303 // OnExit calls when exit event is received
304 func (g *GdbXds) OnExit(f func(code int, err error)) {
308 // Read calls when a message/string event is received on stdout or stderr
309 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
313 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
314 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
318 // Write writes message/string into gdb stdin
319 func (g *GdbXds) Write(args ...interface{}) error {
320 return g.ioSock.Emit(xaapiv1.ExecInEvent, args...)
323 // SendSignal is used to send a signal to remote process/gdb
324 func (g *GdbXds) SendSignal(sig os.Signal) error {
326 return fmt.Errorf("cmdID not set")
329 sigArg := xaapiv1.ExecSignalArgs{
331 Signal: sig.String(),
333 g.log.Debugf("POST /signal %v", sigArg)
334 return g.httpCli.Post("/signal", sigArg, nil)
337 //***** Private functions *****
339 func (g *GdbXds) printProjectsList() (int, error) {
341 if len(g.projects) > 0 {
342 msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
343 msg += " ID\t\t\t\t | Label"
344 for _, f := range g.projects {
345 msg += fmt.Sprintf("\n %s\t | %s", f.ID, f.Label)
346 if f.DefaultSdk != "" {
347 msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
353 // FIXME : support multiple servers
354 sdks := []xaapiv1.SDK{}
355 if err := g.httpCli.Get("/servers/0/sdks", &sdks); err != nil {
356 return int(syscallEBADE), err
358 msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
359 msg += " ID\t\t\t\t\t | NAME\n"
360 for _, s := range sdks {
361 msg += fmt.Sprintf(" %s\t | %s\n", s.ID, s.Name)
364 if len(g.projects) > 0 && len(sdks) > 0 {
365 msg += fmt.Sprintf("\n")
366 msg += fmt.Sprintf("For example: \n")
367 msg += fmt.Sprintf(" XDS_PROJECT_ID=%q XDS_SDK_ID=%q %s -x myGdbConf.ini\n",
368 g.projects[0].ID, sdks[0].ID, AppName)
371 return 0, fmt.Errorf(msg)