Skip to content
Permalink
Browse files

all: implement gdb sub-command for easy debugging

  • Loading branch information...
aykevl committed Oct 3, 2018
1 parent ef2ac09 commit b08c8a0cf0bf3c148330fa718a8124dae1b6179d
Showing with 122 additions and 3 deletions.
  1. +39 −0 colorwriter.go
  2. +73 −2 main.go
  3. +6 −0 target.go
  4. +4 −1 targets/pca10040.json
@@ -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
}
75 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 <binary>
// 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)
@@ -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,13 +33,16 @@ 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.
// Arduino).
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
@@ -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"]
}

0 comments on commit b08c8a0

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.