2 * Copyright (C) 2017-2018 "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.
32 "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1"
33 common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
34 "github.com/Sirupsen/logrus"
35 sio_client "github.com/sebd71/go-socket.io-client"
38 // GdbXds - Implementation of IGDB used to interfacing XDS
53 httpCli *common.HTTPClient
54 ioSock *sio_client.Client
56 projects []xaapiv1.ProjectConfig
60 cbOnDisconnect func(error)
61 cbRead func(timestamp, stdout, stderr string)
62 cbInferiorRead func(timestamp, stdout, stderr string)
63 cbOnExit func(code int, err error)
66 // NewGdbXds creates a new instance of GdbXds
67 func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
70 ccmd: "exec $GDB", // var set by environment-setup-xxx script
75 xGdbPid: strconv.Itoa(os.Getpid()),
79 // SetConfig set additional config fields
80 func (g *GdbXds) SetConfig(name string, value interface{}) error {
82 if name != "listProject" {
83 val = strings.TrimSpace(value.(string))
97 g.listPrj = value.(bool)
99 return fmt.Errorf("Unknown %s field", name)
104 // Init initializes gdb XDS
105 func (g *GdbXds) Init() (int, error) {
107 // Reset command ID (also used to enable sending of signals)
110 // Define HTTP and WS url
111 baseURL := g.agentURL
113 // Allow to only set port number
114 if match, _ := regexp.MatchString("^([0-9]+)$", baseURL); match {
115 baseURL = "http://localhost:" + g.agentURL
117 // Add http prefix if missing
118 if baseURL != "" && !strings.HasPrefix(g.agentURL, "http://") {
119 baseURL = "http://" + g.agentURL
122 // Create HTTP client
123 g.log.Infoln("Connect HTTP client on ", baseURL)
124 conf := common.HTTPClientConfig{
125 URLPrefix: "/api/v1",
126 HeaderClientKeyName: "Xds-Agent-Sid",
129 LogPrefix: "XDSAGENT: ",
130 LogLevel: common.HTTPLogLevelDebug,
132 c, err := common.HTTPNewClient(baseURL, conf)
134 errmsg := err.Error()
135 m, err := regexp.MatchString("Get http.?://", errmsg)
136 if (m && err == nil) || strings.Contains(errmsg, "Failed to get device ID") {
137 i := strings.LastIndex(errmsg, ":")
138 newErr := "Cannot connection to " + baseURL
140 newErr += " (" + strings.TrimSpace(errmsg[i+1:]) + ")"
142 newErr += " (" + strings.TrimSpace(errmsg) + ")"
146 return int(syscallEBADE), fmt.Errorf(errmsg)
149 g.httpCli.SetLogLevel(g.log.Level.String())
150 g.log.Infoln("HTTP session ID:", g.httpCli.GetClientID())
152 // First call to check that xds-agent and server are alive
153 ver := xaapiv1.XDSVersion{}
154 if err := g.httpCli.Get("/version", &ver); err != nil {
155 return int(syscallEBADE), err
157 g.log.Infoln("XDS agent & server version:", ver)
159 // Get current config and update connection to server when needed
160 xdsConf := xaapiv1.APIConfig{}
161 if err := g.httpCli.Get("/config", &xdsConf); err != nil {
162 return int(syscallEBADE), err
164 // FIXME: add multi-servers support
166 svrCfg := xdsConf.Servers[idx]
167 if g.serverURL != "" && (svrCfg.URL != g.serverURL || !svrCfg.Connected) {
168 svrCfg.URL = g.serverURL
169 svrCfg.ConnRetry = 10
170 newCfg := xaapiv1.APIConfig{}
171 if err := g.httpCli.Post("/config", xdsConf, &newCfg); err != nil {
172 return int(syscallEBADE), err
175 } else if !svrCfg.Connected {
176 return int(syscallEBADE), fmt.Errorf("XDS server not connected (url=%s)", svrCfg.URL)
179 // Get XDS projects list
181 if err := g.httpCli.HTTPGet("/projects", &data); err != nil {
182 return int(syscallEBADE), err
185 g.log.Infof("Result of /projects: %v", string(data[:]))
186 g.projects = []xaapiv1.ProjectConfig{}
187 errMar := json.Unmarshal(data, &g.projects)
189 g.log.Errorf("Cannot decode projects configuration: %s", errMar.Error())
192 // Check mandatory args
193 if g.prjID == "" || g.listPrj {
194 return g.printProjectsList()
197 // Create io Websocket client
198 g.log.Infoln("Connecting IO.socket client on ", baseURL)
200 opts := &sio_client.Options{
201 Transport: "websocket",
202 Header: make(map[string][]string),
204 opts.Header["XDS-AGENT-SID"] = []string{c.GetClientID()}
206 iosk, err := sio_client.NewClient(baseURL, opts)
208 e := fmt.Sprintf("IO.socket connection error: " + err.Error())
209 return int(syscall.ECONNABORTED), fmt.Errorf(e)
213 iosk.On("error", func(err error) {
214 if g.cbOnError != nil {
219 iosk.On("disconnection", func(err error) {
220 if g.cbOnDisconnect != nil {
221 g.cbOnDisconnect(err)
226 iosk.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
228 g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
233 if strings.Contains(stdout, "pid = ") {
234 re := regexp.MustCompile("pid = ([0-9]+)")
235 if res := re.FindAllStringSubmatch(stdout, -1); len(res) > 0 {
238 g.log.Errorf("SEB FOUND THREAD in '%s' => gdbPid=%s", stdout, gdbPid)
240 if gdbPid != "" && g.xGdbPid != "" && strings.Contains(stdout, gdbPid) {
241 g.log.Errorf("SEB THREAD REPLACE 1 stdout=%s", stdout)
242 stdout = strings.Replace(stdout, gdbPid, g.xGdbPid, -1)
243 g.log.Errorf("SEB THREAD REPLACE 2 stdout=%s", stdout)
246 g.cbRead(ev.Timestamp, stdout, ev.Stderr)
251 iosk.On(xaapiv1.ExecInferiorOutEvent, func(ev xaapiv1.ExecOutMsg) {
252 if g.cbInferiorRead != nil {
253 g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
257 iosk.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
258 if g.cbOnExit != nil {
259 g.cbOnExit(ev.Code, ev.Error)
263 // Monitor XDS server configuration changes (and specifically connected status)
264 iosk.On(xaapiv1.EVTServerConfig, func(ev xaapiv1.EventMsg) {
265 svrCfg, err := ev.DecodeServerCfg()
266 if err == nil && !svrCfg.Connected {
267 // TODO: should wait that server will be connected back
268 if g.cbOnExit != nil {
269 g.cbOnExit(-1, fmt.Errorf("XDS Server disconnected"))
271 fmt.Printf("XDS Server disconnected")
277 args := xaapiv1.EventRegisterArgs{Name: xaapiv1.EVTServerConfig}
278 if err := g.httpCli.Post("/events/register", args, nil); err != nil {
285 // Close frees allocated objects and close opened connections
286 func (g *GdbXds) Close() error {
287 g.cbOnDisconnect = nil
291 g.cbInferiorRead = nil
297 // Start sends a request to start remotely gdb within xds-server
298 func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
300 var project *xaapiv1.ProjectConfig
302 // Retrieve the project definition
303 for _, f := range g.projects {
304 // check as prefix to support short/partial id name
305 if strings.HasPrefix(f.ID, g.prjID) {
311 // Auto setup rPath if needed
312 if g.rPath == "" && project != nil {
313 cwd, err := os.Getwd()
315 fldRp := project.ClientPath
316 if !strings.HasPrefix(fldRp, "/") {
319 log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
320 if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
321 g.rPath = strings.Trim(sp[1], "/")
322 g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
327 // Enable workaround about inferior output with gdbserver connection
328 // except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
329 _, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
331 // SDK ID must be set else $GDB cannot be resolved
333 return int(syscall.EINVAL), fmt.Errorf("sdkid must be set")
336 args := xaapiv1.ExecArgs{
344 TTYGdbserverFix: !gdbserverNoFix,
345 CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
348 g.log.Infof("POST %s/exec %v", g.agentURL, args)
349 res := xaapiv1.ExecResult{}
350 err = g.httpCli.Post("/exec", args, &res)
352 return int(syscall.EAGAIN), err
355 return int(syscallEBADE), fmt.Errorf("null CmdID")
362 // Cmd returns the command name
363 func (g *GdbXds) Cmd() string {
367 // Args returns the list of arguments
368 func (g *GdbXds) Args() []string {
372 // Env returns the list of environment variables
373 func (g *GdbXds) Env() []string {
377 // OnError is called on a WebSocket error
378 func (g *GdbXds) OnError(f func(error)) {
382 // OnDisconnect is called when WebSocket disconnection
383 func (g *GdbXds) OnDisconnect(f func(error)) {
387 // OnExit calls when exit event is received
388 func (g *GdbXds) OnExit(f func(code int, err error)) {
392 // Read calls when a message/string event is received on stdout or stderr
393 func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) {
397 // InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior)
398 func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) {
402 // Write writes message/string into gdb stdin
403 func (g *GdbXds) Write(args ...interface{}) error {
404 s := fmt.Sprint(args...)
405 return g.ioSock.Emit(xaapiv1.ExecInEvent, []byte(s))
408 // SendSignal is used to send a signal to remote process/gdb
409 func (g *GdbXds) SendSignal(sig os.Signal) error {
411 return fmt.Errorf("cmdID not set")
414 sigArg := xaapiv1.ExecSignalArgs{
416 Signal: sig.String(),
418 g.log.Debugf("POST /signal %v", sigArg)
419 return g.httpCli.Post("/signal", sigArg, nil)
422 //***** Private functions *****
424 func (g *GdbXds) printProjectsList() (int, error) {
425 var prjExample *xaapiv1.ProjectConfig
426 var sdkExample *xaapiv1.SDK
428 writer := new(tabwriter.Writer)
429 writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
431 if len(g.projects) > 0 {
432 fmt.Fprintln(writer, "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>):")
433 fmt.Fprintln(writer, "ID \t Label")
434 for ii, f := range g.projects {
435 fmt.Fprintf(writer, " %s \t %s\n", f.ID, f.Label)
436 prjExample = &g.projects[ii]
440 // FIXME : support multiple servers
441 sdks := []xaapiv1.SDK{}
442 if err := g.httpCli.Get("/servers/0/sdks", &sdks); err != nil {
443 return int(syscallEBADE), err
445 fmt.Fprintln(writer, "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>):")
446 fmt.Fprintln(writer, "ID \t Name")
447 for ii, s := range sdks {
448 if s.Status == xaapiv1.SdkStatusInstalled {
449 fmt.Fprintf(writer, " %s \t %s\n", s.ID, s.Name)
450 sdkExample = &sdks[ii]
454 if prjExample != nil && sdkExample != nil {
455 fmt.Fprintln(writer, "")
456 fmt.Fprintln(writer, "For example: ")
457 if runtime.GOOS == "windows" {
458 fmt.Fprintf(writer, " SET XDS_PROJECT_ID=%s && SET XDS_SDK_ID=%s && %s -x myGdbConf.ini\n",
459 prjExample.ID[:8], sdkExample.ID[:8], AppName)
461 fmt.Fprintf(writer, " XDS_PROJECT_ID=%s XDS_SDK_ID=%s %s -x myGdbConf.ini\n",
462 prjExample.ID[:8], sdkExample.ID[:8], AppName)
465 fmt.Fprintln(writer, "")
466 fmt.Fprintln(writer, "Or define settings within gdb configuration file (see help and :XDS-ENV: tag)")
469 return 0, fmt.Errorf(msg)