I was worried my other post on the subject was getting off-topic. I’ve been trying to use lldb
to debug embedded ARM targets like the Cortex M0. I’ve had some success.
TL;dr:
- Can I use
lldb
to debug tiny MCUs like a Feather M0 (ARM Cortex M0)? It seems to work, but has many issues. - What does
gdb-remote
do thatplatform connect
doesn’t? - Why does LLDB think there’s a bad CPU type in the executable when I try to
run
? - How do I tell LLDB to reset the processor and stop at the first instruction?
-
thread step-over
is very unreliable, am I missing something? - Why aren’t my breakpoints being hit?
- Does
lldb
know when code is Thumb vs ARM?
Details
I can connect and inspect a little bit:
$ lldb /var/folders/16/jfb809_s2fz01ql7k494f6pc0000gn/T/arduino_build_98039/Blink.ino.elf
(lldb) target create "/var/folders/16/jfb809_s2fz01ql7k494f6pc0000gn/T/arduino_build_98039/Blink.ino.elf"
Current executable set to '/var/folders/16/jfb809_s2fz01ql7k494f6pc0000gn/T/arduino_build_98039/Blink.ino.elf' (arm).
(lldb) target list
Current targets:
* target #0: /var/folders/16/jfb809_s2fz01ql7k494f6pc0000gn/T/arduino_build_98039/Blink.ino.elf ( arch=arm-*-*-eabi, platform=host )
(lldb) platform select remote-gdb-server
Platform: remote-gdb-server
Connected: no
(lldb) platform connect connect://localhost:3333
Platform: remote-gdb-server
Hostname: (null)
Connected: yes
(lldb) list
(lldb) l
44 TinyUSB_Device_Init(0);
45 #elif defined(USBCON)
46 USBDevice.init();
47 USBDevice.attach();
48 #endif
49
50 setup();
51
52 for (;;)
53 {
(lldb) c
error: Process must be launched.
But as you can see, lldb
’s state isn’t quite complete (i.e. it thinks there’s no running process). Then I discovered gdb-remote
, which seems to behave differently from platform connect
:
(lldb) platform disconnect
Disconnected from "remote-gdb-server"
(lldb) gdb-remote 3333
Process 1 stopped
* thread #1, stop reason = signal SIGINT
frame #0: 0x00002280 Blink.ino.elf`micros at delay.c:54:13
51 ticks=ticks2;
52 pend=pend2;
53 count=count2;
-> 54 ticks2 = SysTick->VAL;
55 pend2 = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) ;
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
(lldb) n
Process 1 stopped
* thread #1, stop reason = step over
frame #0: 0x00002282 Blink.ino.elf`micros at delay.c:55:21
52 pend=pend2;
53 count=count2;
54 ticks2 = SysTick->VAL;
-> 55 pend2 = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) ;
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
58
I was able to connect to the target, see the current line of code, step to the next.
Getting overly curious, I tried platform process list
, but it showed all the processes on the host Mac.
So I tried l
:
(lldb) l
59 return ((count+pend) * 1000) + (((SysTick->LOAD - ticks)*(1048576/(VARIANT_MCK/1000000)))>>20) ;
60 // this is an optimization to turn a runtime division into two compile-time divisions and
61 // a runtime multiplication and shift, saving a few cycles
62 }
63
64 #ifdef __SAMD51__
65 /*
(lldb) l
66 * On SAMD51, use the (32bit) cycle count maintained by the DWT unit,
67 * and count exact number of cycles elapsed, rather than guessing how
68 * many cycles a loop takes, which is dangerous in the presence of
69 * cache. The overhead of the call and internal code is "about" 20
70 * cycles. (at 120MHz, that's about 1/6 us)
71 */
72 void delayMicroseconds(unsigned int us)
(lldb) l
73 {
74 uint32_t start, elapsed;
75 uint32_t count;
76
77 if (us == 0)
78 return;
79
(lldb) l
80 count = us * (VARIANT_MCK / 1000000) - 20; // convert us to cycles.
81 start = DWT->CYCCNT; //CYCCNT is 32bits, takes 37s or so to wrap.
82 while (1) {
83 elapsed = DWT->CYCCNT - start;
84 if (elapsed >= count)
85 return;
86 }
Okay, cool, I can list code (although sometimes I get lldb into a state where l
does nothing).
Control-C stops it. c
continues. bt
shows a stack trace. up
goes up a frame. r
prompts me to kill and re-run the process, then chokes with “Bad CPU type in executable”:
(lldb) r
There is a running process, kill it and restart?: [Y/n] y
Process 1 exited with status = 6 (0x00000006) unexpected response to k packet: OK
error: Bad CPU type in executable
Meanwhile openocd
shows:
Info : dropped 'gdb' connection
Stepping
So I reconnect, and the processor is still halted where it was before
(lldb) gdb-remote 3333
Process 1 stopped
* thread #1, stop reason = signal SIGTRAP
frame #0: 0x0000229a Blink.ino.elf`micros at delay.c:59:17
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
58
-> 59 return ((count+pend) * 1000) + (((SysTick->LOAD - ticks)*(1048576/(VARIANT_MCK/1000000)))>>20) ;
60 // this is an optimization to turn a runtime division into two compile-time divisions and
61 // a runtime multiplication and shift, saving a few cycles
62 }
(lldb) bt
* thread #1, stop reason = signal SIGTRAP
* frame #0: 0x0000229a Blink.ino.elf`micros at delay.c:59:17
frame #1: 0x000022e0 Blink.ino.elf`delay(ms=180) at delay.c:103:23
frame #2: 0x0000212a Blink.ino.elf`::loop() at Blink.ino:36:8
frame #3: 0x00002eda Blink.ino.elf`main at main.cpp:54:9
frame #4: 0x00002218 Blink.ino.elf`Reset_Handler at cortex_handlers.c:496:3
Okay, let’s get up to my main code:
(lldb) up
frame #1: 0x000022e0 Blink.ino.elf`delay(ms=180) at delay.c:103:23
100 while (ms > 0)
101 {
102 yield();
-> 103 while (ms > 0 && (micros() - start) >= 1000)
104 {
105 ms--;
106 start += 1000;
(lldb) up
frame #2: 0x0000212a Blink.ino.elf`::loop() at Blink.ino:36:8
33 digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
34 delay(1000); // wait for a second
35 digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
-> 36 delay(1000); // wait for a second
37 }
Let’s step over this call:
(lldb) n
Process 1 stopped
* thread #1, stop reason = step over
frame #0: 0x00002282 Blink.ino.elf`micros at delay.c:55:21
52 pend=pend2;
53 count=count2;
54 ticks2 = SysTick->VAL;
-> 55 pend2 = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) ;
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
58
Hmm, no, that’s not where it should be. Let’s try that again:
(lldb) up
frame #1: 0x000022e0 Blink.ino.elf`delay(ms=859) at delay.c:103:23
100 while (ms > 0)
101 {
102 yield();
-> 103 while (ms > 0 && (micros() - start) >= 1000)
104 {
105 ms--;
106 start += 1000;
(lldb) up
frame #2: 0x0000212a Blink.ino.elf`::loop() at Blink.ino:36:8
33 digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
34 delay(1000); // wait for a second
35 digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
-> 36 delay(1000); // wait for a second
37 }
(lldb) n
(lldb)
Hmm. The code is running again. I’ve experienced this sort of thing in Xcode targeting a Swift app on my local Mac a lot: I’ll try to step over code, and it will just continue running.
Let’s try again:
Control-C
Process 1 stopped
* thread #1, stop reason = signal SIGINT
frame #0: 0x00002280 Blink.ino.elf`micros at delay.c:54:13
51 ticks=ticks2;
52 pend=pend2;
53 count=count2;
-> 54 ticks2 = SysTick->VAL;
55 pend2 = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) ;
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
(lldb) up
frame #1: 0x000022e0 Blink.ino.elf`delay(ms=921) at delay.c:103:23
100 while (ms > 0)
101 {
102 yield();
-> 103 while (ms > 0 && (micros() - start) >= 1000)
104 {
105 ms--;
106 start += 1000;
(lldb) up
frame #2: 0x0000211c Blink.ino.elf`::loop() at Blink.ino:34:8
31 // the loop function runs over and over again forever
32 void loop() {
33 digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
-> 34 delay(1000); // wait for a second
35 digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
36 delay(1000); // wait for a second
37 }
(lldb) n
Process 1 stopped
* thread #1, stop reason = step over
frame #0: 0x00002282 Blink.ino.elf`micros at delay.c:55:21
52 pend=pend2;
53 count=count2;
54 ticks2 = SysTick->VAL;
-> 55 pend2 = !!(SCB->ICSR & SCB_ICSR_PENDSTSET_Msk) ;
56 count2 = _ulTickCount ;
57 } while ((pend != pend2) || (count != count2) || (ticks < ticks2));
58
This time n
stopped, but not on line 35. Instead, it stopped somewhere down inside the call on line 34. This is what usually happens when I try to step over the current source line.
Breakpoints
Okay, let’s try some breakpoints:
frame #1: 0x0000212a Blink.ino.elf`::loop() at Blink.ino:36:8
33 digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
34 delay(1000); // wait for a second
35 digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
-> 36 delay(1000); // wait for a second
37 }
(lldb) breakpoint list
No breakpoints currently set.
(lldb) breakpoint set --file Blink.ino --line 34
Breakpoint 1: where = Blink.ino.elf`::loop() + 2 at Blink.ino:34:8, address = 0x0000210a
(lldb) breakpoint list
Current breakpoints:
1: file = 'Blink.ino', line = 34, exact_match = 0, locations = 1, resolved = 1, hit count = 0
1.1: where = Blink.ino.elf`::loop() + 2 at Blink.ino:34:8, address = 0x0000210a, resolved, hit count = 0
(lldb) c
Process 1 resuming
LED resumes blinking, breakpoint is ignored.
I realize there could be issues in OpenOCD or how I’m invoking that. It does seem to recognize the hardware:
$ openocd -f interface/cmsis-dap.cfg -f target/at91samdXX.cfg -c "init;reset init"
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
Warn : could not read product string for device 0x2222:0x0043: Pipe error
Info : CMSIS-DAP: SWD Supported
Info : CMSIS-DAP: FW Version = 0254.2
Info : CMSIS-DAP: Serial# = 310436023538a0ab0533363639323946a5a5a5a597969908
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 400 kHz
Info : SWD DPIDR 0x0bc11477
Info : at91samd.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for at91samd.cpu on 3333
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, current mode: Thread
xPSR: 0x41000000 pc: 0x00000294 msp: 0x20002de0
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : accepting 'gdb' connection on tcp/3333
Info : SAMD MCU: SAMD21G18A (256KB Flash, 32KB RAM)
Warn : Prefer GDB command "target extended-remote 3333" instead of "target remote 3333"
Info : SWD DPIDR 0x0bc11477
Error: Failed to read memory at 0xff4f7400
ARM vs Thumb?
Hmm, after disconnecting and reconnecting (also resetting the MCU and restarting openocd):
(lldb) gdb-remote 3333
Process 1 stopped
* thread #1, stop reason = signal SIGINT
frame #0: 0x00000294 Blink.ino.elf
-> 0x294: stmdami r5!, {r2, r5, r8, r11, lr}
0x298: addmi r11, r1, #112, #10
0x29c: blmi 0x9342cc
0x2a0: andhs r1, r0, #196, #28
If I step over this instruction:
(lldb) thread step-inst-over
Process 1 stopped
* thread #1, stop reason = instruction step over
frame #0: 0x00000296 Blink.ino.elf
-> 0x296: ldrblt r4, [r0, #-0x825]!
0x29a: andle r4, r10, r1, lsl #5
0x29e: vdivne.f64 d20, d4, d20
0x2a2: adcmi r2, r3, #0, #4
Notice that the PC was 0x294, and now it’s 0x296 (two bytes later, implying Thumb instructions). But LLDB is showing 4-byte (ARM) instructions in the disassembly.
Anyway, I’m running out of steam for now.