As discussed in the last post GDB is the program between you and the chip. GDB and the underlying GDB server translate the commands into the correct electrical signaling so that the thing happens that is specified by the command. To get GDB to do what you want to do is however still a bit of a challenge which I will try to address here.

The thing about GDB is that it is versatile and it is old. This means there is plenty to learn and to get wrong. The GDB client also needs to know a bunch of things so that it works the way you expect it to work. First of all the GDB server needs to start up and then the client needs to get the necessary information to start to make sense of what it is being sent by the server.

What GDB can do

GDB (at least as far as I have been able to use it) gets the current state of the target CPU and uses that knowledge to see where it is in it's execution of the program and can show the contents of the local and global variables, the current content of the stack and the next instruction that is to be executed and a few more things that I know of (and probably many things that I am not thinking about).

What we want GDB to do

The thing that we want is primarily that GDB can show us where in the code we are and what the current variables are. We also want GDB to get the debug probe/programmer to write a new Image that we compile after fixing whatever bug we have found into the flash memory of the target.

Environment

This article uses a very minimal program that runs on the STM32F103C8T6 otherwise known as the blue pill. I won't talk about the specifics of the program other than that it is a binary built in the debug profile. I will however write things about these programs and pick them apart in other posts. For rust the stm32f1xx-hal has a good library of examples and the obligatory 'get the on board led to flash' ready to compile. So I'd recommend that you read the things there and get that example to compile if you have a working rust environment.

The Debug build

To be able to debug a program we can't just use the latest release build, we have to use a specially compiled variant of our program (we don't need to rewrite anything, the compiler changes what it outputs when we specify that we want a debug build and not a release build). The release build is optimized for either space or execution time or both (in the last case sacrifices must be made to achieve an acceptable compromise). During optimisation the order of execution does not necessarily stay in the order that was specified by the program code. The compiler builds a dependency tree (similar to what gnu make does with sources) and knows what variables to recalculate when a specific variable is changed because of what the programmer wrote. The compiler can also see patterns that allow for the use of special instructions and optimizes useless things like function wrappers or redefines (not useless for humans but meaningless for the program flow). If we where to look at release code we would not necessarily recognize program flow because it was likely changed by the compiler.

The release build furthermore removes all unnecessary data like the name of variables (the CPU only needs the address after all). If we want to know what variable is referenced by 0x47d28 for example, then that information is not available to the debugger as the debugger does not know the memory layout at an arbitrary point in the program, as data will possibly be shifted around during program execution. To be able to do this, the debug build keeps a "symbol table" linking the name of a variable to it's address. It also updates this table if a variable moves by inserting code into the debug executable. All this book-keeping and missing optimization make debug builds substantially slower than the release executable and a lot faster to compile as all the optimisation can be skipped.

So after we have somehow (this will be discussed in a future post) produced a debug binary, the compiled image can be found at $PROJECT_ROOT/target/thumbv7m-none-eabi/debug/$BINRY_NAME with this it is possible to actually get the code working on the desired target chip.

Starting GDB

To get gdb to load the binary and read in the symbols and other peripheral information we simply need to call gdb with the binary file as an argument. Many other documents on the web include a -q option with the invocation of gdb. This is simply to suppress the copyright and warranty notice that gdb otherwise omits before starting the prompt. Starting gdb without the -q option should print something like the following:

> arm-none-eabi-gdb target/thumbv7m-none-eabi/debug/rusty-blink
GNU gdb (GDB) 8.3
Copyright (C) 2019 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=x86_64-unknown-linux-gnu --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 target/thumbv7m-none-eabi/debug/rusty-blink...
(gdb)

You can see why you might want to use the -q option, but it does contain some useful information.

Connecting to the target (black magic probe)

It would be nice if we could just plug in the controller into the Black Magic Probe and everything would just work. (We will get there, but that is, as usual, going to require some configuration). When we plug in the BMP it will appear as /dev/ttyACM0 and /dev/ttyACM1. It turns up as two devices here because it exposes one serial link to the gdb-server and one serial interface as pass through to the controller (if you choose to connect that one up). The BMP is also capable of powering the controller, so we need to enable the flow of power to the target.

The /dev/ttyACM0 interface of the BMP is the access to the gdb-server and the ttyACM1 is the pass through to the microcontroller. I would like to point out here, that the number 0 and 1 in the devices only works if there was no other pure serial device connected before the BMP. If so the numbers will simply be shifted up the range to the first free values, so please keep that in mind. The smaller number will then be the gdb-server, as it was with the setup described here.

So we need to connect our gdb client with the server running on the BMP. This can be done with the target extended-remote gdb command. The extended-remote part tells gdb that the server is available over a serial link. This serial link is an argument to the above command, so that the whole command reads target extended-remote /dev/$SERIAL_LINK. I have a udev rule in place that maps to the USB ID of the BMP and then links the ttyBmpGdb device to the ttyACM0 and the ttyBmpTarg to the ttyACM1 so that they always have a consistent name.

So we should now read something like the following:

(gdb) target extended-remote /dev/ttyBmpGdb
Remote debugging using /dev/ttyBmpGdb

This tells us that the gdb-client running on our development computer has established a connection with the gdb-server on the BMP.

Power

Now that that is done we have to enable the power to the target. This can be done with the monitor tpwr enable command. The interesting part of this command is the monitor part. Reading the gdb-documentation we can see that the monitor command talks directly with the BMP, 'sidestepping' the normal gdb commands. This is how it is possible to implement command that are specific to the debug probe like the ability to provide power to the target (as this needs to be implemented in the gdb server but would not be part of the normal gdb command set. If you are using the BMP check out the monitor help for the other things that the BMP can do that gdb normally can't. Sending this command off to the BMP, I get the response:

(gdb) monitor tpwr enable
Enabling target power

Scanning for our target

So now we have a powered target. So far so good. Next we need to connect the gdb-server with the chip. As discussed in the last post, I will be using SWD port as it requires only two physical wires and supports all the things I currently need. The BMP needs to scan for devices connected to it using this protocol. The swdp_scan command will do this for us. As it again is a command that is not included in the standard set of gdb commands we need to send that with the monitor gdb command directly to the BMP. Doing that I get:

(gdb) monitor swdp_scan
Target voltage: 3.3V
Available Targets:
No. Att Driver
 1      STM32F1 medium density M3/M4

Attaching to the target

As was reported by the BMP, it found a cortex M3/M4 in an STM32F1 and lists it as chip 1 in it's internal list. With this information we can tell it to actually connect to the chip. So sending the attach command with the number of the chip we want to connect to will actually enable us to work with the chip. Notice that now we are using standard gdb command. Attaching to a target is going to happen in the course of a debug session, so it makes sense to have a gdb command to do that. Connecting to our blue pill I get:

(gdb) attach 1
Attaching to program: /path/to/program/target/thumbv7m-none-eabi/debug/$BINARY, Remote target
cortex_m::peripheral::syst::<impl cortex_m::peripheral::SYST>::has_wrapped (self=0x20004fc4) at /home/$USER/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.6.4/src/peripheral/syst.rs:136
136	       self.csr.read() & SYST_CSR_COUNTFLAG != 0

I have substituted the path on my specific machine in the call above with /path/to/program/ as this will depend on how you have decided to organize your files. The third line in the above call will look very different from what you can see here because when we attached to the controller the BMP stopped the CPU in it's track and now displays where (so in which file in the third and the exact line in line four) the processor halted. When we enabled power, the program that was possibly pre loaded started executing. Therefore the file and line you get will depend on the exact timing of the attach command.

Loading our binary

Now, FINALLY, we are in the position that we can load the binary that we specified when we invoked gdb. Doing this will erase the data that is currently on the chip. So just keep this in mind when loading the image. It is possible to get the data off the device before writing a new image to it, but I won't go into that here. Loading the program that we specified during invocation of gdb is possible with the load command. This time everything is specified so a simple load will do. I get this, when I call load:

(gdb) load
Loading section .vector_table, size 0x130 lma 0x8000000
Loading section .text, size 0x2910 lma 0x8000130
Loading section .rodata, size 0x700 lma 0x8002a40
Start address 0x8000130, load size 12608
Transfer rate: 18 KB/sec, 900 bytes/write.

GDB also halted the CPU at the start address, so the CPU has not executed anything yet and is held halted by the debugger.

We have done it! We have loaded a program to the chip that we have the sources for and a debug binary. With all of that we can actually start debugging and looking at code and seeing what is going on on our chip.

To release the CPU from halt we simply have to issue the continue command on the gdb command line. The program started running and I got the following output:

(gdb) continue
Continuing.

I'll end with the discussion of the use of the GDB here with a little blinking LED on the blue pill board.


Other debug Probes

As I know that I have the privilege to be able to afford an expensive and fancy debug probe I will also give a quick run-down of the use of the ubiquitous STLink debug adapter that can be purchased for only a few dollars on sites like aliexpress.

Let's start with some context. I also wrote a short paragraph here so it will mostly be a repetition of that. The STLink is the built in programmer for the 'discovery' evaluation boards of STMicro. It has been copied by Chinese manufacturers and is available in a USB-stick format. It's essentially a transceiver for SWD and speaks a somewhat proprietary protocol out the other end. The good news is that there exists an integration into the openOCD software for this debugger. As it's essentially ubiquitous as it's Chinese clones cost about two dollars. I have multiple (I don't know why).

OpenOCD

OpenOCD acts as the software that is running on the BMP, translating the custom commands of the STLink into standard gdb commands and possibly some monitor commands. So OpenOCD acts as the gdb-server and runs on the host (our development machine) and connects to the STLink over USB. It exposes a gdb server over a TCP connection (port 3333 normally). OpenOCD can do a whole bunch of other things, but I'm not that competent yet so that I could write about it, so I'll stick to the GDB-server functionality for now. This procedure is also described (differently of course) in the Embedded book but they use a ready made project that comes with some pre-made scripts, and as I want to build these things myself It includes a lot of things that I'll go into in separate posts. It also does things for a different chip (the STM32F3xx series). So that also needs a bit of change to work with the setup described there.

Ah, also I am assuming, just as I have in all the previous texts, that you are running a fairly run of the mill linux. I don't know my way around Windows and I doubt I ever will. So if you are running on a Windows machine, either considers booting a linux based system, adapt the instructions here or try to find information fitting to your use case.

Connecting OpenOCD to the programmer/debugger

So let's get to it then, shall we.

As it is a bit tricky to talk to microcontrollers using the different protocols OpenOCD comes with configurations ready for the different chips and debuggers. So the thing we need to do is to call OpenOCD and the configurations that come along with every installation (at least every Installation I have used) handle everything else for us. Depending how you have set up your privileges you may need to execute the command as root using sudo. Otherwise the it should look something like this:

$ sudo openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v17 API v2 SWIM v4 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.270400
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

As it is possible to see in the above text, OpenOCD was able to find the STLink-v2 and connect to it. It performed a scan similar to what we did with the black magic probe, find an MCU with the right properties (so essentially an STM32F1xx type chip) and connect to it.

We don't need to power it as the STLink clone just provides 3.3V / 5V on some of it's output pins. Just be careful which one you connect up to the chip. As it is a rather cheap clone it also lacks any protections against misuse or ESD, so again some care is advised (but you don't need to be paranoid either).

Connecting gdb to OpenOCD

So now that we have a working connection to our chip, we can start to use OpenOCD as our gdb-server. We need to connect to it. Again, we start up an arm-none-eabi-gdb with the binary we want to debug/load as an argument and the optional -q to suppress the legal notice.

> arm-none-eabi-gdb -q target/thumbv7m-none-eabi/debug/rusty-blink
Reading symbols from target/thumbv7m-none-eabi/debug/rusty-blink...
(gdb)

So now that gdb is up and running, we can connect to OpenOCD. OpenOCD opens a TCP port on the machine we are going to connect to. This is also done using the target extended-remote command only this time with the port as an argument. Calling that results in:

(gdb) target extended-remote :3333
Remote debugging using :3333
0x00000000 in ?? ()
(gdb)

We have now established a connection with OpenOCD. OpenOCD also supports the monitor command only it is way more powerful than the BMP and returns quite a long message when prompted with the monitor help command. As this is a complex topic in it's own right, Ill not be going into any detail here (I also have no clue yet either). You may however end up using many of the OpenOCD commands that lie outside of the domain of gdb. I'll probably write more on that at another time.

Loading the binary

To load the binary we simply need to call load again and it does exactly as it did with the Black Magic Probe (hurray for abstractions). I get the following output:

(gdb) load
Loading section .vector_table, size 0x130 lma 0x8000000
Loading section .text, size 0x2910 lma 0x8000130
Loading section .rodata, size 0x700 lma 0x8002a40
Start address 0x8000130, load size 12608
Transfer rate: 15 KB/sec, 4202 bytes/write.

And you can see that it is very similar to the output above. It loads exactly the same amount of data to exactly the same addresses as before. If you want to see the binary running, you have to release the processor from the halt that the debugger holds it in. A simple continue command on the gdb console will do that (you can also type in the shorthand c command).

That Was that. We have gotten the binary onto our controller. And hopefully have a flashing LED by now. Be careful with the configuration files of OpenOCD and if they don't work the first time round, try different versions of the STLink configuration file (there should be at least an stlink-v2.cfg and a stlink-v2.1.cfg file available).

I hope you found this as helpful to read as I found this to write. If you have questions feel free to write me an Email.