Limitations of AVR debugging with Bloom
Using Bloom to debug AVR microcontrollers on Linux comes with some limitations. These are described here.
Breakpoints can cause excessive wear on AVR flash memory
By default, GDB inserts and removes breakpoints liberally, in order to reduce the likelihood of breakpoints being left on the target in the event of GDB terminating abruptly.
Bloom doesn't support hardware breakpoints. All breakpoints set via GDB will be implemented as software breakpoints. This means that all breakpoints are written to the AVR target's flash memory.
When GDB makes unnecessary breakpoint removal/insertion requests, this can cause excessive wear on the target's flash memory.
Some debug tools will delay the applying of breakpoint changes to flash memory, until they receive a flow control command. This reduces the unnecessary updating of flash memory, but does not completely prevent it.
Mitigation
GDB can be configured to keep breakpoints in place, instead of removing them immediately after target
execution comes to a halt. This can be done via the always-inserted
breakpoint setting:
(gdb) show breakpoint always-inserted
Always inserted breakpoint mode is off.
(gdb) set breakpoint always-inserted on
(gdb) show breakpoint always-inserted
Always inserted breakpoint mode is on.
(gdb)
Enabling the always-inserted
setting will further reduce the excessive wear
of flash memory, when debugging via GDB and Bloom. It should also improve debugging performance.
Bloom automatically clears all breakpoints before disconnecting from the debug tool. This means
that enabling the always-inserted
setting should not affect the
likelihood of redundant software breakpoints being left on the target, in the event of GDB
terminating abruptly.
Stepping performance
When stepping over a line of code, GDB instructs Bloom to step one instruction at a time. This can have a significant impact on the time it takes to step over an instruction-heavy line of code.
This issue is particularly noticeable when stepping over calls to busy-wait functions, such as
_delay_ms()
and _delay_us()
.
Mitigation
Avoid stepping over instruction-heavy lines - instead, use breakpoints. Place a breakpoint at the subsequent line, then continue execution. This will allow the target to execute all the instructions between the PC and breakpoint address, before halting at the subsequent line.
The tbreak
command accepts an offset. The following example will
install a temporary breakpoint +1 line from the current line, in the current source file.
(gdb) tbreak +1
Alternatively, the until
GDB command can be used to continue execution
until a specific line is reached:
(gdb) until main.cpp:39
In the example above, we instruct GDB to continue execution and stop at line 39, in main.cpp.
Some IDE debuggers provide similar functionality. In CLion, the 'Run to Cursor' function can be used.
If, by mistake, you've begun stepping over an instruction-heavy line, just interrupt execution with Ctrl+C in the GDB console (or use the 'pause' function in your IDE debugger). Then, insert the necessary breakpoint and resume execution.
GDB timeout warnings
Bloom can only perform one operation on the AVR target at any given time. If GDB sends a command to Bloom whilst some other operation is taking place, Bloom will not be able to service the command from GDB until the other operation is complete. This can result in GDB having to wait for a lengthened period of time, for a response from Bloom. GDB will report these timeouts as warnings, in the form of "Ignoring packet error, continuing..." messages.
In most cases, the warnings can be ignored. Bloom will eventually get around to servicing the command from GDB. After which, GDB will stop printing the warning messages.
Mitigation
To prevent the warnings, increase GDB's remotetimeout
setting:
(gdb) show remotetimeout
Timeout limit to wait for target to respond is 2.
(gdb) set remotetimeout 30
(gdb) show remotetimeout
Timeout limit to wait for target to respond is 30.
(gdb)
The timeout value is in seconds. The set remotetimeout 30
command will
set the timeout to 30 seconds.
Program memory corruption (redundant software breakpoints)
Upon receiving a shutdown signal, Bloom will attempt a clean shutdown, where it will clear all breakpoints on the target before disconnecting from the debug tool. However, if Bloom is not given a chance to shut down properly, any active software breakpoints will remain in the target's program memory. These breakpoints may block program execution on the target, until the target is reprogrammed.
Furthermore, the debug tool can be left in an undefined state, if Bloom is not given a chance to properly disconnect from it. This can result in issues with starting subsequent debug sessions, until the debug tool is disconnected and reconnected.
Mitigation
Do not force Bloom to shut down immediately, unless you deem it absolutely necessary to do so. Do not
send a SIGKILL
signal to Bloom's process. Sending a single
SIGTERM
or SIGINT
signal will trigger a
clean shutdown. Sending more than one of either of those signals will cause Bloom to shut down
immediately.
To remove any redundant software breakpoints, reprogram the target. This can be done via GDB's
load
command or any other AVR programming software.
If Bloom shuts down abruptly on its own, this is a bug and should be reported as such.