tinygo-org / tinygo

Go compiler for small places. Microcontrollers, WebAssembly (WASM/WASI), and command-line tools. Based on LLVM.
https://tinygo.org
Other
15.31k stars 905 forks source link

how to trap/see/debug a panic (SAMD21) #1995

Open bradleypeabody opened 3 years ago

bradleypeabody commented 3 years ago

On SAMD platforms (tested using Arduino Nano33 IoT and other similar SAMD21-based Sparkfun boards), I cannot find an easy way to find the cause of a panic. I'm concerned about panics from things like nil-pointers or array index issues. The overall problem I'm trying to solve is that as the complexity of my application rises I get occasional crashes and without some way to gather more data, it's really hard debug. Although I sometimes get partial output (see below) - an array index out of bounds, nil-pointer deref, memory exhaustion, stack overflow - I've suspected each of these as the cause but haven't found a good way to track down what is happening.

For panics, even just the simplest program demonstrates the issue:

func main() {
    defer func() {
        r := recover()
        println("recovered: ", r) // USB-CDC output
        time.Sleep(time.Millisecond * 100) // give time to ensure the UART sent
    }()
    println("started") // USB-CDC output
    time.Sleep(time.Millisecond * 100) // give time to ensure the UART sent
    panic("it all went sideways")
}

I never get the output in the defer block (just noticed https://tinygo.org/docs/reference/lang-support/#a-note-on-the-recover-builtin as I'm writing this - makes sense). I imagine the oddness here is related to LLVM coroutine stuff and how to related to the stack and panics and so forth, but I'm just wondering if there is some way to execute specific code, with at least some conext, when a panic is fired?

Also, occasionally I will get partial output from panics or fatal errors. E.g. fatal error: stack ove and then it cuts off. I believe I have seen this with regular panics as well, but am not sure - I can collect more data if it would be helpful. I'm assuming this partial output is because it hit whatever trap or halt instruction before output completed, but that's just a guess.

I'm basically just trying to find a way to know when internal errors occur and get some sort of (more) reliable behavior that I can use to debug and flush these errors out. I realize not all errors are the same and cases like out of memory and stack overflow might be harder to recover from than e.g. an array out of bounds check failing.

Here's what I've tried so far:

Any guidances or help on this would be appreciated.

aykevl commented 3 years ago

In short: use a debugger. They are meant for exactly this purpose.

I see you've tried to use it. How do you use tinygo gdb and what is the error that you're getting when you use it?

Microchip/Atmel Studio's debugger - works but doesn't map the source so I just get a disassembly showing it's blocked on a wfi ARM instruction.

You need to provide it the binary that you flashed to the chip. Otherwise it won't be able to connect it back to the source language. The firmware that's flashed on the chip does not contain any function names or other debug information.

bradleypeabody commented 3 years ago

Cool, makes sense. I typically prefer logging over debugging but I think that comes from years of writing server-side code where logging is nearly always available and reliable. But it makes sense that for MCUs it's at least as likely if not more so that a debugger will be available vs accessible log output.

I did some more fiddling and the thing that tripped me up on the debugger was not realizing I needed to specify the programmer. The example here https://tinygo.org/docs/tutorials/gdb/ doesn't explicitly say that the -programmer option is required but automatically supplied by the target in the case of the microbit. When there is no programmer specified, it looks like tinygo gdb ... runs arm-none-eabi-gdb [path/to/bin] which starts but with no debugging execution capability, and no output to clarify that the -programmer option is missing. (Just mentioning in case a doc update is warranted, not a criticism.)

Once I realized what was happening, I tried -programmer=cmsis-dap and that worked with my Atmel ICE debugging hardware, so that's all good.

Now it's just a matter of navigating in gdb. Never done much with it before but I'll fiddle with it and see if I can effectively debug an example program or two that panics. I'll post a followup if I run into an issue with that, hopefully it Just Works.

bradleypeabody commented 3 years ago

The first issue I'm running into is it doesn't seem to be linked to the source. I imagine this is since there is no binary format for MCUs but rather just raw code in files or .hex. Is there a means to output the correct code but in a binary format that is readable by gdb? (i.e. I could run tinygo build ... to produce a separate executable with debug info that isn't otherwise usable, do tinygo flash ... to load the code, and and then do tinygo gdb ... to debug it, with some option to join up to this other executable for debug info?)

I'm also not clear on how to trap a panic specifically. In the example from https://tinygo.org/docs/tutorials/gdb/ it stops on a wfe instruction. In my case it doesn't stop, either on a sleep, or when the panic occurs.

Here's more detail on my setup:

Programmer: Atmel ICE Board: Sparkfun ATSAMD21G dev breakout board (which is close enough to arduino-nano33) I was programming it via the regular bossa stuff and the USB port earlier, but I switched over to SWD and the Atmel ICE and changed the flash command to use atprogram (see below). The reason is because this will ultimately be for a custom board with a custom bootloader and I'm planning on not being able to rely on the Arduino USB/bossa bootloader stuff. But I'm not sure if this has any impact on the gdb setup, since that appears to just use openocd, completely separate from whatever tinygo flashdoes.

Test program: pgms/panictest/main.go

package main

import (
    "machine"
    "time"
)

func main() {

    led := machine.LED
    led.Configure(machine.PinConfig{Mode: machine.PinOutput})

    for i := 0; i < 10; i++ {
        led.Low()
        time.Sleep(time.Millisecond * 500)

        led.High()
        time.Sleep(time.Millisecond * 500)
    }

    panic("all your base [pointer] are belong to us")
}

Target: custom.js

{
    "inherits": ["atsamd21g18a"],
    "build-tags": ["custom_r1","arduino_nano33"],
    "flash-command": "atprogram -v -t atmelice -i swd -d atsamd21g18a program -o 0x2000 -f {bin}",
    "openocd-interface": "cmsis-dap"
}
$ tinygo flash -x -target=custom.json ./pgms/panictest
ld.lld --emit-relocs --gc-sections -L C:\Users\Brad\scoop\apps\tinygo\current -T targets/atsamd21.ld -o C:\Users\Brad\AppData\Local\Temp\tinygo455915887\main C:\Users\Brad\AppData\Local\Temp\tinygo455915887\main.o C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\compiler-rt.a C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\picolibc.a C:\Users\Brad\AppData\Local\tinygo\obj-24ef96ff33d8f682e38b001fa43839aae8526f44c028cc7c51f32074.o C:\Users\Brad\AppData\Local\tinygo\obj-02dab3595d02d155a7e61e05c95ff1839fd243d9fd36ecb0feb8c8f5.o C:\Users\Brad\AppData\Local\tinygo\obj-f5f01decec380e4e745ff274d955ad9f8f9e45b0b68a928e1e739839.o C:\Users\Brad\AppData\Local\tinygo\obj-d17a82e9821a123a003cf7ee266cc6df612ff4b3e4b24b67bc4e8db3.o
atprogram -v -t atmelice -i swd -d atsamd21g18a program -o 0x2000 -f C:\Users\Brad\AppData\Local\Temp\tinygo455915887\main.bin
[DEBUG] Starting execution of "program"
[DEBUG] Starting process 'C:\Program Files (x86)\Atmel\Studio\7.0\atbackend\atbackend.exe'
[DEBUG] Connecting to TCP:127.0.0.1:53230
[INFO] Connected to atmelice, fw version: 1.29
[INFO] Firmware check OK
[DEBUG] Memory segment  written at 0x00002000. Size = 0x00002050.
[DEBUG] Command "program" finished with return code 0
[DEBUG] Exit successfully.
Firmware check OK
Programming completed successfully.

Brad@DESKTOP-HV744NI MINGW64 ~/git/trackersw/gotrack (master)
$ tinygo gdb -x -target=custom.json ./pgms/panictest
ld.lld --emit-relocs --gc-sections -L C:\Users\Brad\scoop\apps\tinygo\current -T targets/atsamd21.ld -o C:\Users\Brad\AppData\Local\Temp\tinygo082637271\main C:\Users\Brad\AppData\Local\Temp\tinygo082637271\main.o C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\compiler-rt.a C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\picolibc.a C:\Users\Brad\AppData\Local\tinygo\obj-24ef96ff33d8f682e38b001fa43839aae8526f44c028cc7c51f32074.o C:\Users\Brad\AppData\Local\tinygo\obj-02dab3595d02d155a7e61e05c95ff1839fd243d9fd36ecb0feb8c8f5.o C:\Users\Brad\AppData\Local\tinygo\obj-f5f01decec380e4e745ff274d955ad9f8f9e45b0b68a928e1e739839.o C:\Users\Brad\AppData\Local\tinygo\obj-d17a82e9821a123a003cf7ee266cc6df612ff4b3e4b24b67bc4e8db3.o
openocd -f interface/cmsis-dap.cfg -c transport select swd -f target/at91samdXX.cfg
arm-none-eabi-gdb C:\Users\Brad\AppData\Local\Temp\tinygo082637271\main -ex target remote :3333 -ex monitor halt -ex load -ex monitor reset halt
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from C:\Users\Brad\AppData\Local\Temp\tinygo082637271\main...done.
Remote debugging using :3333
0x0000325c in runtime.scheduler ()
    at C:\Users\Brad\AppData\Local\tinygo\goroot-go1.16.5-314a21957e9668e14863810251327fcc96de910f5372c04063d0ab664b4ce612-syscall\src\runtime/scheduler.go:127
127                             now = ticks()
Loading section .text, size 0x202c lma 0x2000
Loading section .tinygo_stacksizes, size 0x4 lma 0x402c
Loading section .data, size 0x20 lma 0x4030
Start address 0x31c8, load size 8272
Transfer rate: 7 KB/sec, 2757 bytes/write.
target halted due to debug-request, current mode: Thread
xPSR: 0x21000000 pc: 0x00000f4c msp: 0x20007c00
(gdb) bt
#0  0x000031c8 in Reset_Handler ()
    at C:\Users\Brad\AppData\Local\tinygo\goroot-go1.16.5-314a21957e9668e14863810251327fcc96de910f5372c04063d0ab664b4ce612-syscall\src\runtime/gc_stack_raw.go:40
(gdb) frame
#0  0x000031c8 in Reset_Handler ()
    at C:\Users\Brad\AppData\Local\tinygo\goroot-go1.16.5-314a21957e9668e14863810251327fcc96de910f5372c04063d0ab664b4ce612-syscall\src\runtime/gc_stack_raw.go:40
40      }
(gdb) c
Continuing.

Also, in another run:

(gdb) list -
13      //
14      //     *r.Reg
15      //
16      //go:inline
17      func (r *Register8) Get() uint8 {
18              return LoadUint8(&r.Reg)
19      }
20
21      // Set updates the register value. It is the volatile equivalent of:
22      //
(gdb) break abort
Function "abort" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]

It hangs indefinitely after a continue (runs for the remainder of the 10 seconds and then blinking stops and nothing responds). Also what I'm doing here seems to send the debugging hardware into a strange state because I usually have to unplug it and plug it back in each time to get it to start over clean. Possibly my use of atprogram here is the culprit, but I don't follow how bossa would do a better job since it will also just start running the program immediately after load (I think...)

aykevl commented 3 years ago

Cool, makes sense. I typically prefer logging over debugging but I think that comes from years of writing server-side code where logging is nearly always available and reliable. But it makes sense that for MCUs it's at least as likely if not more so that a debugger will be available vs accessible log output.

It really depends. For AVR microcontrollers (Arduino Uno for example), there are debuggers but they're all very expensive and hard to use. So in that case, writing to a UART is often easier.

I did some more fiddling and the thing that tripped me up on the debugger was not realizing I needed to specify the programmer. The example here https://tinygo.org/docs/tutorials/gdb/ doesn't explicitly say that the -programmer option is required but automatically supplied by the target in the case of the microbit. When there is no programmer specified, it looks like tinygo gdb ... runs arm-none-eabi-gdb [path/to/bin] which starts but with no debugging execution capability, and no output to clarify that the -programmer option is missing. (Just mentioning in case a doc update is warranted, not a criticism.)

Some boards contain an on-board debugger and so they have a fixed debugger. In that case, specifying the programmer is not necessary as it is already configured in the target JSON file.

That said, I checked it and this is actually a bug in TinyGo. It should print an error in this case. Instead, it assumes you're running the binary directly on the host, which of course won't work. So yes, the docs could be updated to provide information about this flag (or at least point out its existence), but perhaps more importantly TinyGo should be fixed to distinguish a local GDB session from a remote one and print a helpful error message for boards that don't contain a programmer.

The first issue I'm running into is it doesn't seem to be linked to the source. I imagine this is since there is no binary format for MCUs but rather just raw code in files or .hex. Is there a means to output the correct code but in a binary format that is readable by gdb? (i.e. I could run tinygo build ... to produce a separate executable with debug info that isn't otherwise usable, do tinygo flash ... to load the code, and and then do tinygo gdb ... to debug it, with some option to join up to this other executable for debug info?)

I'm not entirely sure what you're asking but I'll give some pointers. The firmware on the microcontroller is just raw code and data, it drops all debugging metadata (such as filenames). There are a few formats for working with this, common formats are bin, hex and ELF. .bin files are just a binary dump of the ROM and I would not recommend using them. Hex files are better: they are also a raw dump but also include where in the device memory the data should be stored. For flashing that's enough, but these formats don't contain any debug information either. That's why you will normally use ELF files: they contain everything (code, data, and debug information) and is the base format used inside TinyGo to produce .bin and .hex files. This is in fact the same format as is normally used on Linux. Because most tools to work with microcontrollers understand ELF files, I would recommend always using it unless some tool can't deal with it.

Also, note that tinygo gdb will also flash the binary. So you don't have to flash the program first and then run tinygo gdb to debug it. You can see this in the following lines:

Loading section .text, size 0x202c lma 0x2000
Loading section .tinygo_stacksizes, size 0x4 lma 0x402c
Loading section .data, size 0x20 lma 0x4030
Start address 0x31c8, load size 8272
Transfer rate: 7 KB/sec, 2757 bytes/write.

In summary, if you just want to flash you can use tinygo flash, if you want to debug you can use tinygo gdb and rely on the fact that you'll always be debugging a freshly flashed program.

I'm also not clear on how to trap a panic specifically. In the example from https://tinygo.org/docs/tutorials/gdb/ it stops on a wfe instruction. In my case it doesn't stop, either on a sleep, or when the panic occurs.

You can break on runtime.abort (using b runtime.abort) and print a backtrace. This would be a good addition to the tutorial, because debugging a panic is a common thing to do (especially nondescript panics such as "nil pointer dereference").

bradleypeabody commented 3 years ago

Thanks Ayke, very helpful.

At this point, I'm down to two issues:

  1. I can't tell if the stack trace is correct, but it does not appear to be usable. I'd expect some sort of reference to main or main.go. And it says the call stack is currently in printnl? Maybe, but doesn't seem right.
(gdb) b runtime.abort
Breakpoint 1 at 0x2c1e: runtime.abort. (3 locations)
(gdb) c
Continuing.
Note: automatically using hardware breakpoints for read-only addresses.

Breakpoint 1, runtime._panic (
    message=<error reading variable: access outside bounds of object referenced via synthetic pointer>)
    at C:\Program Files\Go\src\runtime/panic.go:13
13      // We have two different ways of doing defers. The older way involves creating a
(gdb) bt
#0  runtime._panic (
    message=<error reading variable: access outside bounds of object referenced via synthetic pointer>)
    at C:\Program Files\Go\src\runtime/panic.go:13
#1  0x00002c56 in runtime.printnl () at C:\Program Files\Go\src\runtime/print.go:287
#2  0x00000000 in ?? ()
(gdb)

And then I do list - and it lands me at the top of whatever file has panicCheck1 in it:

(gdb) list -
8               "runtime/internal/atomic"
9               "runtime/internal/sys"
10              "unsafe"
11      )
12
13      // We have two different ways of doing defers. The older way involves creating a
14      // defer record at the time that a defer statement is executing and adding it to a
15      // defer chain. This chain is inspected by the deferreturn call at all function
16      // exits in order to run the appropriate defer calls. A cheaper way (which we call
17      // open-coded defers) is used for functions in which no defer statements occur in
(gdb) list
18      // loops. In that case, we simply store the defer function/arg information into
19      // specific stack slots at the point of each defer statement, as well as setting a
20      // bit in a bitmask. At each function exit, we add inline code to directly make
21      // the appropriate defer calls based on the bitmask and fn/arg information stored
22      // on the stack. During panic/Goexit processing, the appropriate defer calls are
23      // made using extra funcdata info that indicates the exact stack slots that
24      // contain the bitmask and defer fn/args.
25
26      // Check to make sure we can really generate a panic. If the panic
27      // was generated from the runtime, or from inside malloc, then convert
(gdb) list
28      // to a throw of msg.
29      // pc should be the program counter of the compiler-generated code that
30      // triggered this panic.
31      func panicCheck1(pc uintptr, msg string) {
32              if sys.GoarchWasm == 0 && hasPrefix(funcname(findfunc(pc)), "runtime.") {
33                      // Note: wasm can't tail call, so we can't get the original caller's pc.
34                      throw(msg)
35              }
36              // TODO: is this redundant? How could we be in malloc
37              // but not in the runtime? runtime/internal/*, maybe?
(gdb)

Not sure what to make of the above.

  1. I sometimes have tinygo gdb runs that don't start properly. This seems to happen when running from mingw64 bash (Git Bash), so possible that's just been adding unnecessary problems - I'll let you know if I can reproduce this with regular Windows Command Prompt, but so far that seems to have fixed it, no idea why, just including the below for info. This was tripping me up a lot before but not since I switched to regular Command Prompt.
$ tinygo gdb -x -target=custom_r1.json ./pgms/panictest
ld.lld --emit-relocs --gc-sections -L C:\Users\Brad\scoop\apps\tinygo\current -T targets/atsamd21.ld -o C:\Users\Brad\AppData\Local\Temp\tinygo363597775\main C:\Users\Brad\AppData\Local\Temp\tinygo363597775\main.o C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\compiler-rt.a C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\picolibc.a C:\Users\Brad\AppData\Local\tinygo\obj-24ef96ff33d8f682e38b001fa43839aae8526f44c028cc7c51f32074.o C:\Users\Brad\AppData\Local\tinygo\obj-02dab3595d02d155a7e61e05c95ff1839fd243d9fd36ecb0feb8c8f5.o C:\Users\Brad\AppData\Local\tinygo\obj-f5f01decec380e4e745ff274d955ad9f8f9e45b0b68a928e1e739839.o C:\Users\Brad\AppData\Local\tinygo\obj-d17a82e9821a123a003cf7ee266cc6df612ff4b3e4b24b67bc4e8db3.o
openocd -f interface/cmsis-dap.cfg -c transport select swd -f target/at91samdXX.cfg
arm-none-eabi-gdb C:\Users\Brad\AppData\Local\Temp\tinygo363597775\main -ex target remote :3333 -ex monitor halt -ex load -ex monitor reset halt
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from C:\Users\Brad\AppData\Local\Temp\tinygo363597775\main...done.
Remote debugging using :3333
device/arm.DisableInterrupts ()
    at C:\Users\Brad\AppData\Local\tinygo\goroot-go1.16.5-314a21957e9668e14863810251327fcc96de910f5372c04063d0ab664b4ce612-syscall\src\device\arm/arm.go:178
178             return AsmFull(`
Loading section .text, size 0x202c lma 0x2000
Loading section .tinygo_stacksizes, size 0x4 lma 0x402c
Loading section .data, size 0x20 lma 0x4030
Start address 0x31c8, load size 8272
Transfer rate: 7 KB/sec, 2757 bytes/write.
target halted due to debug-request, current mode: Thread
xPSR: 0x61000000 pc: 0x00000f4c msp: 0x20007c00
(gdb) b runtime.panic
Function "runtime.panic" not defined.
Make breakpoint pending on future shared library load? (y or [n]) [answered N; input not from terminal]
(gdb)

Function "runtime.panic" not defined.

And then, in another run:

$ tinygo gdb -x -target=../targets/waldo_r1.json ./pgms/panictest
ld.lld --emit-relocs --gc-sections -L C:\Users\Brad\scoop\apps\tinygo\current -T targets/atsamd21.ld -o C:\Users\Brad\AppData\Local\Temp\tinygo144438147\main C:\Users\Brad\AppData\Local\Temp\tinygo144438147\main.o C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\compiler-rt.a C:\Users\Brad\scoop\apps\tinygo\current\pkg\armv6m-none-eabi\picolibc.a C:\Users\Brad\AppData\Local\tinygo\obj-24ef96ff33d8f682e38b001fa43839aae8526f44c028cc7c51f32074.o C:\Users\Brad\AppData\Local\tinygo\obj-02dab3595d02d155a7e61e05c95ff1839fd243d9fd36ecb0feb8c8f5.o C:\Users\Brad\AppData\Local\tinygo\obj-f5f01decec380e4e745ff274d955ad9f8f9e45b0b68a928e1e739839.o C:\Users\Brad\AppData\Local\tinygo\obj-d17a82e9821a123a003cf7ee266cc6df612ff4b3e4b24b67bc4e8db3.o
openocd -f interface/cmsis-dap.cfg -c transport select swd -f target/at91samdXX.cfg
arm-none-eabi-gdb C:\Users\Brad\AppData\Local\Temp\tinygo144438147\main -ex target remote :3333 -ex monitor halt -ex load -ex monitor reset halt
GNU gdb (GNU Tools for ARM Embedded Processors) 7.10.1.20160923-cvs
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=i686-w64-mingw32 --target=arm-none-eabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from C:\Users\Brad\AppData\Local\Temp\tinygo144438147\main...done.
Remote debugging using :3333
device/arm.DisableInterrupts ()
    at C:\Users\Brad\AppData\Local\tinygo\goroot-go1.16.5-314a21957e9668e14863810251327fcc96de910f5372c04063d0ab664b4ce612-syscall\src\device\arm/arm.go:178
178             return AsmFull(`
Loading section .text, size 0x202c lma 0x2000
Loading section .tinygo_stacksizes, size 0x4 lma 0x402c
Loading section .data, size 0x20 lma 0x4030
Start address 0x31c8, load size 8272
Transfer rate: 7 KB/sec, 2757 bytes/write.
target halted due to debug-request, current mode: Thread
xPSR: 0x61000000 pc: 0x00000f4c msp: 0x20007c00
(gdb) b runtime.abort
Breakpoint 1 at 0x2c1e: runtime.abort. (3 locations)
(gdb) c
The program is not being run.
(gdb)

The program is not being run.

Odd stuff. But again, so far it's just in Git Bash, so I'm over it.