+/*
+ * Copyright (C) 2017-2018 "IoT.bzh"
+ * Author Sebastien Douheret <sebastien@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
package main
import (
"encoding/json"
"fmt"
- "net/http"
"os"
+ "regexp"
+ "runtime"
+ "strconv"
"strings"
"syscall"
+ "text/tabwriter"
+ "gerrit.automotivelinux.org/gerrit/src/xds/xds-agent.git/lib/xaapiv1"
+ common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git"
"github.com/Sirupsen/logrus"
- common "github.com/iotbzh/xds-common/golib"
- "github.com/iotbzh/xds-server/lib/apiv1"
- "github.com/iotbzh/xds-server/lib/crosssdk"
- "github.com/iotbzh/xds-server/lib/xdsconfig"
- sio_client "github.com/zhouhui8915/go-socket.io-client"
+ sio_client "github.com/sebd71/go-socket.io-client"
)
-// GdbXds -
+// GdbXds - Implementation of IGDB used to interfacing XDS
type GdbXds struct {
- log *logrus.Logger
- ccmd string
- aargs []string
- eenv []string
- uri string
- prjID string
- sdkID string
- rPath string
- listPrj bool
- cmdID string
+ log *logrus.Logger
+ ccmd string
+ aargs []string
+ eenv []string
+ agentURL string
+ serverURL string
+ prjID string
+ sdkID string
+ rPath string
+ listPrj bool
+ cmdID string
+ xGdbPid string
httpCli *common.HTTPClient
ioSock *sio_client.Client
+ projects []xaapiv1.ProjectConfig
+
// callbacks
cbOnError func(error)
cbOnDisconnect func(error)
func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds {
return &GdbXds{
log: log,
- ccmd: "$GDB", // var set by environment-setup-xxx script
+ ccmd: "exec $GDB", // var set by environment-setup-xxx script
aargs: args,
eenv: env,
httpCli: nil,
ioSock: nil,
+ xGdbPid: strconv.Itoa(os.Getpid()),
}
}
// SetConfig set additional config fields
func (g *GdbXds) SetConfig(name string, value interface{}) error {
+ var val string
+ if name != "listProject" {
+ val = strings.TrimSpace(value.(string))
+ }
switch name {
- case "uri":
- g.uri = value.(string)
+ case "agentURL":
+ g.agentURL = val
+ case "serverURL":
+ g.serverURL = val
case "prjID":
- g.prjID = value.(string)
+ g.prjID = val
case "sdkID":
- g.sdkID = value.(string)
+ g.sdkID = val
case "rPath":
- g.rPath = value.(string)
+ g.rPath = val
case "listProject":
g.listPrj = value.(bool)
default:
// Init initializes gdb XDS
func (g *GdbXds) Init() (int, error) {
+ // Reset command ID (also used to enable sending of signals)
+ g.cmdID = ""
+
// Define HTTP and WS url
- baseURL := g.uri
- if !strings.HasPrefix(g.uri, "http://") {
- baseURL = "http://" + g.uri
+ baseURL := g.agentURL
+
+ // Allow to only set port number
+ if match, _ := regexp.MatchString("^([0-9]+)$", baseURL); match {
+ baseURL = "http://localhost:" + g.agentURL
+ }
+ // Add http prefix if missing
+ if baseURL != "" && !strings.HasPrefix(g.agentURL, "http://") {
+ baseURL = "http://" + g.agentURL
}
// Create HTTP client
g.log.Infoln("Connect HTTP client on ", baseURL)
conf := common.HTTPClientConfig{
URLPrefix: "/api/v1",
- HeaderClientKeyName: "XDS-SID",
+ HeaderClientKeyName: "Xds-Agent-Sid",
CsrfDisable: true,
+ LogOut: g.log.Out,
+ LogPrefix: "XDSAGENT: ",
+ LogLevel: common.HTTPLogLevelDebug,
}
c, err := common.HTTPNewClient(baseURL, conf)
if err != nil {
- return int(syscallEBADE), err
+ errmsg := err.Error()
+ m, err := regexp.MatchString("Get http.?://", errmsg)
+ if (m && err == nil) || strings.Contains(errmsg, "Failed to get device ID") {
+ i := strings.LastIndex(errmsg, ":")
+ newErr := "Cannot connection to " + baseURL
+ if i > 0 {
+ newErr += " (" + strings.TrimSpace(errmsg[i+1:]) + ")"
+ } else {
+ newErr += " (" + strings.TrimSpace(errmsg) + ")"
+ }
+ errmsg = newErr
+ }
+ return int(syscallEBADE), fmt.Errorf(errmsg)
}
g.httpCli = c
+ g.httpCli.SetLogLevel(g.log.Level.String())
+ g.log.Infoln("HTTP session ID:", g.httpCli.GetClientID())
+
+ // First call to check that xds-agent and server are alive
+ ver := xaapiv1.XDSVersion{}
+ if err := g.httpCli.Get("/version", &ver); err != nil {
+ return int(syscallEBADE), err
+ }
+ g.log.Infoln("XDS agent & server version:", ver)
- // First call to check that xds-server is alive
+ // Get current config and update connection to server when needed
+ xdsConf := xaapiv1.APIConfig{}
+ if err := g.httpCli.Get("/config", &xdsConf); err != nil {
+ return int(syscallEBADE), err
+ }
+ // FIXME: add multi-servers support
+ idx := 0
+ svrCfg := xdsConf.Servers[idx]
+ if g.serverURL != "" && (svrCfg.URL != g.serverURL || !svrCfg.Connected) {
+ svrCfg.URL = g.serverURL
+ svrCfg.ConnRetry = 10
+ newCfg := xaapiv1.APIConfig{}
+ if err := g.httpCli.Post("/config", xdsConf, &newCfg); err != nil {
+ return int(syscallEBADE), err
+ }
+
+ } else if !svrCfg.Connected {
+ return int(syscallEBADE), fmt.Errorf("XDS server not connected (url=%s)", svrCfg.URL)
+ }
+
+ // Get XDS projects list
var data []byte
- if err := c.HTTPGet("/folders", &data); err != nil {
+ if err := g.httpCli.HTTPGet("/projects", &data); err != nil {
return int(syscallEBADE), err
}
- g.log.Infof("Result of /folders: %v", string(data[:]))
+
+ g.log.Infof("Result of /projects: %v", string(data[:]))
+ g.projects = []xaapiv1.ProjectConfig{}
+ errMar := json.Unmarshal(data, &g.projects)
+ if errMar != nil {
+ g.log.Errorf("Cannot decode projects configuration: %s", errMar.Error())
+ }
// Check mandatory args
if g.prjID == "" || g.listPrj {
- return getProjectsList(c, g.log, data)
+ return g.printProjectsList()
}
// Create io Websocket client
Transport: "websocket",
Header: make(map[string][]string),
}
- opts.Header["XDS-SID"] = []string{c.GetClientID()}
+ opts.Header["XDS-AGENT-SID"] = []string{c.GetClientID()}
iosk, err := sio_client.NewClient(baseURL, opts)
if err != nil {
}
})
- iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) {
+ // SEB gdbPid := ""
+ iosk.On(xaapiv1.ExecOutEvent, func(ev xaapiv1.ExecOutMsg) {
if g.cbRead != nil {
g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr)
+ /*
+ stdout := ev.Stdout
+ // SEB
+ //New Thread 15139
+ if strings.Contains(stdout, "pid = ") {
+ re := regexp.MustCompile("pid = ([0-9]+)")
+ if res := re.FindAllStringSubmatch(stdout, -1); len(res) > 0 {
+ gdbPid = res[0][1]
+ }
+ g.log.Errorf("SEB FOUND THREAD in '%s' => gdbPid=%s", stdout, gdbPid)
+ }
+ if gdbPid != "" && g.xGdbPid != "" && strings.Contains(stdout, gdbPid) {
+ g.log.Errorf("SEB THREAD REPLACE 1 stdout=%s", stdout)
+ stdout = strings.Replace(stdout, gdbPid, g.xGdbPid, -1)
+ g.log.Errorf("SEB THREAD REPLACE 2 stdout=%s", stdout)
+ }
+
+ g.cbRead(ev.Timestamp, stdout, ev.Stderr)
+ */
}
})
- iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) {
+ iosk.On(xaapiv1.ExecInferiorOutEvent, func(ev xaapiv1.ExecOutMsg) {
if g.cbInferiorRead != nil {
g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr)
}
})
- iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) {
+ iosk.On(xaapiv1.ExecExitEvent, func(ev xaapiv1.ExecExitMsg) {
if g.cbOnExit != nil {
g.cbOnExit(ev.Code, ev.Error)
}
})
+ // Monitor XDS server configuration changes (and specifically connected status)
+ iosk.On(xaapiv1.EVTServerConfig, func(ev xaapiv1.EventMsg) {
+ svrCfg, err := ev.DecodeServerCfg()
+ if err == nil && !svrCfg.Connected {
+ // TODO: should wait that server will be connected back
+ if g.cbOnExit != nil {
+ g.cbOnExit(-1, fmt.Errorf("XDS Server disconnected"))
+ } else {
+ fmt.Printf("XDS Server disconnected")
+ os.Exit(-1)
+ }
+ }
+ })
+
+ args := xaapiv1.EventRegisterArgs{Name: xaapiv1.EVTServerConfig}
+ if err := g.httpCli.Post("/events/register", args, nil); err != nil {
+ return 0, err
+ }
+
return 0, nil
}
+// Close frees allocated objects and close opened connections
func (g *GdbXds) Close() error {
g.cbOnDisconnect = nil
g.cbOnError = nil
g.cbOnExit = nil
g.cbRead = nil
g.cbInferiorRead = nil
+ g.cmdID = ""
return nil
}
// Start sends a request to start remotely gdb within xds-server
func (g *GdbXds) Start(inferiorTTY bool) (int, error) {
- var body []byte
var err error
+ var project *xaapiv1.ProjectConfig
+
+ // Retrieve the project definition
+ for _, f := range g.projects {
+ // check as prefix to support short/partial id name
+ if strings.HasPrefix(f.ID, g.prjID) {
+ project = &f
+ break
+ }
+ }
+
+ // Auto setup rPath if needed
+ if g.rPath == "" && project != nil {
+ cwd, err := os.Getwd()
+ if err == nil {
+ fldRp := project.ClientPath
+ if !strings.HasPrefix(fldRp, "/") {
+ fldRp = "/" + fldRp
+ }
+ log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp)
+ if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 {
+ g.rPath = strings.Trim(sp[1], "/")
+ g.log.Debugf("Auto-setup rPath to: '%s'", g.rPath)
+ }
+ }
+ }
// Enable workaround about inferior output with gdbserver connection
// except if XDS_GDBSERVER_OUTPUT_NOFIX is defined
_, gdbserverNoFix := os.LookupEnv("XDS_GDBSERVER_OUTPUT_NOFIX")
- args := apiv1.ExecArgs{
+ // SDK ID must be set else $GDB cannot be resolved
+ if g.sdkID == "" {
+ return int(syscall.EINVAL), fmt.Errorf("sdkid must be set")
+ }
+
+ args := xaapiv1.ExecArgs{
ID: g.prjID,
SdkID: g.sdkID,
Cmd: g.ccmd,
TTYGdbserverFix: !gdbserverNoFix,
CmdTimeout: -1, // no timeout, end when stdin close or command exited normally
}
- body, err = json.Marshal(args)
- if err != nil {
- return int(syscallEBADE), err
- }
- g.log.Infof("POST %s/exec %v", g.uri, string(body))
- var res *http.Response
- var found bool
- res, err = g.httpCli.HTTPPostWithRes("/exec", string(body))
+ g.log.Infof("POST %s/exec %v", g.agentURL, args)
+ res := xaapiv1.ExecResult{}
+ err = g.httpCli.Post("/exec", args, &res)
if err != nil {
return int(syscall.EAGAIN), err
}
- dRes := make(map[string]interface{})
- json.Unmarshal(g.httpCli.ResponseToBArray(res), &dRes)
- if _, found = dRes["cmdID"]; !found {
- return int(syscallEBADE), err
+ if res.CmdID == "" {
+ return int(syscallEBADE), fmt.Errorf("null CmdID")
}
- g.cmdID = dRes["cmdID"].(string)
+ g.cmdID = res.CmdID
return 0, nil
}
// Write writes message/string into gdb stdin
func (g *GdbXds) Write(args ...interface{}) error {
- return g.ioSock.Emit(apiv1.ExecInEvent, args...)
+ s := fmt.Sprint(args...)
+ return g.ioSock.Emit(xaapiv1.ExecInEvent, []byte(s))
}
// SendSignal is used to send a signal to remote process/gdb
func (g *GdbXds) SendSignal(sig os.Signal) error {
- var body []byte
- body, err := json.Marshal(apiv1.ExecSignalArgs{
+ if g.cmdID == "" {
+ return fmt.Errorf("cmdID not set")
+ }
+
+ sigArg := xaapiv1.ExecSignalArgs{
CmdID: g.cmdID,
Signal: sig.String(),
- })
- if err != nil {
- g.log.Errorf(err.Error())
}
- g.log.Debugf("POST /signal %s", string(body))
- return g.httpCli.HTTPPost("/signal", string(body))
+ g.log.Debugf("POST /signal %v", sigArg)
+ return g.httpCli.Post("/signal", sigArg, nil)
}
//***** Private functions *****
-func getProjectsList(c *common.HTTPClient, log *logrus.Logger, prjData []byte) (int, error) {
+func (g *GdbXds) printProjectsList() (int, error) {
+ var prjExample *xaapiv1.ProjectConfig
+ var sdkExample *xaapiv1.SDK
- folders := xdsconfig.FoldersConfig{}
- errMar := json.Unmarshal(prjData, &folders)
+ writer := new(tabwriter.Writer)
+ writer.Init(os.Stdout, 0, 8, 0, '\t', 0)
msg := ""
- if errMar == nil {
- msg += "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>): \n"
- msg += " ID\t\t\t\t | Label"
- for _, f := range folders {
- msg += fmt.Sprintf("\n %s\t | %s", f.ID, f.Label)
- if f.DefaultSdk != "" {
- msg += fmt.Sprintf("\t(default SDK: %s)", f.DefaultSdk)
- }
+ if len(g.projects) > 0 {
+ fmt.Fprintln(writer, "List of existing projects (use: export XDS_PROJECT_ID=<< ID >>):")
+ fmt.Fprintln(writer, "ID \t Label")
+ for ii, f := range g.projects {
+ fmt.Fprintf(writer, " %s \t %s\n", f.ID, f.Label)
+ prjExample = &g.projects[ii]
}
- msg += "\n"
}
- var data []byte
- if err := c.HTTPGet("/sdks", &data); err != nil {
+ // FIXME : support multiple servers
+ sdks := []xaapiv1.SDK{}
+ if err := g.httpCli.Get("/servers/0/sdks", &sdks); err != nil {
return int(syscallEBADE), err
}
- log.Infof("Result of /sdks: %v", string(data[:]))
-
- sdks := []crosssdk.SDK{}
- errMar = json.Unmarshal(data, &sdks)
- if errMar == nil {
- msg += "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>): \n"
- msg += " ID\t\t\t\t\t | NAME\n"
- for _, s := range sdks {
- msg += fmt.Sprintf(" %s\t | %s\n", s.ID, s.Name)
+ fmt.Fprintln(writer, "\nList of installed cross SDKs (use: export XDS_SDK_ID=<< ID >>):")
+ fmt.Fprintln(writer, "ID \t Name")
+ for ii, s := range sdks {
+ if s.Status == xaapiv1.SdkStatusInstalled {
+ fmt.Fprintf(writer, " %s \t %s\n", s.ID, s.Name)
+ sdkExample = &sdks[ii]
}
}
- if len(folders) > 0 && len(sdks) > 0 {
- msg += fmt.Sprintf("\n")
- msg += fmt.Sprintf("For example: \n")
- msg += fmt.Sprintf(" XDS_PROJECT_ID=%q XDS_SDK_ID=%q %s -x myGdbConf.ini\n",
- folders[0].ID, sdks[0].ID, AppName)
+ if prjExample != nil && sdkExample != nil {
+ fmt.Fprintln(writer, "")
+ fmt.Fprintln(writer, "For example: ")
+ if runtime.GOOS == "windows" {
+ fmt.Fprintf(writer, " SET XDS_PROJECT_ID=%s && SET XDS_SDK_ID=%s && %s -x myGdbConf.ini\n",
+ prjExample.ID[:8], sdkExample.ID[:8], AppName)
+ } else {
+ fmt.Fprintf(writer, " XDS_PROJECT_ID=%s XDS_SDK_ID=%s %s -x myGdbConf.ini\n",
+ prjExample.ID[:8], sdkExample.ID[:8], AppName)
+ }
}
+ fmt.Fprintln(writer, "")
+ fmt.Fprintln(writer, "Or define settings within gdb configuration file (see help and :XDS-ENV: tag)")
+ writer.Flush()
return 0, fmt.Errorf(msg)
}