From 60974e66c57cacdc2483d74718c4bb0a993d2183 Mon Sep 17 00:00:00 2001 From: Sebastien Douheret Date: Mon, 7 Aug 2017 08:49:25 +0200 Subject: [PATCH 1/1] Initial commit Signed-off-by: Sebastien Douheret --- .gitignore | 9 + .vscode/launch.json | 57 +++++ .vscode/settings.json | 22 ++ LICENSE | 201 ++++++++++++++++++ Makefile | 121 +++++++++++ README.md | 4 + gdb-common.go | 21 ++ gdb-common_darwin.go | 42 ++++ gdb-common_linux.go | 37 ++++ gdb-common_windows.go | 7 + gdb-native.go | 164 ++++++++++++++ gdb-xds.go | 311 +++++++++++++++++++++++++++ glide.yaml | 23 ++ main.go | 578 ++++++++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1597 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 gdb-common.go create mode 100644 gdb-common_darwin.go create mode 100644 gdb-common_linux.go create mode 100644 gdb-common_windows.go create mode 100644 gdb-native.go create mode 100644 gdb-xds.go create mode 100644 glide.yaml create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07d6bd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin +tools +glide.lock +vendor +package +*.zip + +debug +__* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f047dc0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,57 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "name": "xds-gdb help", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_LOGLEVEL": "debug" + }, + "args": ["-tt", "--help", "--version"], + "showLog": false + }, + { + "name": "xds-gdb", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}" + }, + "args": ["-x", "/tmp/gdbconf.ini", "-nx"], + "showLog": false + }, + { + "name": "xds-gdb TTY", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}" + }, + "args": ["-tty", "/dev/pts27", "-nx", "-x", "${workspaceRoot}/__config/gdb-on-m3ulcb_debug_pi.ini"], + "showLog": false + }, + { + "name": "xds-gdb native", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_LOGLEVEL": "debug" + }, + "args": ["-x", "${workspaceRoot}/__config/gdb-on-localhost_debug_pi.ini"], + "showLog": false + } + + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c9bea18 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +// Place your settings in this file to overwrite default and user settings. +{ + // Configure glob patterns for excluding files and folders. + "files.exclude": { + "**/.tmp": true, + ".git": true, + "glide.lock": true, + "vendor/": true, + "debug": true, + "bin": true, + "tools": true + }, + // Words to add to dictionary for a workspace. + "cSpell.words": [ + "apiv", + "iosk", + "zhouhui", + "ldflags", + "socketio", + "xdsconfig" + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8c58555 --- /dev/null +++ b/Makefile @@ -0,0 +1,121 @@ +# Makefile used to build xds-gdb commands + +# Application Version +VERSION := 0.0.1 + + +# Retrieve git tag/commit to set sub-version string +ifeq ($(origin SUB_VERSION), undefined) + SUB_VERSION := $(shell git describe --exact-match --tags 2>/dev/null | sed 's/^v//') + ifneq ($(SUB_VERSION), ) + VERSION := $(firstword $(subst -, ,$(SUB_VERSION))) + SUB_VERSION := $(word 2,$(subst -, ,$(SUB_VERSION))) + else + SUB_VERSION := $(shell git rev-parse --short HEAD) + ifeq ($(SUB_VERSION), ) + SUB_VERSION := unknown-dev + endif + endif +endif + +HOST_GOOS=$(shell go env GOOS) +HOST_GOARCH=$(shell go env GOARCH) +ARCH=$(HOST_GOOS)-$(HOST_GOARCH) + +EXT= +ifeq ($(HOST_GOOS), windows) + EXT=.exe +endif + + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +ROOT_SRCDIR := $(patsubst %/,%,$(dir $(mkfile_path))) +BINDIR := $(ROOT_SRCDIR)/bin +ROOT_GOPRJ := $(abspath $(ROOT_SRCDIR)/../../../..) +PACKAGE_DIR := $(ROOT_SRCDIR)/package + +export GOPATH := $(shell go env GOPATH):$(ROOT_GOPRJ) +export PATH := $(PATH):$(ROOT_SRCDIR)/tools + +VERBOSE_1 := -v +VERBOSE_2 := -v -x + +# Release or Debug mode +ifeq ($(filter 1,$(RELEASE) $(REL)),) + GORELEASE= + BUILD_MODE="Debug mode" +else + # optimized code without debug info + GORELEASE= -s -w + BUILD_MODE="Release mode" +endif + +REPOPATH=github.com/iotbzh/xds-gdb +TARGET := xds-gdb + +build: $(TARGET) + +xds-gdb: vendor + @echo "### Build $@ (version $(VERSION), subversion $(SUB_VERSION)) - $(BUILD_MODE)"; + @cd $(ROOT_SRCDIR); $(BUILD_ENV_FLAGS) go build $(VERBOSE_$(V)) -i -o $(BINDIR)/$@$(EXT) -ldflags "$(GORELEASE) -X main.AppVersion=$(VERSION) -X main.AppSubVersion=$(SUB_VERSION)" . + @([ "$(HOST_GOOS)" = "linux" ] && { cd $(BINDIR) && ln -sf $@ $(subst xds-,,$@); } || { true; } ) + +test: tools/glide + go test --race $(shell ./tools/glide novendor) + +vet: tools/glide + go vet $(shell ./tools/glide novendor) + +fmt: tools/glide + go fmt $(shell ./tools/glide novendor) + +.PHONY: clean +clean: + rm -rf $(BINDIR)/* debug $(ROOT_GOPRJ)/pkg/*/$(REPOPATH) $(PACKAGE_DIR) + +distclean: clean + rm -rf $(BINDIR) tools glide.lock vendor $(ROOT_SRCDIR)/*.zip + +.PHONY: release +release: + RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile clean build + +package: clean build + @mkdir -p $(PACKAGE_DIR)/xds-gdb + @cp -a $(BINDIR)/*gdb$(EXT) $(PACKAGE_DIR)/xds-gdb + @cd $(PACKAGE_DIR) && zip --symlinks -r $(ROOT_SRCDIR)/xds-gdb_$(ARCH)-v$(VERSION)_$(SUB_VERSION).zip ./xds-gdb + +.PHONY: package-all +package-all: + @echo "# Build linux amd64..." + GOOS=linux GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + @echo "# Build windows amd64..." +# GOOS=windows GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + @echo " WARNING: build on Windows not supported for now." + @echo "# Build darwin amd64..." + GOOS=darwin GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + +vendor: tools/glide glide.yaml + ./tools/glide install --strip-vendor + +vendor/debug: vendor + (cd vendor/github.com/iotbzh && \ + rm -rf xds-common && ln -s ../../../../xds-common && \ + rm -rf xds-server && ln -s ../../../../xds-server ) + +tools/glide: + @echo "Downloading glide" + mkdir -p tools + curl --silent -L https://glide.sh/get | GOBIN=./tools sh + +help: + @echo "Main supported rules:" + @echo " build (default)" + @echo " release" + @echo " clean" + @echo " package" + @echo " distclean" + @echo "" + @echo "Influential make variables:" + @echo " V - Build verbosity {0,1,2}." + @echo " BUILD_ENV_FLAGS - Environment added to 'go build'." diff --git a/README.md b/README.md new file mode 100644 index 0000000..389c03d --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +xds-gdb: wrapper on gdb for XDS +================================= + +`xds-gdb` is a wrapper on gdb debugger for X(cross) Development System. diff --git a/gdb-common.go b/gdb-common.go new file mode 100644 index 0000000..a6984cc --- /dev/null +++ b/gdb-common.go @@ -0,0 +1,21 @@ +package main + +import "os" + +// IGDB is an interface for GDB +type IGDB interface { + Init() (int, error) + Close() error + SetConfig(name string, value interface{}) error + Start(bool) (int, error) + Cmd() string + Args() []string + Env() []string + OnError(f func(error)) + OnDisconnect(f func(error)) + OnExit(f func(int, error)) + Read(f func(timestamp, stdout, stderr string)) + InferiorRead(f func(timestamp, stdout, stderr string)) + Write(args ...interface{}) error + SendSignal(sig os.Signal) error +} diff --git a/gdb-common_darwin.go b/gdb-common_darwin.go new file mode 100644 index 0000000..d167c5d --- /dev/null +++ b/gdb-common_darwin.go @@ -0,0 +1,42 @@ +package main + +import ( + "os" + "syscall" + "unsafe" +) + +const ( + syscallEBADE = syscall.EBADEXEC + + syscall_TCGETS = 0x402c7413 + syscall_TCSETS = 0x802c7414 +) + +func fcntl(fd uintptr, cmd int, arg int) (val int, err error) { + r, _, e := syscall.Syscall(syscall.SYS_FCNTL, fd, uintptr(cmd), + uintptr(arg)) + val = int(r) + if e != 0 { + err = e + } + return +} + +func tcsetattr(fd uintptr, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} + +func tcgetattr(fd uintptr, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} diff --git a/gdb-common_linux.go b/gdb-common_linux.go new file mode 100644 index 0000000..a2f4bf6 --- /dev/null +++ b/gdb-common_linux.go @@ -0,0 +1,37 @@ +package main + +import ( + "os" + "syscall" + "unsafe" +) + +const syscallEBADE = syscall.EBADE + +func fcntl(fd uintptr, cmd int, arg int) (val int, err error) { + r, _, e := syscall.Syscall(syscall.SYS_FCNTL, fd, uintptr(cmd), + uintptr(arg)) + val = int(r) + if e != 0 { + err = e + } + return +} + +func tcsetattr(fd uintptr, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall.TCSETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} + +func tcgetattr(fd uintptr, termios *syscall.Termios) error { + r, _, e := syscall.Syscall(syscall.SYS_IOCTL, + fd, uintptr(syscall.TCGETS), uintptr(unsafe.Pointer(termios))) + if r != 0 { + return os.NewSyscallError("SYS_IOCTL", e) + } + return nil +} diff --git a/gdb-common_windows.go b/gdb-common_windows.go new file mode 100644 index 0000000..b233943 --- /dev/null +++ b/gdb-common_windows.go @@ -0,0 +1,7 @@ +package main + +import "syscall" + +const ( + syscallEBADE = syscall.EBADE +) diff --git a/gdb-native.go b/gdb-native.go new file mode 100644 index 0000000..a4e6189 --- /dev/null +++ b/gdb-native.go @@ -0,0 +1,164 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "syscall" + "time" + + "github.com/Sirupsen/logrus" + "github.com/kr/pty" +) + +type GdbNative struct { + log *logrus.Logger + ccmd string + aargs []string + eenv []string + + exeCmd *exec.Cmd + fdPty *os.File + + // callbacks + cbOnDisconnect func(error) + cbRead func(timestamp, stdout, stderr string) + cbInferiorRead func(timestamp, stdout, stderr string) + cbOnExit func(code int, err error) + + running bool +} + +// NewGdbNative creates a new instance of GdbNative +func NewGdbNative(log *logrus.Logger, args []string, env []string) *GdbNative { + return &GdbNative{ + log: log, + ccmd: "/usr/bin/gdb", + aargs: args, + eenv: env, + } +} + +// SetConfig set additional config fields +func (g *GdbNative) SetConfig(name string, value interface{}) error { + return fmt.Errorf("Unknown %s field", name) +} + +// Init initializes gdb XDS +func (g *GdbNative) Init() (int, error) { + + // Create the exec command + g.exeCmd = exec.Command(g.ccmd, g.aargs...) + + return 0, nil +} + +// Close +func (g *GdbNative) Close() error { + g.cbOnDisconnect = nil + g.cbOnExit = nil + g.cbRead = nil + g.cbInferiorRead = nil + + g.running = false + + return nil +} + +// Start sends a request to start remotely gdb within xds-server +func (g *GdbNative) Start(inferiorTTY bool) (int, error) { + var err error + + // Start pty and consequently gdb process + if g.fdPty, err = pty.Start(g.exeCmd); err != nil { + return int(syscall.ESPIPE), err + } + + g.running = true + + // Monitor gdb process EOF + go func() { + // Execute command and wait EOF + err := g.exeCmd.Wait() + g.cbOnDisconnect(err) + g.running = false + }() + + // Handle STDOUT + go func() { + sc := bufio.NewScanner(g.fdPty) + sc.Split(split) + for sc.Scan() { + if g.cbRead != nil { + g.cbRead(time.Now().String(), sc.Text(), "") + } + if !g.running { + return + } + } + }() + + return 0, nil +} + +// Cmd returns the command name +func (g *GdbNative) Cmd() string { + return g.ccmd +} + +// Args returns the list of arguments +func (g *GdbNative) Args() []string { + return g.aargs +} + +// Env returns the list of environment variables +func (g *GdbNative) Env() []string { + return g.eenv +} + +// OnError doesn't make sens +func (g *GdbNative) OnError(f func(error)) { + // nothing to do +} + +// OnDisconnect is called when stdin is disconnected +func (g *GdbNative) OnDisconnect(f func(error)) { + g.cbOnDisconnect = f +} + +// OnExit calls when exit event is received +func (g *GdbNative) 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 *GdbNative) 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 *GdbNative) InferiorRead(f func(timestamp, stdout, stderr string)) { + g.cbInferiorRead = f +} + +// Write writes message/string into gdb stdin +func (g *GdbNative) Write(args ...interface{}) error { + s := fmt.Sprint(args...) + _, err := g.fdPty.Write([]byte(s)) + return err +} + +// SendSignal is used to send a signal to remote process/gdb +func (g *GdbNative) SendSignal(sig os.Signal) error { + return g.exeCmd.Process.Signal(sig) +} + +//***** Private functions ***** + +func split(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + return len(data), data, nil +} 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) +} diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..f368862 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,23 @@ +package: github.com/iotbzh/xds-gdb +license: Apache-2.0 +owners: +- name: Sebastien Douheret + email: sebastien@iot.bzh +import: +- package: github.com/codegangsta/cli + version: ^1.19.1 +- package: github.com/Sirupsen/logrus + version: ^0.11.5 +- package: github.com/zhouhui8915/go-socket.io-client +- package: github.com/iotbzh/xds-server + subpackages: + - apiv1 + - crosssdk + - xdsconfig +- package: github.com/iotbzh/xds-common + subpackages: + - golib/common +- package: github.com/joho/godotenv + version: ^1.1.0 + subpackages: + - cmd/godotenv diff --git a/main.go b/main.go new file mode 100644 index 0000000..01b46c9 --- /dev/null +++ b/main.go @@ -0,0 +1,578 @@ +// 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 earlyLog = []string{} + +// 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") + os.Exit(int(code)) +} + +// main +func main() { + var uri, prjID, rPath, logLevel, logFile, sdkid, confFile, gdbNative string + var listProject bool + var err error + + 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 + 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": + 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) + 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() + + // Set logger level, formatter and log file + 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.Formatter = &logrus.TextFormatter{} + + // Always log into a file + if logFile == "" { + logFile = "/tmp/xds-gdb.log" + } + 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)) + } + log.Out = fdL + + // Build env variables + env := []string{} + for k, v := range envMap { + env = append(env, k+"="+v) + } + + // 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 early print + for _, msg := range earlyLog { + log.Debugf(msg) + } + + // 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 != "" { + logEarly("Try extract config from gdbCmdFile: %s", gdbCmdFile) + confFile, err = extractEnvFromCmdFile(gdbCmdFile) + if confFile != "" { + defer os.Remove(confFile) + } + if err != nil { + return envMap, confFile, fmt.Errorf(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, ".xds"), + } { + confFile := path.Join(d, xdsEnvFile) + logEarly("Search config in %s", confFile) + if common.Exists(confFile) { + break + } + } + } + } + + if confFile == "" { + 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 +} + +func logEarly(format string, a ...interface{}) { + earlyLog = append(earlyLog, fmt.Sprintf(format, a...)) +} -- 2.16.6