From b08c8a0cf0bf3c148330fa718a8124dae1b6179d Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 3 Oct 2018 19:01:25 +0200 Subject: [PATCH] all: implement gdb sub-command for easy debugging --- colorwriter.go | 39 ++++++++++++++++++++++ main.go | 75 +++++++++++++++++++++++++++++++++++++++++-- target.go | 6 ++++ targets/pca10040.json | 5 ++- 4 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 colorwriter.go diff --git a/colorwriter.go b/colorwriter.go new file mode 100644 index 00000000..958be450 --- /dev/null +++ b/colorwriter.go @@ -0,0 +1,39 @@ +package main + +import ( + "io" +) + +// ANSI escape codes for terminal colors. +const ( + TermColorReset = "\x1b[0m" + TermColorYellow = "\x1b[93m" +) + +// ColorWriter wraps an io.Writer but adds a prefix and a terminal color. +type ColorWriter struct { + Out io.Writer + Color string + Prefix string + line []byte +} + +// Write implements io.Writer, but with an added prefix and terminal color. +func (w *ColorWriter) Write(p []byte) (n int, err error) { + for _, c := range p { + if c == '\n' { + w.line = append(w.line, []byte(TermColorReset)...) + w.line = append(w.line, '\n') + // Write this line. + _, err := w.Out.Write(w.line) + w.line = w.line[:0] + w.line = append(w.line, []byte(w.Color+w.Prefix)...) + if err != nil { + return 0, err + } + } else { + w.line = append(w.line, c) + } + } + return len(p), nil +} diff --git a/main.go b/main.go index ee1bb5fc..7d03811d 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,10 @@ import ( "io/ioutil" "os" "os/exec" + "os/signal" "path/filepath" "strings" + "syscall" "github.com/aykevl/go-llvm" "github.com/aykevl/tinygo/compiler" @@ -199,6 +201,70 @@ func Flash(pkgName, target, port string, printIR, dumpSSA, debug bool, printSize }) } +// Flash a program on a microcontroller and drop into a GDB shell. +// +// Note: this command is expected to execute just before exiting, as it +// modifies global state. +func FlashGDB(pkgName, target, port string, printIR, dumpSSA bool, printSizes string) error { + spec, err := LoadTarget(target) + if err != nil { + return err + } + + if spec.GDB == "" { + return errors.New("gdb not configured in the target specification") + } + + debug := true // always enable debug symbols + return Compile(pkgName, "", spec, printIR, dumpSSA, debug, printSizes, func(tmppath string) error { + if len(spec.OCDDaemon) != 0 { + // We need a separate debugging daemon for on-chip debugging. + daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...) + // Make it clear which output is from the daemon. + daemon.Stderr = &ColorWriter{ + Out: os.Stderr, + Prefix: spec.OCDDaemon[0] + ": ", + Color: TermColorYellow, + } + // Make sure the daemon doesn't receive Ctrl-C that is intended for + // GDB (to break the currently executing program). + // https://stackoverflow.com/a/35435038/559350 + daemon.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Pgid: 0, + } + // Start now, and kill it on exit. + daemon.Start() + defer func() { + daemon.Process.Signal(os.Interrupt) + // Maybe we should send a .Kill() after x seconds? + daemon.Wait() + }() + } + + // Ignore Ctrl-C, it must be passed on to GDB. + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for range c { + } + }() + + // Construct and execute a gdb command. + // By default: gdb -ex run + // Exit GDB with Ctrl-D. + params := []string{tmppath} + for _, cmd := range spec.GDBCmds { + params = append(params, "-ex", cmd) + } + cmd := exec.Command(spec.GDB, params...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + }) +} + // Run the specified package directly (using JIT or interpretation). func Run(pkgName string) error { config := compiler.Config{ @@ -284,13 +350,18 @@ func main() { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } - case "flash": + case "flash", "gdb": if *outpath != "" { fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.") usage() os.Exit(1) } - err := Flash(flag.Arg(0), *target, *port, *printIR, *dumpSSA, !*nodebug, *printSize) + var err error + if command == "flash" { + err = Flash(flag.Arg(0), *target, *port, *printIR, *dumpSSA, !*nodebug, *printSize) + } else { + err = FlashGDB(flag.Arg(0), *target, *port, *printIR, *dumpSSA, *printSize) + } if err != nil { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) diff --git a/target.go b/target.go index 8f7a44cc..eb4f48b7 100644 --- a/target.go +++ b/target.go @@ -20,6 +20,9 @@ type TargetSpec struct { PreLinkArgs []string `json:"pre-link-args"` Objcopy string `json:"objcopy"` Flasher string `json:"flash"` + OCDDaemon []string `json:"ocd-daemon"` + GDB string `json:"gdb"` + GDBCmds []string `json:"gdb-initial-cmds"` } // Load a target specification @@ -30,6 +33,8 @@ func LoadTarget(target string) (*TargetSpec, error) { Linker: "cc", PreLinkArgs: []string{"-no-pie"}, // WARNING: clang < 5.0 requires -nopie Objcopy: "objcopy", + GDB: "gdb", + GDBCmds: []string{"run"}, } // See whether there is a target specification for this target (e.g. @@ -37,6 +42,7 @@ func LoadTarget(target string) (*TargetSpec, error) { path := filepath.Join(sourceDir(), "targets", strings.ToLower(target)+".json") if fp, err := os.Open(path); err == nil { defer fp.Close() + *spec = TargetSpec{} // reset all fields err := json.NewDecoder(fp).Decode(spec) if err != nil { return nil, err diff --git a/targets/pca10040.json b/targets/pca10040.json index 8aec981d..6a21f048 100644 --- a/targets/pca10040.json +++ b/targets/pca10040.json @@ -4,5 +4,8 @@ "linker": "arm-none-eabi-gcc", "pre-link-args": ["-nostdlib", "-nostartfiles", "-mcpu=cortex-m4", "-mthumb", "-T", "targets/nrf52.ld", "-Wl,--gc-sections", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections", "-Os", "-DNRF52832_XXAA", "-Ilib/CMSIS/CMSIS/Include", "lib/nrfx/mdk/system_nrf52.c", "src/device/nrf/nrf52.s"], "objcopy": "arm-none-eabi-objcopy", - "flash": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset" + "flash": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", + "ocd-daemon": ["openocd", "-f", "interface/jlink.cfg", "-c", "transport select swd", "-f", "target/nrf51.cfg"], + "gdb": "arm-none-eabi-gdb", + "gdb-initial-cmds": ["target remote :3333", "monitor halt", "load", "monitor reset", "c"] }