diff options
Diffstat (limited to 'gdb-xds.go')
-rw-r--r-- | gdb-xds.go | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/gdb-xds.go b/gdb-xds.go new file mode 100644 index 0000000..f174017 --- /dev/null +++ b/gdb-xds.go @@ -0,0 +1,311 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strings" + "syscall" + + "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" +) + +// GdbXds - +type GdbXds struct { + log *logrus.Logger + ccmd string + aargs []string + eenv []string + uri string + prjID string + sdkID string + rPath string + listPrj bool + cmdID string + + httpCli *common.HTTPClient + ioSock *sio_client.Client + + // callbacks + cbOnError func(error) + cbOnDisconnect func(error) + cbRead func(timestamp, stdout, stderr string) + cbInferiorRead func(timestamp, stdout, stderr string) + cbOnExit func(code int, err error) +} + +// NewGdbXds creates a new instance of GdbXds +func NewGdbXds(log *logrus.Logger, args []string, env []string) *GdbXds { + return &GdbXds{ + log: log, + ccmd: "$GDB", // var set by environment-setup-xxx script + aargs: args, + eenv: env, + httpCli: nil, + ioSock: nil, + } +} + +// SetConfig set additional config fields +func (g *GdbXds) SetConfig(name string, value interface{}) error { + switch name { + case "uri": + g.uri = value.(string) + case "prjID": + g.prjID = value.(string) + case "sdkID": + g.sdkID = value.(string) + case "rPath": + g.rPath = value.(string) + case "listProject": + g.listPrj = value.(bool) + default: + return fmt.Errorf("Unknown %s field", name) + } + return nil +} + +// Init initializes gdb XDS +func (g *GdbXds) Init() (int, error) { + + // Define HTTP and WS url + baseURL := g.uri + if !strings.HasPrefix(g.uri, "http://") { + baseURL = "http://" + g.uri + } + + // Create HTTP client + g.log.Infoln("Connect HTTP client on ", baseURL) + conf := common.HTTPClientConfig{ + URLPrefix: "/api/v1", + HeaderClientKeyName: "XDS-SID", + CsrfDisable: true, + } + c, err := common.HTTPNewClient(baseURL, conf) + if err != nil { + return int(syscallEBADE), err + } + g.httpCli = c + + // First call to check that xds-server is alive + var data []byte + if err := c.HTTPGet("/folders", &data); err != nil { + return int(syscallEBADE), err + } + g.log.Infof("Result of /folders: %v", string(data[:])) + + // Check mandatory args + if g.prjID == "" || g.listPrj { + return getProjectsList(c, g.log, data) + } + + // Create io Websocket client + g.log.Infoln("Connecting IO.socket client on ", baseURL) + + opts := &sio_client.Options{ + Transport: "websocket", + Header: make(map[string][]string), + } + opts.Header["XDS-SID"] = []string{c.GetClientID()} + + iosk, err := sio_client.NewClient(baseURL, opts) + if err != nil { + e := fmt.Sprintf("IO.socket connection error: " + err.Error()) + return int(syscall.ECONNABORTED), fmt.Errorf(e) + } + g.ioSock = iosk + + iosk.On("error", func(err error) { + if g.cbOnError != nil { + g.cbOnError(err) + } + }) + + iosk.On("disconnection", func(err error) { + if g.cbOnDisconnect != nil { + g.cbOnDisconnect(err) + } + }) + + iosk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) { + if g.cbRead != nil { + g.cbRead(ev.Timestamp, ev.Stdout, ev.Stderr) + } + }) + + iosk.On(apiv1.ExecInferiorOutEvent, func(ev apiv1.ExecOutMsg) { + if g.cbInferiorRead != nil { + g.cbInferiorRead(ev.Timestamp, ev.Stdout, ev.Stderr) + } + }) + + iosk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) { + if g.cbOnExit != nil { + g.cbOnExit(ev.Code, ev.Error) + } + }) + + return 0, nil +} + +func (g *GdbXds) Close() error { + g.cbOnDisconnect = nil + g.cbOnError = nil + g.cbOnExit = nil + g.cbRead = nil + g.cbInferiorRead = nil + + 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 + + // 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{ + ID: g.prjID, + SdkID: g.sdkID, + Cmd: g.ccmd, + Args: g.aargs, + Env: g.eenv, + RPath: g.rPath, + TTY: inferiorTTY, + 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)) + 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 + } + g.cmdID = dRes["cmdID"].(string) + + return 0, nil +} + +// Cmd returns the command name +func (g *GdbXds) Cmd() string { + return g.ccmd +} + +// Args returns the list of arguments +func (g *GdbXds) Args() []string { + return g.aargs +} + +// Env returns the list of environment variables +func (g *GdbXds) Env() []string { + return g.eenv +} + +// OnError is called on a WebSocket error +func (g *GdbXds) OnError(f func(error)) { + g.cbOnError = f +} + +// OnDisconnect is called when WebSocket disconnection +func (g *GdbXds) OnDisconnect(f func(error)) { + g.cbOnDisconnect = f +} + +// OnExit calls when exit event is received +func (g *GdbXds) OnExit(f func(code int, err error)) { + g.cbOnExit = f +} + +// Read calls when a message/string event is received on stdout or stderr +func (g *GdbXds) Read(f func(timestamp, stdout, stderr string)) { + g.cbRead = f +} + +// InferiorRead calls when a message/string event is received on stdout or stderr of the debugged program (IOW inferior) +func (g *GdbXds) InferiorRead(f func(timestamp, stdout, stderr string)) { + g.cbInferiorRead = f +} + +// Write writes message/string into gdb stdin +func (g *GdbXds) Write(args ...interface{}) error { + return g.ioSock.Emit(apiv1.ExecInEvent, args...) +} + +// 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{ + 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)) +} + +//***** Private functions ***** + +func getProjectsList(c *common.HTTPClient, log *logrus.Logger, prjData []byte) (int, error) { + + folders := xdsconfig.FoldersConfig{} + errMar := json.Unmarshal(prjData, &folders) + 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) + } + } + msg += "\n" + } + + var data []byte + if err := c.HTTPGet("/sdks", &data); 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) + } + } + + 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) + } + + return 0, fmt.Errorf(msg) +} |