1 // xds-gdb: a wrapper on gdb tool for X(cross) Development System.
18 "github.com/Sirupsen/logrus"
19 "github.com/codegangsta/cli"
20 common "github.com/iotbzh/xds-common/golib"
21 "github.com/joho/godotenv"
24 var appAuthors = []cli.Author{
25 cli.Author{Name: "Sebastien Douheret", Email: "sebastien@iot.bzh"},
28 // AppName name of this application
29 var AppName = "xds-gdb"
31 // AppVersion Version of this application
33 var AppVersion = "?.?.?"
35 // AppSubVersion is the git tag id added to version string
36 // Should be set by compilation -ldflags "-X main.AppSubVersion=xxx"
38 var AppSubVersion = "unknown-dev"
41 var log = logrus.New()
42 var earlyLog = []string{}
44 // Application details
46 appCopyright = "Apache-2.0"
47 defaultLogLevel = "warning"
51 type exitResult struct {
56 // EnvVar - Environment variables used by application
63 // exitError terminates this program with the specified error
64 func exitError(code syscall.Errno, f string, a ...interface{}) {
65 err := fmt.Sprintf(f, a...)
66 fmt.Fprintf(os.Stderr, err+"\n")
72 var uri, prjID, rPath, logLevel, logFile, sdkid, confFile, gdbNative string
76 uri = "localhost:8000"
77 logLevel = defaultLogLevel
79 // Create a new App instance
82 app.Usage = "wrapper on gdb for X(cross) Development System."
83 app.Version = AppVersion + " (" + AppSubVersion + ")"
84 app.Authors = appAuthors
85 app.Copyright = appCopyright
86 app.Metadata = make(map[string]interface{})
87 app.Metadata["version"] = AppVersion
88 app.Metadata["git-tag"] = AppSubVersion
89 app.Metadata["logger"] = log
91 app.Flags = []cli.Flag{
94 Usage: "list existing xds projects",
95 Destination: &listProject,
99 appEnvVars := []EnvVar{
102 Usage: "env config file to source on startup",
103 Destination: &confFile,
106 Name: "XDS_LOGLEVEL",
107 Usage: "logging level (supported levels: panic, fatal, error, warn, info, debug)",
108 Destination: &logLevel,
112 Usage: "logging file",
113 Destination: &logFile,
116 Name: "XDS_NATIVE_GDB",
117 Usage: "use native gdb instead of remote XDS server",
118 Destination: &gdbNative,
121 Name: "XDS_PROJECT_ID",
122 Usage: "project ID you want to build (mandatory variable)",
127 Usage: "relative path into project",
132 Usage: "Cross Sdk ID to use to build project",
136 Name: "XDS_SERVER_URL",
137 Usage: "remote XDS server url",
142 // Process gdb arguments
143 args := make([]string, len(os.Args))
145 gdbArgs := make([]string, len(os.Args))
147 // Split xds-xxx options from gdb options
148 copy(gdbArgs, os.Args[1:])
149 for idx, a := range os.Args[1:] {
150 // Specific case to print help or version of xds-gdb
152 case "--help", "-h", "--version", "-v":
156 // Detect skip option (IOW '--') to split arguments
157 copy(args, os.Args[0:idx+1])
158 copy(gdbArgs, os.Args[idx+2:])
164 // Parse gdb arguments to detect:
165 // --tty option: used for inferior/ tty of debugged program
166 // -x/--command option: XDS env vars may be set within gdb command file
169 for idx, a := range gdbArgs {
171 case strings.HasPrefix(a, "--tty="):
172 clientPty = a[len("--tty="):]
176 case strings.HasPrefix(a, "-tty"):
177 clientPty = gdbArgs[idx+1]
181 case strings.HasPrefix(a, "--command="):
182 gdbCmdFile = a[len("--command="):]
184 case a == "--command":
185 case strings.HasPrefix(a, "-x"):
186 gdbCmdFile = gdbArgs[idx+1]
190 // Source config env file
191 // (we cannot use confFile var because env variables setting is just after)
192 envMap, confFile, err := loadConfigEnvFile(os.Getenv("XDS_CONFIG"), gdbCmdFile)
194 // Only rise an error when args is not set (IOW when --help or --version is not set)
197 exitError(syscall.ENOENT, err.Error())
201 // Managed env vars and create help
202 dynDesc := "\nENVIRONMENT VARIABLES:"
203 for _, ev := range appEnvVars {
204 dynDesc += fmt.Sprintf("\n %s \t\t %s", ev.Name, ev.Usage)
205 if evVal, evExist := os.LookupEnv(ev.Name); evExist && ev.Destination != nil {
206 *ev.Destination = evVal
209 app.Description = "gdb wrapper for X(cross) Development System\n"
210 app.Description += "\n"
211 app.Description += " Two debugging models are supported:\n"
212 app.Description += " - xds remote debugging requiring an XDS server and allowing cross debug\n"
213 app.Description += " - native debugging\n"
214 app.Description += " By default xds remote debug is used and you need to define XDS_NATIVE_GDB to\n"
215 app.Description += " use native gdb debug mode instead.\n"
216 app.Description += "\n"
217 app.Description += " xds-gdb configuration (see variables list below) can be set using:\n"
218 app.Description += " - a config file (XDS_CONFIG)\n"
219 app.Description += " - or environment variables\n"
220 app.Description += " - or by setting variables within gdb ini file (commented line including :XDS-ENV: tag)\n"
221 app.Description += " Example of gdb ini file where we define project and sdk ID:\n"
222 app.Description += " # :XDS-ENV: XDS_PROJECT_ID=IW7B4EE-DBY4Z74_myProject\n"
223 app.Description += " # :XDS-ENV: XDS_SDK_ID=poky-agl_aarch64_3.99.1+snapshot\n"
224 app.Description += "\n"
225 app.Description += dynDesc + "\n"
228 app.Action = func(ctx *cli.Context) error {
230 curDir, _ := os.Getwd()
232 // Set logger level, formatter and log file
233 if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
234 msg := fmt.Sprintf("Invalid log level : \"%v\"\n", logLevel)
235 return cli.NewExitError(msg, int(syscall.EINVAL))
237 log.Formatter = &logrus.TextFormatter{}
239 // Always log into a file
241 logFile = "/tmp/xds-gdb.log"
243 fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
245 msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
246 return cli.NewExitError(msgErr, int(syscall.EPERM))
250 // Build env variables
252 for k, v := range envMap {
253 env = append(env, k+"="+v)
256 // Create cross or native gdb interface
259 gdb = NewGdbNative(log, gdbArgs, env)
261 gdb = NewGdbXds(log, gdbArgs, env)
262 gdb.SetConfig("uri", uri)
263 gdb.SetConfig("prjID", prjID)
264 gdb.SetConfig("sdkID", sdkid)
265 gdb.SetConfig("rPath", rPath)
266 gdb.SetConfig("listProject", listProject)
270 for _, msg := range earlyLog {
275 log.Infof("Original arguments: %v", os.Args)
276 log.Infof("Current directory : %v", curDir)
277 log.Infof("Use confFile : '%s'", confFile)
278 log.Infof("Execute : /exec %v %v", gdb.Cmd(), gdb.Args())
280 // Properly report invalid init file error
281 gdbCommandFileError := ""
282 for i, a := range gdbArgs {
284 gdbCommandFileError = gdbArgs[i+1] + ": No such file or directory."
286 } else if strings.HasPrefix(a, "--command=") {
287 gdbCommandFileError = strings.TrimLeft(a, "--command=") + ": No such file or directory."
291 log.Infof("Add detection of error: <%s>", gdbCommandFileError)
293 // Init gdb subprocess management
294 if code, err := gdb.Init(); err != nil {
295 return cli.NewExitError(err.Error(), code)
298 exitChan := make(chan exitResult, 1)
300 gdb.OnError(func(err error) {
301 fmt.Println("ERROR: ", err.Error())
304 gdb.OnDisconnect(func(err error) {
305 fmt.Println("Disconnection: ", err.Error())
306 exitChan <- exitResult{err, int(syscall.ESHUTDOWN)}
309 gdb.Read(func(timestamp, stdout, stderr string) {
311 fmt.Printf("%s", stdout)
312 log.Debugf("Recv OUT: <%s>", stdout)
315 fmt.Fprintf(os.Stderr, "%s", stderr)
316 log.Debugf("Recv ERR: <%s>", stderr)
319 // Correctly report error about init file
320 if gdbCommandFileError != "" && strings.Contains(stdout, gdbCommandFileError) {
321 fmt.Fprintf(os.Stderr, "ERROR: "+gdbCommandFileError)
322 log.Errorf("ERROR: " + gdbCommandFileError)
323 if err := gdb.SendSignal(syscall.SIGTERM); err != nil {
324 log.Errorf("Error while sending signal: %s", err.Error())
326 exitChan <- exitResult{err, int(syscall.ENOENT)}
330 gdb.OnExit(func(code int, err error) {
331 exitChan <- exitResult{err, code}
334 // Handle client tty / pts
336 log.Infoln("Client tty detected: %v\n", clientPty)
338 cpFd, err := os.OpenFile(clientPty, os.O_RDWR, 0)
340 return cli.NewExitError(err.Error(), int(syscall.EPERM))
345 /* XXX TODO - implement stdin to send data to debugged program
347 reader := bufio.NewReader(cpFd)
348 sc := bufio.NewScanner(reader)
351 iosk.Emit(apiv1.ExecInferiorInEvent, data+"\n")
352 log.Debugf("Inferior IN: <%v>", data)
355 log.Warnf("Inferior Stdin scanner exit, close stdin (err=%v)", sc.Err())
361 gdb.InferiorRead(func(timestamp, stdout, stderr string) {
363 fmt.Fprintf(cpFd, "%s", stdout)
364 log.Debugf("Inferior OUT: <%s>", stdout)
367 fmt.Fprintf(cpFd, "%s", stderr)
368 log.Debugf("Inferior ERR: <%s>", stderr)
373 // Allow to overwrite some gdb commands
374 var overwriteMap = make(map[string]string)
375 if overEnv, exist := os.LookupEnv("XDS_OVERWRITE_COMMANDS"); exist {
376 overEnvS := strings.TrimSpace(overEnv)
377 if len(overEnvS) > 0 {
378 // Extract overwrite commands from env variable
379 for _, def := range strings.Split(overEnvS, ",") {
380 if kv := strings.Split(def, ":"); len(kv) == 2 {
381 overwriteMap[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
383 return cli.NewExitError(
384 fmt.Errorf("Invalid definition in XDS_OVERWRITE_COMMANDS (%s)", def),
390 overwriteMap["-exec-run"] = "-exec-continue"
391 overwriteMap["-file-exec-and-symbols"] = "-file-exec-file"
393 log.Debugf("overwriteMap = %v", overwriteMap)
395 // Send stdin though WS
398 reader := bufio.NewReader(os.Stdin)
401 sc := bufio.NewScanner(reader)
405 // overwrite some commands
406 for key, value := range overwriteMap {
407 if strings.Contains(command, key) {
408 command = strings.Replace(command, key, value, 1)
409 log.Debugf("OVERWRITE %s -> %s", key, value)
412 gdb.Write(command + "\n")
413 log.Debugf("Send: <%v>", command)
415 log.Infof("Stdin scanner exit, close stdin (err=%v)", sc.Err())
417 // CTRL-D exited scanner, so send it explicitly
419 time.Sleep(time.Millisecond * 100)
421 if paranoia--; paranoia <= 0 {
422 msg := "Abnormal loop detected on stdin"
423 log.Errorf("Abnormal loop detected on stdin")
424 gdb.SendSignal(syscall.SIGTERM)
425 exitChan <- exitResult{fmt.Errorf(msg), int(syscall.ELOOP)}
430 // Handling all Signals
431 sigs := make(chan os.Signal, 1)
437 if err := gdb.SendSignal(sig); err != nil {
438 log.Errorf("Error while sending signal: %s", err.Error())
444 if code, err := gdb.Start(clientPty != ""); err != nil {
445 return cli.NewExitError(err.Error(), code)
450 case res := <-exitChan:
453 log.Infoln("Exit successfully")
455 if res.error != nil {
456 log.Infoln("Exit with ERROR: ", res.error.Error())
457 errStr = res.error.Error()
459 return cli.NewExitError(errStr, res.code)
467 func loadConfigEnvFile(confFile, gdbCmdFile string) (map[string]string, string, error) {
469 envMap := make(map[string]string)
471 // 1- if no confFile set, use setting from gdb command file is option
472 // --command/-x is set
473 if confFile == "" && gdbCmdFile != "" {
474 logEarly("Try extract config from gdbCmdFile: %s", gdbCmdFile)
475 confFile, err = extractEnvFromCmdFile(gdbCmdFile)
477 defer os.Remove(confFile)
480 return envMap, confFile, fmt.Errorf(err.Error())
483 // 2- search xds-gdb.env file in various locations
485 curDir, _ := os.Getwd()
486 if u, err := user.Current(); err == nil {
487 xdsEnvFile := "xds-gdb.env"
488 for _, d := range []string{
490 path.Join(curDir, "..", ".."),
491 path.Join(curDir, "../../target"),
492 path.Join(u.HomeDir, ".xds"),
494 confFile = path.Join(d, xdsEnvFile)
495 logEarly("Search config in %s", confFile)
496 if common.Exists(confFile) {
504 return envMap, "", nil
507 if !common.Exists(confFile) {
508 return envMap, confFile, fmt.Errorf("Error no env config file not found")
510 if err = godotenv.Load(confFile); err != nil {
511 return envMap, confFile, fmt.Errorf("Error loading env config file " + confFile)
513 if envMap, err = godotenv.Read(confFile); err != nil {
514 return envMap, confFile, fmt.Errorf("Error reading env config file " + confFile)
516 return envMap, confFile, nil
520 extractEnvFromCmdFile: extract xds-gdb env variable from gdb command file
521 All commented lines (#) in gdb command file that start with ':XDS-ENV:' prefix
522 will be considered as XDS env commands. For example the 3 syntaxes below
524 # :XDS-ENV: XDS_PROJECT_ID=IW7B4EE-DBY4Z74_myProject
525 #:XDS-ENV:XDS_SDK_ID=poky-agl_aarch64_3.99.1+snapshot
526 # :XDS-ENV: export XDS_SERVER_URL=localhost:8800
528 func extractEnvFromCmdFile(cmdFile string) (string, error) {
529 if !common.Exists(cmdFile) {
532 cFd, err := os.Open(cmdFile)
534 return "", fmt.Errorf("Cannot open %s : %s", cmdFile, err.Error())
539 scanner := bufio.NewScanner(cFd)
541 lines = append(lines, scanner.Text())
543 if err = scanner.Err(); err != nil {
544 return "", fmt.Errorf("Cannot parse %s : %s", cmdFile, err.Error())
547 envFile, err := ioutil.TempFile("", "xds-gdb_env.ini")
549 return "", fmt.Errorf("Error while creating temporary env file: %s", err.Error())
551 envFileName := envFile.Name()
552 defer envFile.Close()
555 for _, ln := range lines {
556 ln = strings.TrimSpace(ln)
557 if strings.HasPrefix(ln, "#") && strings.Contains(ln, ":XDS-ENV:") {
558 env := strings.SplitAfterN(ln, ":XDS-ENV:", 2)
561 if _, err := envFile.WriteString(strings.TrimSpace(env[1]) + "\n"); err != nil {
562 return "", fmt.Errorf("Error write into temporary env file: %s", err.Error())
565 log.Warnf("Error while decoding line %s", ln)
577 return envFileName, nil
580 func logEarly(format string, a ...interface{}) {
581 earlyLog = append(earlyLog, fmt.Sprintf(format, a...))