DEV Community

Ahmet Can Gulmez
Ahmet Can Gulmez

Posted on

Flashing and Debugging A Firmware in Detail

In this tutorial, I will inspect, flash and debug a firmware written for STM32F446RE microcontroller. To accomplish this, we need some dedicated tools:

  • openocd
  • arm-none-eabi-*
  • gdb-multiarch

openocd is the primarily tool that we have to get. It's used as bridge between your host machine and debugger hardware. (I'm using use ST-Link hardware in this time). I will use it to connect to the debugger hardware. arm-none-eabi-* tools are the ARM toolchain used in development of the firmware. gdb-multiarch is the debugger too.

You can get these tools:

$ sudo apt install openocd gcc-arm-none-eabi gdb-multiarch
Enter fullscreen mode Exit fullscreen mode

Note: I'm on Ubuntu 24.04, gcc-arm-none-eabi tools don't include the gdb debugger like arm-none-eabi-gdb. So that gdb-multiarch is being installed externally. On some distribution, the gdb debugger comes with gcc-arm-none-eabi.

Suppose that I have a firmware called "firmware.elf" and wanna inspect it as first step.

The first thing that you should do probably is to determine the firmware itself:

$ file firmware.elf
firmware.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, with debug_info, not stripped
Enter fullscreen mode Exit fullscreen mode

The file command simply outputs the firmware properties like the endiannes, architecture, or so on. You can see easily that this firmware was written for 32-bit ARM Cortex.

We can see also the section sizes (in bytes) of the firmware with arm-none-eabi-size command:

$ arm-none-eabi-size firmware.elf
   text    data     bss     dec     hex filename
   5252      20    1732    7004    1b5c firmware.elf
Enter fullscreen mode Exit fullscreen mode

If you are not familiar with sections of the executables, I've wrote many tutorials that explains the ELF executable. You can look at there.

If you exactly wanna see the contents of the firmware, arm-none-eabi-objdump is the primarily tool for that:

$ arm-none-eabi-objdump -j .text -d firmware.elf

firmware.elf:     file format elf32-littlearm


Disassembly of section .text:

(...)

0800079c <main>:
 800079c:   b500        push    {lr}
 800079e:   b091        sub sp, #68 @ 0x44
 80007a0:   f000 f8ac   bl  80008fc <HAL_Init>
 80007a4:   f000 f82c   bl  8000800 <configOscClk>
 80007a8:   f000 f844   bl  8000834 <configDebugPort>
 80007ac:   4911        ldr r1, [pc, #68]   @ (80007f4 <main+0x58>)
 80007ae:   4d12        ldr r5, [pc, #72]   @ (80007f8 <main+0x5c>)
 80007b0:   4c12        ldr r4, [pc, #72]   @ (80007fc <main+0x60>)
 80007b2:   4668        mov r0, sp
 80007b4:   f7ff ff08   bl  80005c8 <strcpy>
 80007b8:   4668        mov r0, sp
 80007ba:   f7ff ff81   bl  80006c0 <strlen>
 80007be:   f04f 33ff   mov.w   r3, #4294967295 @ 0xffffffff
 80007c2:   b282        uxth    r2, r0
 80007c4:   4669        mov r1, sp
 80007c6:   480d        ldr r0, [pc, #52]   @ (80007fc <main+0x60>)
 80007c8:   f000 fdc2   bl  8001350 <HAL_UART_Transmit>
 80007cc:   4629        mov r1, r5
 80007ce:   4668        mov r0, sp
 80007d0:   f7ff fefa   bl  80005c8 <strcpy>
 80007d4:   4668        mov r0, sp
 80007d6:   f7ff ff73   bl  80006c0 <strlen>
 80007da:   f04f 33ff   mov.w   r3, #4294967295 @ 0xffffffff
 80007de:   b282        uxth    r2, r0
 80007e0:   4669        mov r1, sp
 80007e2:   4620        mov r0, r4
 80007e4:   f000 fdb4   bl  8001350 <HAL_UART_Transmit>
 80007e8:   f44f 707a   mov.w   r0, #1000   @ 0x3e8
 80007ec:   f000 f8b2   bl  8000954 <HAL_Delay>
 80007f0:   e7ec        b.n 80007cc <main+0x30>
 80007f2:   bf00        nop
 80007f4:   0800146c    .word   0x0800146c
 80007f8:   08001489    .word   0x08001489
 80007fc:   20000084    .word   0x20000084

(...)
Enter fullscreen mode Exit fullscreen mode

For example, you see the main() function in .text section of the firmware. At left, the address of the machine code were being displayed. At center, you see the machine code corresponding to assembly instructions that is at right side.

Inspecting the contents of ELF executable is huge topic. It has many variants and view-of-points in there.

"Flashing" term means writing the dedicated sections of the firmware into the flash memory area of the microcontroller. It's done with many different ways. But I will show you openocd tool.

In here, I have to define that how exactly does openocd work. It actually creates a server into the host machine with given configuration files. After starting the server session, you need to connect to that server over telnet or gdb.

Let's do it. Firstly, connect the microcontroller to the host machine. Then open a terminal session and type this command:

$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
Enter fullscreen mode Exit fullscreen mode

In here, I gave the two configuration files. First is the interface. As I said, I use the ST-Link connection. Second is the target file. It defines the target architecture. The openocd comes a bunch of configuration files. So search it and find the appropriate configuration files.

You will see something like this:

Open On-Chip Debugger 0.12.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
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : clock speed 2000 kHz
Info : STLINK V2J33M25 (API v2) VID:PID 0483:374B
Info : Target voltage: 3.224656
Info : [stm32f4x.cpu] Cortex-M4 r0p1 processor detected
Info : [stm32f4x.cpu] target has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f4x.cpu on 3333
Info : Listening on port 3333 for gdb connections
Enter fullscreen mode Exit fullscreen mode

The openocd attached the 4444 port to telnet and 3333 port to gdb. So that I will bind the 3333 port in the gdb session.

Secondly, open the second terminal and type:

$ gdb-multiarch firmware.elf
Enter fullscreen mode Exit fullscreen mode

At the moment, you are in the gdb session. In here, bind to the 3333 port:

(gdb) target remote localhost:3333
Enter fullscreen mode Exit fullscreen mode

For now, you are in the control and responsible for the all flashing and debugging operations.

To flash the firmware, use these commands:

(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset halt
Enter fullscreen mode Exit fullscreen mode

There are tons of commands you can type in the gdb session. We can split these commands in two categories. monitor commands directly are run by the openocd command. Other commands are run by the gdb itself.

load is the main command to flash the firmware. Before and after that command, resetting and halting the CPU execution is good practice. Output will be like:

Loading section .isr_vector, size 0x1c4 lma 0x8000000
Loading section .text, size 0x126c lma 0x8000200
Loading section .rodata, size 0x4c lma 0x800146c
Loading section .ARM, size 0x8 lma 0x80014b8
Loading section .init_array, size 0x4 lma 0x80014c0
Loading section .fini_array, size 0x4 lma 0x80014c4
Loading section .data, size 0xc lma 0x80014c8
Start address 0x08001400, load size 5272
Transfer rate: 9 KB/sec, 753 bytes/write.
Enter fullscreen mode Exit fullscreen mode

That means that the firmware successfully was flashed 🥳.

If you just wanna flash the firmware and not debugging, this following command is enough:

$ openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program firmware.elf verify reset exit"
Enter fullscreen mode Exit fullscreen mode

Instead of dealing with two terminal sessions, above command is just the one-step operation.

The main reason opening the second gdb session is to make the debugging. If you did the debugging previously, you will be familiar with these commands:

(gdb) break main
(gdb) continue
(gdb) next
(gdb) finish
(gdb) print var
(gdb) x/16wx *ptr
(gdb) set var
(gdb) list
Enter fullscreen mode Exit fullscreen mode

The debugging operations are huge and will not fit into a single tutorial as you guest. So that I don't wanna dive into the these and other operations. But I wanna give you a good reference: The Art of Debugging with GDB, DDD, and Eclipse written by Norman Matloff and Peter Jay Salzman.

Until here, I gave a overall explanation the end-to-end pipeline. I'll explain the other stuffs in further.

Top comments (0)