main: implement tinygo lldb
subcommand
LLDB mostly works on most platforms, but it is still lacking in some features. For example, it doesn't seem to support RISC-V yet (coming in LLVM 12), it only partially supports AVR (no stacktraces), and it doesn't seem to support the Ctrl-C keyboard command when running a binary for another platform (e.g. with GOOS=arm64). However, it does mostly work, even on baremetal systems.
Этот коммит содержится в:
родитель
878b62bbe8
коммит
dbfaaf7c13
3 изменённых файлов: 84 добавлений и 36 удалений
|
@ -21,6 +21,7 @@ func init() {
|
||||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
commands["clang"] = []string{"clang-" + llvmMajor}
|
||||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
||||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
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
|
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
||||||
// manually set $PATH).
|
// manually set $PATH).
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
|
@ -28,6 +29,7 @@ func init() {
|
||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
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
|
// 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
|
// 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["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["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["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.
|
// Add the path to LLVM installed from ports.
|
||||||
if runtime.GOOS == "freebsd" {
|
if runtime.GOOS == "freebsd" {
|
||||||
|
@ -43,23 +46,34 @@ func init() {
|
||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
||||||
|
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func execCommand(cmdNames []string, args ...string) error {
|
// LookupCommand looks up the executable name for a given LLVM tool such as
|
||||||
for _, cmdName := range cmdNames {
|
// clang or wasm-ld. It returns the (relative) command that can be used to
|
||||||
cmd := exec.Command(cmdName, args...)
|
// invoke the tool or an error if it could not be found.
|
||||||
cmd.Stdout = os.Stdout
|
func LookupCommand(name string) (string, error) {
|
||||||
cmd.Stderr = os.Stderr
|
for _, cmdName := range commands[name] {
|
||||||
err := cmd.Run()
|
_, err := exec.LookPath(cmdName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
|
if errors.Unwrap(err) == exec.ErrNotFound {
|
||||||
// this command was not found, try the next
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
return cmdName, err
|
||||||
|
}
|
||||||
|
return cmdName, nil
|
||||||
|
}
|
||||||
|
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
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
cmd := exec.Command(name, args...)
|
||||||
}
|
cmd.Stdout = os.Stdout
|
||||||
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ func runCCompiler(flags ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile this with an external invocation of the Clang compiler.
|
// 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.
|
// 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.
|
// Fall back to external command.
|
||||||
if cmdNames, ok := commands[linker]; ok {
|
if _, ok := commands[linker]; ok {
|
||||||
return execCommand(cmdNames, flags...)
|
return execCommand(linker, flags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
|
|
74
main.go
74
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
|
// Debug compiles and flashes a program to a microcontroller (just like Flash)
|
||||||
// Flash) but instead of resetting the target, it will drop into a GDB shell.
|
// but instead of resetting the target, it will drop into a debug shell like GDB
|
||||||
// You can then set breakpoints, run the GDB `continue` command to start, hit
|
// or LLDB. You can then set breakpoints, run the `continue` command to start,
|
||||||
// Ctrl+C to break the running program, etc.
|
// hit Ctrl+C to break the running program, etc.
|
||||||
//
|
//
|
||||||
// Note: this command is expected to execute just before exiting, as it
|
// Note: this command is expected to execute just before exiting, as it
|
||||||
// modifies global state.
|
// 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)
|
config, err := builder.NewConfig(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -460,6 +466,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the GDB server, if necessary.
|
// Run the GDB server, if necessary.
|
||||||
|
port := ""
|
||||||
var gdbCommands []string
|
var gdbCommands []string
|
||||||
var daemon *exec.Cmd
|
var daemon *exec.Cmd
|
||||||
switch gdbInterface {
|
switch gdbInterface {
|
||||||
|
@ -471,9 +478,11 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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":
|
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.
|
// We need a separate debugging daemon for on-chip debugging.
|
||||||
args, err := config.OpenOCDConfiguration()
|
args, err := config.OpenOCDConfiguration()
|
||||||
|
@ -492,7 +501,8 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro
|
||||||
daemon.Stderr = w
|
daemon.Stderr = w
|
||||||
}
|
}
|
||||||
case "jlink":
|
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.
|
// We need a separate debugging daemon for on-chip debugging.
|
||||||
daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice)
|
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
|
daemon.Stderr = w
|
||||||
}
|
}
|
||||||
case "qemu":
|
case "qemu":
|
||||||
gdbCommands = append(gdbCommands, "target extended-remote :1234")
|
port = ":1234"
|
||||||
|
|
||||||
// Run in an emulator.
|
// Run in an emulator.
|
||||||
args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S")
|
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.Stdout = os.Stdout
|
||||||
daemon.Stderr = os.Stderr
|
daemon.Stderr = os.Stderr
|
||||||
case "qemu-user":
|
case "qemu-user":
|
||||||
gdbCommands = append(gdbCommands, "target extended-remote :1234")
|
port = ":1234"
|
||||||
|
|
||||||
// Run in an emulator.
|
// Run in an emulator.
|
||||||
args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary)
|
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.Stdout = os.Stdout
|
||||||
daemon.Stderr = os.Stderr
|
daemon.Stderr = os.Stderr
|
||||||
case "mgba":
|
case "mgba":
|
||||||
gdbCommands = append(gdbCommands, "target extended-remote :2345")
|
port = ":2345"
|
||||||
|
|
||||||
// Run in an emulator.
|
// Run in an emulator.
|
||||||
args := append(config.Target.Emulator[1:], result.Binary, "-g")
|
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.Stdout = os.Stdout
|
||||||
daemon.Stderr = os.Stderr
|
daemon.Stderr = os.Stderr
|
||||||
case "simavr":
|
case "simavr":
|
||||||
gdbCommands = append(gdbCommands, "target extended-remote :1234")
|
port = ":1234"
|
||||||
|
|
||||||
// Run in an emulator.
|
// Run in an emulator.
|
||||||
args := append(config.Target.Emulator[1:], "-g", result.Binary)
|
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 <binary>
|
// By default: gdb -ex run <binary>
|
||||||
// Exit GDB with Ctrl-D.
|
// Exit the debugger with Ctrl-D.
|
||||||
params := []string{result.Binary}
|
params := []string{result.Binary}
|
||||||
|
switch debugger {
|
||||||
|
case "gdb":
|
||||||
|
if port != "" {
|
||||||
|
params = append(params, "-ex", "target extended-remote "+port)
|
||||||
|
}
|
||||||
for _, cmd := range gdbCommands {
|
for _, cmd := range gdbCommands {
|
||||||
params = append(params, "-ex", cmd)
|
params = append(params, "-ex", cmd)
|
||||||
}
|
}
|
||||||
cmd := executeCommand(config.Options, gdb, params...)
|
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, cmdName, params...)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
err := cmd.Run()
|
err := cmd.Run()
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
})
|
})
|
||||||
|
@ -1251,18 +1285,18 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
}
|
}
|
||||||
case "flash", "gdb":
|
case "flash", "gdb", "lldb":
|
||||||
pkgName := filepath.ToSlash(flag.Arg(0))
|
pkgName := filepath.ToSlash(flag.Arg(0))
|
||||||
if command == "flash" {
|
if command == "flash" {
|
||||||
err := Flash(pkgName, *port, options)
|
err := Flash(pkgName, *port, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
} else {
|
} else {
|
||||||
if !options.Debug {
|
if !options.Debug {
|
||||||
fmt.Fprintln(os.Stderr, "Debug disabled while running gdb?")
|
fmt.Fprintln(os.Stderr, "Debug disabled while running debugger?")
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
err := FlashGDB(pkgName, *ocdOutput, options)
|
err := Debug(command, pkgName, *ocdOutput, options)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
}
|
}
|
||||||
case "run":
|
case "run":
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче