diff --git a/builder/commands.go b/builder/commands.go index e96aca67..9b167c6f 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -21,6 +21,7 @@ func init() { commands["clang"] = []string{"clang-" + llvmMajor} commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} + commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"} // Add the path to a Homebrew-installed LLVM for ease of use (no need to // manually set $PATH). if runtime.GOOS == "darwin" { @@ -28,6 +29,7 @@ func init() { commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") + commands["lldb"] = append(commands["lldb"], prefix+"lldb") } // Add the path for when LLVM was installed with the installer from // llvm.org, which by default doesn't add LLVM to the $PATH environment @@ -36,6 +38,7 @@ func init() { commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe") commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe") commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe") + commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe") } // Add the path to LLVM installed from ports. if runtime.GOOS == "freebsd" { @@ -43,23 +46,34 @@ func init() { commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor) commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld") commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld") + commands["lldb"] = append(commands["lldb"], prefix+"lldb") } } -func execCommand(cmdNames []string, args ...string) error { - for _, cmdName := range cmdNames { - cmd := exec.Command(cmdName, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() +// LookupCommand looks up the executable name for a given LLVM tool such as +// clang or wasm-ld. It returns the (relative) command that can be used to +// invoke the tool or an error if it could not be found. +func LookupCommand(name string) (string, error) { + for _, cmdName := range commands[name] { + _, err := exec.LookPath(cmdName) if err != nil { - if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") { - // this command was not found, try the next + if errors.Unwrap(err) == exec.ErrNotFound { continue } - return err + return cmdName, err } - return nil + return cmdName, nil } - return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " ")) + return "", errors.New("%#v: none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) +} + +func execCommand(name string, args ...string) error { + name, err := LookupCommand(name) + if err != nil { + return err + } + cmd := exec.Command(name, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() } diff --git a/builder/tools.go b/builder/tools.go index 0b6cd37f..53d89bf0 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -24,7 +24,7 @@ func runCCompiler(flags ...string) error { } // Compile this with an external invocation of the Clang compiler. - return execCommand(commands["clang"], flags...) + return execCommand("clang", flags...) } // link invokes a linker with the given name and flags. @@ -38,8 +38,8 @@ func link(linker string, flags ...string) error { } // Fall back to external command. - if cmdNames, ok := commands[linker]; ok { - return execCommand(cmdNames, flags...) + if _, ok := commands[linker]; ok { + return execCommand(linker, flags...) } cmd := exec.Command(linker, flags...) diff --git a/main.go b/main.go index de148499..edad0aae 100644 --- a/main.go +++ b/main.go @@ -417,19 +417,25 @@ func Flash(pkgName, port string, options *compileopts.Options) error { }) } -// FlashGDB compiles and flashes a program to a microcontroller (just like -// Flash) but instead of resetting the target, it will drop into a GDB shell. -// You can then set breakpoints, run the GDB `continue` command to start, hit -// Ctrl+C to break the running program, etc. +// Debug compiles and flashes a program to a microcontroller (just like Flash) +// but instead of resetting the target, it will drop into a debug shell like GDB +// or LLDB. You can then set breakpoints, run the `continue` command to start, +// hit Ctrl+C to break the running program, etc. // // Note: this command is expected to execute just before exiting, as it // modifies global state. -func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) error { +func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Options) error { config, err := builder.NewConfig(options) if err != nil { return err } - gdb, err := config.Target.LookupGDB() + var cmdName string + switch debugger { + case "gdb": + cmdName, err = config.Target.LookupGDB() + case "lldb": + cmdName, err = builder.LookupCommand("lldb") + } if err != nil { return err } @@ -460,6 +466,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro } // Run the GDB server, if necessary. + port := "" var gdbCommands []string var daemon *exec.Cmd switch gdbInterface { @@ -471,9 +478,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro if err != nil { return err } - gdbCommands = append(gdbCommands, "target extended-remote "+bmpGDBPort, "monitor swdp_scan", "compare-sections", "attach 1", "load") + port = bmpGDBPort + gdbCommands = append(gdbCommands, "monitor swdp_scan", "compare-sections", "attach 1", "load") case "openocd": - gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt") + port = ":3333" + gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. args, err := config.OpenOCDConfiguration() @@ -492,7 +501,8 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stderr = w } case "jlink": - gdbCommands = append(gdbCommands, "target extended-remote :2331", "load", "monitor reset halt") + port = ":2331" + gdbCommands = append(gdbCommands, "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) @@ -507,7 +517,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stderr = w } case "qemu": - gdbCommands = append(gdbCommands, "target extended-remote :1234") + port = ":1234" // Run in an emulator. args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S") @@ -515,7 +525,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "qemu-user": - gdbCommands = append(gdbCommands, "target extended-remote :1234") + port = ":1234" // Run in an emulator. args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary) @@ -523,7 +533,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "mgba": - gdbCommands = append(gdbCommands, "target extended-remote :2345") + port = ":2345" // Run in an emulator. args := append(config.Target.Emulator[1:], result.Binary, "-g") @@ -531,7 +541,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "simavr": - gdbCommands = append(gdbCommands, "target extended-remote :1234") + port = ":1234" // Run in an emulator. args := append(config.Target.Emulator[1:], "-g", result.Binary) @@ -576,20 +586,44 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro } }() - // Construct and execute a gdb command. + // Construct and execute a gdb or lldb command. // By default: gdb -ex run - // Exit GDB with Ctrl-D. + // Exit the debugger with Ctrl-D. params := []string{result.Binary} - for _, cmd := range gdbCommands { - params = append(params, "-ex", cmd) + switch debugger { + case "gdb": + if port != "" { + params = append(params, "-ex", "target extended-remote "+port) + } + for _, cmd := range gdbCommands { + params = append(params, "-ex", cmd) + } + case "lldb": + params = append(params, "--arch", config.Triple()) + if port != "" { + if strings.HasPrefix(port, ":") { + params = append(params, "-o", "gdb-remote "+port[1:]) + } else { + return fmt.Errorf("cannot use LLDB over a gdb-remote that isn't a TCP port: %s", port) + } + } + for _, cmd := range gdbCommands { + if strings.HasPrefix(cmd, "monitor ") { + params = append(params, "-o", "process plugin packet "+cmd) + } else if cmd == "load" { + params = append(params, "-o", "target modules load --load --slide 0") + } else { + return fmt.Errorf("don't know how to convert GDB command %#v to LLDB", cmd) + } + } } - cmd := executeCommand(config.Options, gdb, params...) + cmd := executeCommand(config.Options, cmdName, params...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { - return &commandError{"failed to run gdb with", result.Binary, err} + return &commandError{"failed to run " + cmdName + " with", result.Binary, err} } return nil }) @@ -1251,18 +1285,18 @@ func main() { if err != nil { handleCompilerError(err) } - case "flash", "gdb": + case "flash", "gdb", "lldb": pkgName := filepath.ToSlash(flag.Arg(0)) if command == "flash" { err := Flash(pkgName, *port, options) handleCompilerError(err) } else { if !options.Debug { - fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?") + fmt.Fprintln(os.Stderr, "Debug disabled while running debugger?") usage() os.Exit(1) } - err := FlashGDB(pkgName, *ocdOutput, options) + err := Debug(command, pkgName, *ocdOutput, options) handleCompilerError(err) } case "run":