summaryrefslogtreecommitdiffstats
path: root/main.go
blob: 4302e9c9fb9afddbd9c233ce1516b570ff6c8598 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149

@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
/*
 * Copyright (C) 2016 "IoT.bzh"
 * Author Fulup Ar Foll <fulup@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, something express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Reference:
 *   Json load using json_unpack https://jansson.readthedocs.io/en/2.9/apiref.html#parsing-and-validating-values
 */

#ifndef _CTL_CONFIG_INCLUDE_
#define _CTL_CONFIG_INCLUDE_

#ifdef __cplusplus
extern "C" {
#endif

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "ctl-plugin.h"
#include <filescan-utils.h>
#include <wrap-json.h>

#ifdef CONTROL_SUPPORT_LUA
  #include "ctl-lua.h"
#endif

#ifndef CONTROL_MAXPATH_LEN
  #define CONTROL_MAXPATH_LEN 255
#endif

#ifndef CONTROL_CONFIG_PRE
  #define CONTROL_CONFIG_PRE "onload"
#endif

#ifndef CTL_PLUGIN_EXT
  #define CTL_PLUGIN_EXT ".ctlso"
  #define CTL_SCRIPT_EXT ".lua"
#endif

#define LUA_ACTION_PREFIX "lua://"
#define API_ACTION_PREFIX "api://"
#define PLUGIN_ACTION_PREFIX "plugin://"

typedef struct ConfigSectionS {
  const char *key;
  const char *uid;
  const char *info;
  const char *prefix;
  int (*loadCB)(AFB_ApiT apihandle, struct ConfigSectionS *section, json_object *sectionJ);
  void *handle;
  CtlActionT *actions;
} CtlSectionT;

typedef struct {
    const char *api;
    const char *uid;
    const char *info;
    const char *version;
    const char *author;
    const char *date;
    const char *prefix;
    json_object *configJ;
    json_object *requireJ;
    CtlSectionT *sections;
    void *external;
} CtlConfigT;

// This should not be global as application may want to define their own sections
typedef enum {
  CTL_
// xds-gdb: a wrapper on gdb tool for X(cross) Development System.
package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"os/user"
	"syscall"
	"time"

	"strings"

	"path"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	common "github.com/iotbzh/xds-common/golib"
	"github.com/joho/godotenv"
)

var appAuthors = []cli.Author{
	cli.Author{Name: "Sebastien Douheret", Email: "sebastien@iot.bzh"},
}

// AppName name of this application
var AppName = "xds-gdb"

// AppVersion Version of this application
// (set by Makefile)
var AppVersion = "?.?.?"

// AppSubVersion is the git tag id added to version string
// Should be set by compilation -ldflags "-X main.AppSubVersion=xxx"
// (set by Makefile)
var AppSubVersion = "unknown-dev"

// Create logger
var log = logrus.New()
var logFileInitial = "/tmp/xds-gdb.log"

// Application details
const (
	appCopyright    = "Apache-2.0"
	defaultLogLevel = "warning"
)

// Exit events
type exitResult struct {
	error error
	code  int
}

// EnvVar - Environment variables used by application
type EnvVar struct {
	Name        string
	Usage       string
	Destination *string
}

// exitError terminates this program with the specified error
func exitError(code syscall.Errno, f string, a ...interface{}) {
	err := fmt.Sprintf(f, a...)
	fmt.Fprintf(os.Stderr, err+"\n")
	log.Debugf("Exit: code=%v, err=%s", code, err)

	os.Exit(int(code))
}

// main
func main() {
	var uri, prjID, rPath, logLevel, logFile, sdkid, confFile, gdbNative string
	var listProject bool
	var err error

	// Init Logger and set temporary file and level for the 1st part
	// IOW while XDS_LOGLEVEL and XDS_LOGFILE options are not parsed
	logFile = logFileInitial
	fdL, err := os.OpenFile(logFileInitial, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
	if err != nil {
		msgErr := fmt.Sprintf("Cannot create log file %s", logFileInitial)
		exitError(syscall.EPERM, msgErr)
	}
	log.Formatter = &logrus.TextFormatter{}
	log.Out = fdL
	log.Level = logrus.DebugLevel

	uri = "localhost:8000"
	logLevel = defaultLogLevel

	// Create a new App instance
	app := cli.NewApp()
	app.Name = AppName
	app.Usage = "wrapper on gdb for X(cross) Development System."
	app.Version = AppVersion + " (" + AppSubVersion + ")"
	app.Authors = appAuthors
	app.Copyright = appCopyright
	app.Metadata = make(map[string]interface{})
	app.Metadata["version"] = AppVersion
	app.Metadata["git-tag"] = AppSubVersion
	app.Metadata["logger"] = log

	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:        "list, ls",
			Usage:       "list existing xds projects",
			Destination: &listProject,
		},
	}

	appEnvVars := []EnvVar{
		EnvVar{
			Name:        "XDS_CONFIG",
			Usage:       "env config file to source on startup",
			Destination: &confFile,
		},
		EnvVar{
			Name:        "XDS_LOGLEVEL",
			Usage:       "logging level (supported levels: panic, fatal, error, warn, info, debug)",
			Destination: &logLevel,
		},
		EnvVar{
			Name:        "XDS_LOGFILE",
			Usage:       "logging file",
			Destination: &logFile,
		},
		EnvVar{
			Name:        "XDS_NATIVE_GDB",
			Usage:       "use native gdb instead of remote XDS server",
			Destination: &gdbNative,
		},
		EnvVar{
			Name:        "XDS_PROJECT_ID",
			Usage:       "project ID you want to build (mandatory variable)",
			Destination: &prjID,
		},
		EnvVar{
			Name:        "XDS_RPATH",
			Usage:       "relative path into project",
			Destination: &rPath,
		},
		EnvVar{
			Name:        "XDS_SDK_ID",
			Usage:       "Cross Sdk ID to use to build project",
			Destination: &sdkid,
		},
		EnvVar{
			Name:        "XDS_SERVER_URL",
			Usage:       "remote XDS server url",
			Destination: &uri,
		},
	}

	// Process gdb arguments
	log.Debugf("xds-gdb started with args: %v", os.Args)
	args := make([]string, len(os.Args))
	args[0] = os.Args[0]
	gdbArgs := make([]string, len(os.Args))

	// Split xds-xxx options from gdb options
	copy(gdbArgs, os.Args[1:])
	for idx, a := range os.Args[1:] {
		// Specific case to print help or version of xds-gdb
		switch a {
		case "--help", "-h", "--version", "-v", "--list", "-ls":
			args[1] = a
			goto endloop
		case "--":
			// Detect skip option (IOW '--') to split arguments
			copy(args, os.Args[0:idx+1])
			copy(gdbArgs, os.Args[idx+2:])
			goto endloop
		}
	}
endloop:

	// Parse gdb arguments to detect:
	//  --tty option: used for inferior/ tty of debugged program
	//  -x/--command option: XDS env vars may be set within gdb command file
	clientPty := ""
	gdbCmdFile := ""
	for idx, a := range gdbArgs {
		switch {
		case strings.HasPrefix(a, "--tty="):
			clientPty = a[len("--tty="):]
			gdbArgs[idx] = ""

		case a == "--tty":
		case strings.HasPrefix(a, "-tty"):
			clientPty = gdbArgs[idx+1]
			gdbArgs[idx] = ""
			gdbArgs[idx+1] = ""

		case strings.HasPrefix(a, "--command="):
			gdbCmdFile = a[len("--command="):]

		case a == "--command":
		case strings.HasPrefix(a, "-x"):
			gdbCmdFile = gdbArgs[idx+1]
		}
	}

	// Source config env file
	// (we cannot use confFile var because env variables setting is just after)
	envMap, confFile, err := loadConfigEnvFile(os.Getenv("XDS_CONFIG"), gdbCmdFile)
	log.Infof("Load env config: envMap=%v, confFile=%v, err=%v", envMap, confFile, err)

	// Only rise an error when args is not set (IOW when --help or --version is not set)
	if len(args) == 1 {
		if err != nil {
			exitError(syscall.ENOENT, err.Error())
		}
	}

	// Managed env vars and create help
	dynDesc := "\nENVIRONMENT VARIABLES:"
	for _, ev := range appEnvVars {
		dynDesc += fmt.Sprintf("\n %s \t\t %s", ev.Name, ev.Usage)
		if evVal, evExist := os.LookupEnv(ev.Name); evExist && ev.Destination != nil {
			*ev.Destination = evVal
		}
	}
	app.Description = "gdb wrapper for X(cross) Development System\n"
	app.Description += "\n"
	app.Description += " Two debugging models are supported:\n"
	app.Description += "  - xds remote debugging requiring an XDS server and allowing cross debug\n"
	app.Description += "  - native debugging\n"
	app.Description += " By default xds remote debug is used and you need to define XDS_NATIVE_GDB to\n"
	app.Description += " use native gdb debug mode instead.\n"
	app.Description += "\n"
	app.Description += " xds-gdb configuration (see variables list below) can be set using:\n"
	app.Description += "  - a config file (XDS_CONFIG)\n"
	app.Description += "  - or environment variables\n"
	app.Description += "  - or by setting variables within gdb ini file (commented line including :XDS-ENV: tag)\n"
	app.Description += "    Example of gdb ini file where we define project and sdk ID:\n"
	app.Description += "     # :XDS-ENV: XDS_PROJECT_ID=IW7B4EE-DBY4Z74_myProject\n"
	app.Description += "     # :XDS-ENV: XDS_SDK_ID=poky-agl_aarch64_3.99.1+snapshot\n"
	app.Description += "\n"
	app.Description += dynDesc + "\n"

	// only one action
	app.Action = func(ctx *cli.Context) error {
		var err error
		curDir, _ := os.Getwd()

		// Build env variables
		env := []string{}
		for k, v := range envMap {
			env = append(env, k+"="+v)
		}

		// Now set logger level and log file to correct/env var settings
		if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
			msg := fmt.Sprintf("Invalid log level : \"%v\"\n", logLevel)
			return cli.NewExitError(msg, int(syscall.EINVAL))
		}
		log.Infof("Switch log level to %s", logLevel)

		if logFile != logFileInitial {
			log.Infof("Switch logging to log file %s", logFile)

			fdL, err := os.OpenFile(logFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
			if err != nil {
				msgErr := fmt.Sprintf("Cannot create log file %s", logFile)
				return cli.NewExitError(msgErr, int(syscall.EPERM))
			}
			defer fdL.Close()
			log.Out = fdL
		}

		// Create cross or native gdb interface
		var gdb IGDB
		if gdbNative != "" {
			gdb = NewGdbNative(log, gdbArgs, env)
		} else {
			gdb = NewGdbXds(log, gdbArgs, env)
			gdb.SetConfig("uri", uri)
			gdb.SetConfig("prjID", prjID)
			gdb.SetConfig("sdkID", sdkid)
			gdb.SetConfig("rPath", rPath)
			gdb.SetConfig("listProject", listProject)
		}

		// Log useful info
		log.Infof("Original arguments: %v", os.Args)
		log.Infof("Current directory : %v", curDir)
		log.Infof("Use confFile      : '%s'", confFile)
		log.Infof("Execute           : /exec %v %v", gdb.Cmd(), gdb.Args())

		// Properly report invalid init file error
		gdbCommandFileError := ""
		for i, a := range gdbArgs {
			if a == "-x" {
				gdbCommandFileError = gdbArgs[i+1] + ": No such file or directory."
				break
			} else if strings.HasPrefix(a, "--command=") {
				gdbCommandFileError = strings.TrimLeft(a, "--command=") + ": No such file or directory."
				break
			}
		}
		log.Infof("Add detection of error: <%s>", gdbCommandFileError)

		// Init gdb subprocess management
		if code, err := gdb.Init(); err != nil {
			return cli.NewExitError(err.Error(), code)
		}

		exitChan := make(chan exitResult, 1)

		gdb.OnError(func(err error) {
			fmt.Println("ERROR: ", err.Error())
		})

		gdb.OnDisconnect(func(err error) {
			fmt.Println("Disconnection: ", err.Error())
			exitChan <- exitResult{err, int(syscall.ESHUTDOWN)}
		})

		gdb.Read(func(timestamp, stdout, stderr string) {
			if stdout != "" {
				fmt.Printf("%s", stdout)
				log.Debugf("Recv OUT: <%s>", stdout)
			}
			if stderr != "" {
				fmt.Fprintf(os.Stderr, "%s", stderr)
				log.Debugf("Recv ERR: <%s>", stderr)
			}

			// Correctly report error about init file
			if gdbCommandFileError != "" && strings.Contains(stdout, gdbCommandFileError) {
				fmt.Fprintf(os.Stderr, "ERROR: "+gdbCommandFileError)
				log.Errorf("ERROR: " + gdbCommandFileError)
				if err := gdb.SendSignal(syscall.SIGTERM); err != nil {
					log.Errorf("Error while sending signal: %s", err.Error())
				}
				exitChan <- exitResult{err, int(syscall.ENOENT)}
			}
		})

		gdb.OnExit(func(code int, err error) {
			exitChan <- exitResult{err, code}
		})

		// Handle client tty / pts
		if clientPty != "" {
			log.Infoln("Client tty detected: %v\n", clientPty)

			cpFd, err := os.OpenFile(clientPty, os.O_RDWR, 0)
			if err != nil {
				return cli.NewExitError(err.Error(), int(syscall.EPERM))
			}
			defer cpFd.Close()

			// client tty stdin
			/* XXX TODO - implement stdin to send data to debugged program
			go func() {
				reader := bufio.NewReader(cpFd)
				sc := bufio.NewScanner(reader)
				for sc.Scan() {
					data := sc.Text()
					iosk.Emit(apiv1.ExecInferiorInEvent, data+"\n")
					log.Debugf("Inferior IN: <%v>", data)
				}
				if sc.Err() != nil {
					log.Warnf("Inferior Stdin scanner exit, close stdin (err=%v)", sc.Err())
				}
			}()
			*/

			// client tty stdout
			gdb.InferiorRead(func(timestamp, stdout, stderr string) {
				if stdout != "" {
					fmt.Fprintf(cpFd, "%s", stdout)
					log.Debugf("Inferior OUT: <%s>", stdout)
				}
				if stderr != "" {
					fmt.Fprintf(cpFd, "%s", stderr)
					log.Debugf("Inferior ERR: <%s>", stderr)
				}
			})
		}

		// Allow to overwrite some gdb commands
		var overwriteMap = make(map[string]string)
		if overEnv, exist := os.LookupEnv("XDS_OVERWRITE_COMMANDS"); exist {
			overEnvS := strings.TrimSpace(overEnv)
			if len(overEnvS) > 0 {
				// Extract overwrite commands from env variable
				for _, def := range strings.Split(overEnvS, ",") {
					if kv := strings.Split(def, ":"); len(kv) == 2 {
						overwriteMap[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
					} else {
						return cli.NewExitError(
							fmt.Errorf("Invalid definition in XDS_OVERWRITE_COMMANDS (%s)", def),
							int(syscall.EINVAL))
					}
				}
			}
		} else {
			overwriteMap["-exec-run"] = "-exec-continue"
			overwriteMap["-file-exec-and-symbols"] = "-file-exec-file"
		}
		log.Debugf("overwriteMap = %v", overwriteMap)

		// Send stdin though WS
		go func() {
			paranoia := 600
			reader := bufio.NewReader(os.Stdin)

			for {
				sc := bufio.NewScanner(reader)
				for sc.Scan() {
					command := sc.Text()

					// overwrite some commands
					for key, value := range overwriteMap {
						if strings.Contains(command, key) {
							command = strings.Replace(command, key, value, 1)
							log.Debugf("OVERWRITE %s -> %s", key, value)
						}
					}
					gdb.Write(command + "\n")
					log.Debugf("Send: <%v>", command)
				}
				log.Infof("Stdin scanner exit, close stdin (err=%v)", sc.Err())

				// CTRL-D exited scanner, so send it explicitly
				gdb.Write("\x04")
				time.Sleep(time.Millisecond * 100)

				if paranoia--; paranoia <= 0 {
					msg := "Abnormal loop detected on stdin"
					log.Errorf("Abnormal loop detected on stdin")
					gdb.SendSignal(syscall.SIGTERM)
					exitChan <- exitResult{fmt.Errorf(msg), int(syscall.ELOOP)}
				}
			}
		}()

		// Handling all Signals
		sigs := make(chan os.Signal, 1)
		signal.Notify(sigs)

		go func() {
			for {
				sig := <-sigs
				if err := gdb.SendSignal(sig); err != nil {
					log.Errorf("Error while sending signal: %s", err.Error())
				}
			}
		}()

		// Start gdb
		if code, err := gdb.Start(clientPty != ""); err != nil {
			return cli.NewExitError(err.Error(), code)
		}

		// Wait exit
		select {
		case res := <-exitChan:
			errStr := ""
			if res.code == 0 {
				log.Infoln("Exit successfully")
			}
			if res.error != nil {
				log.Infoln("Exit with ERROR: ", res.error.Error())
				errStr = res.error.Error()
			}
			return cli.NewExitError(errStr, res.code)
		}
	}

	app.Run(args)
}

// loadConfigEnvFile
func loadConfigEnvFile(confFile, gdbCmdFile string) (map[string]string, string, error) {
	var err error
	envMap := make(map[string]string)

	// 1- if no confFile set, use setting from gdb command file is option
	//    --command/-x is set
	if confFile == "" && gdbCmdFile != "" {
		log.Infof("Try extract config from gdbCmdFile: %s", gdbCmdFile)
		confFile, err = extractEnvFromCmdFile(gdbCmdFile)
		if confFile != "" {
			defer os.Remove(confFile)
		}
		if err != nil {
			log.Infof("Extraction from gdbCmdFile failed: %v", err.Error())
		}
	}
	// 2- search xds-gdb.env file in various locations
	if confFile == "" {
		curDir, _ := os.Getwd()
		if u, err := user.Current(); err == nil {
			xdsEnvFile := "xds-gdb.env"
			for _, d := range []string{
				path.Join(curDir),
				path.Join(curDir, ".."),
				path.Join(curDir, "target"),
				path.Join(u.HomeDir, ".config", "xds"),
			} {
				confFile = path.Join(d, xdsEnvFile)
				log.Infof("Search config in %s", confFile)
				if common.Exists(confFile) {
					break
				}
			}
		}
	}

	if confFile == "" {
		log.Infof("NO valid conf file found!")
		return envMap, "", nil
	}

	if !common.Exists(confFile) {
		return envMap, confFile, fmt.Errorf("Error no env config file not found")
	}
	if err = godotenv.Load(confFile); err != nil {
		return envMap, confFile, fmt.Errorf("Error loading env config file " + confFile)
	}
	if envMap, err = godotenv.Read(confFile); err != nil {
		return envMap, confFile, fmt.Errorf("Error reading env config file " + confFile)
	}

	return envMap, confFile, nil
}

/*
 extractEnvFromCmdFile: extract xds-gdb env variable from gdb command file
  All commented lines (#) in gdb command file that start with ':XDS-ENV:' prefix
  will be considered as XDS env commands. For example the 3 syntaxes below
  are supported:
  # :XDS-ENV: XDS_PROJECT_ID=IW7B4EE-DBY4Z74_myProject
  #:XDS-ENV:XDS_SDK_ID=poky-agl_aarch64_3.99.1+snapshot
  # :XDS-ENV:  export XDS_SERVER_URL=localhost:8800
*/
func extractEnvFromCmdFile(cmdFile string) (string, error) {
	if !common.Exists(cmdFile) {
		return "", nil
	}
	cFd, err := os.Open(cmdFile)
	if err != nil {
		return "", fmt.Errorf("Cannot open %s : %s", cmdFile, err.Error())
	}
	defer cFd.Close()

	var lines []string
	scanner := bufio.NewScanner(cFd)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if err = scanner.Err(); err != nil {
		return "", fmt.Errorf("Cannot parse %s : %s", cmdFile, err.Error())
	}

	envFile, err := ioutil.TempFile("", "xds-gdb_env.ini")
	if err != nil {
		return "", fmt.Errorf("Error while creating temporary env file: %s", err.Error())
	}
	envFileName := envFile.Name()
	defer envFile.Close()

	envFound := false
	for _, ln := range lines {
		ln = strings.TrimSpace(ln)
		if strings.HasPrefix(ln, "#") && strings.Contains(ln, ":XDS-ENV:") {
			env := strings.SplitAfterN(ln, ":XDS-ENV:", 2)
			if len(env) == 2 {
				envFound = true
				if _, err := envFile.WriteString(strings.TrimSpace(env[1]) + "\n"); err != nil {
					return "", fmt.Errorf("Error write into temporary env file: %s", err.Error())
				}
			} else {
				log.Warnf("Error while decoding line %s", ln)
			}
		}
	}

	if !envFound {
		ff := envFileName
		defer os.Remove(ff)
		envFileName = ""

	}

	return envFileName, nil
}