Debug it

We are already inside a debugging session so let’s debug our program.

After the load command, our program is stopped at its entry point. This is indicated by the“Start address 0x8000XXX” part of GDB’s output. The entry point is the part of a program that aprocessor / CPU will execute first.

The starter project I’ve provided to you has some extra code that runs before the main function.At this time, we are not interested in that “pre-main” part so let’s skip right to the beginning ofthe main function. We’ll do that using a breakpoint:

  1. (gdb) break main
  2. Breakpoint 1 at 0x800018c: file src/05-led-roulette/src/main.rs, line 10.
  3. (gdb) continue
  4. Continuing.
  5. Note: automatically using hardware breakpoints for read-only addresses.
  6. Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10
  7. 10 let x = 42;

Breakpoints can be used to stop the normal flow of a program. The continue command will let theprogram run freely until it reaches a breakpoint. In this case, until it reaches the mainfunction because there’s a breakpoint there.

Note that GDB output says “Breakpoint 1”. Remember that our processor can only use six of thesebreakpoints so it’s a good idea to pay attention to these messages.

For a nicer debugging experience, we’ll be using GDB’s Text User Interface (TUI). To enter into thatmode, on the GDB shell enter the following command:

  1. (gdb) layout src

NOTE Apologies Windows users. The GDB shipped with the GNU ARM Embedded Toolchain doesn’tsupport this TUI mode :-(.

GDB session

At any point you can leave the TUI mode using the following command:

  1. (gdb) tui disable

OK. We are now at the beginning of main. We can advance the program statement by statement usingthe step command. So let’s use that twice to reach the _y = x statement. Once you’ve typed steponce you can just hit enter to run itagain.

  1. (gdb) step
  2. 14 _y = x;

If you are not using the TUI mode, on each step call GDB will print back the current statementalong with its line number.

We are now “on” the _y = x statement; that statement hasn’t been executed yet. This means that xis initialized but _y is not. Let’s inspect those stack/local variables using the print command:

  1. (gdb) print x
  2. $1 = 42
  3. (gdb) print &x
  4. $2 = (i32 *) 0x10001ff4
  5. (gdb) print _y
  6. $3 = -536810104
  7. (gdb) print &_y
  8. $4 = (i32 *) 0x10001ff0

As expected, x contains the value 42. _y, however, contains the value -536810104 (?). Because_y has not been initialized yet, it contains some garbage value.

The command print &x prints the address of the variable x. The interesting bit here is that GDBoutput shows the type of the reference: i32*, a pointer to an i32 value. Another interestingthing is that the addresses of x and _y are very close to each other: their addresses are just4 bytes apart.

Instead of printing the local variables one by one, you can also use the info locals command:

  1. (gdb) info locals
  2. x = 42
  3. _y = -536810104

OK. With another step, we’ll be on top of the loop {} statement:

  1. (gdb) step
  2. 17 loop {}

And _y should now be initialized.

  1. (gdb) print _y
  2. $5 = 42

If we use step again on top of the loop {} statement, we’ll get stuck because the program willnever pass that statement. Instead, we’ll switch to the disassemble view with the layout asmcommand and advance one instruction at a time using stepi. You can always switch back into Rustsource code view later by issuing the layout src command again.

NOTE If you used the step command by mistake and GDB got stuck, you can get unstuck by hitting Ctrl+C.

  1. (gdb) layout asm

GDB session

If you are not using the TUI mode, you can use the disassemble /m command to disassemble theprogram around the line you are currently at.

  1. (gdb) disassemble /m
  2. Dump of assembler code for function main:
  3. 7 #[entry]
  4. 0x08000188 <+0>: sub sp, #8
  5. 0x0800018a <+2>: movs r0, #42 ; 0x2a
  6. 8 fn main() -> ! {
  7. 9 let _y;
  8. 10 let x = 42;
  9. 0x0800018c <+4>: str r0, [sp, #4]
  10. 11 _y = x;
  11. 0x0800018e <+6>: ldr r0, [sp, #4]
  12. 0x08000190 <+8>: str r0, [sp, #0]
  13. 12
  14. 13 // infinite loop; just so we don't leave this stack frame
  15. 14 loop {}
  16. => 0x08000192 <+10>: b.n 0x8000194 <main+12>
  17. 0x08000194 <+12>: b.n 0x8000194 <main+12>
  18. End of assembler dump.

See the fat arrow => on the left side? It shows the instruction the processor will execute next.

If not inside the TUI mode on each stepi command GDB will print the statement, the line numberand the address of the instruction the processor will execute next.

  1. (gdb) stepi
  2. 0x08000194 14 loop {}
  3. (gdb) stepi
  4. 0x08000194 14 loop {}

One last trick before we move to something more interesting. Enter the following commands into GDB:

  1. (gdb) monitor reset halt
  2. Unable to match requested speed 1000 kHz, using 950 kHz
  3. Unable to match requested speed 1000 kHz, using 950 kHz
  4. adapter speed: 950 kHz
  5. target halted due to debug-request, current mode: Thread
  6. xPSR: 0x01000000 pc: 0x08000196 msp: 0x10002000
  7. (gdb) continue
  8. Continuing.
  9. Breakpoint 1, main () at src/05-led-roulette/src/main.rs:10
  10. 10 let x = 42;

We are now back at the beginning of main!

monitor reset halt will reset the microcontroller and stop it right at the program entry point.The following continue command will let the program run freely until it reaches the mainfunction that has a breakpoint on it.

This combo is handy when you, by mistake, skipped over a part of the program that you wereinterested in inspecting. You can easily roll back the state of your program back to its verybeginning.

The fine print: This reset command doesn’t clear or touch RAM. That memory will retain itsvalues from the previous run. That shouldn’t be a problem though, unless your program behaviordepends of the value of uninitialized variables but that’s the definition of Undefined Behavior(UB).

We are done with this debug session. You can end it with the quit command.

  1. (gdb) quit
  2. A debugging session is active.
  3. Inferior 1 [Remote target] will be detached.
  4. Quit anyway? (y or n) y
  5. Detaching from program: $PWD/target/thumbv7em-none-eabihf/debug/led-roulette, Remote target
  6. Ending remote debugging.

NOTE If the default GDB CLI is not to your liking check out gdb-dashboard. It uses Python toturn the default GDB CLI into a dashboard that shows registers, the source view, the assembly viewand other things.

Don’t close OpenOCD though! We’ll use it again and again later on. It’s betterjust to leave it running.

What’s next? The high level API I promised.