Flashing and Debugging A Firmware in Detail

Published: (March 10, 2026 at 04:38 PM EDT)
7 min read
Source: Dev.to

Source: Dev.to

Source: Dev.to

Inspect, Flash, and Debug an STM32F446RE Firmware

In this tutorial we will inspect, flash, and debug a firmware built for the STM32F446RE microcontroller.
To accomplish this we need a few dedicated tools:

  • openocd – the primary tool that bridges the host machine and the debugger hardware (e.g., an ST‑Link).
  • arm-none-eabi-* – the ARM toolchain used to build the firmware.
  • gdb-multiarch – the GDB front‑end for debugging.

Installing the required tools

On Ubuntu 24.04, the following command installs everything we need:

sudo apt install openocd gcc-arm-none-eabi gdb-multiarch

Note: The gcc-arm-none-eabi package on Ubuntu does not include arm-none-eabi-gdb, so we install gdb-multiarch separately. On some other distributions the GDB debugger is bundled with the toolchain.

1. Inspecting the Firmware

Assume we have a firmware file called firmware.elf. The first step is to query the file itself:

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

The file command reports the ELF properties (endianness, architecture, etc.).
We can also view the section sizes with arm-none-eabi-size:

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

Tip: If you are unfamiliar with ELF sections, see my earlier tutorials that explain the ELF format.

Disassembling the .text Section

To look at the actual code, use arm-none-eabi-objdump:

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

firmware.elf:     file format elf32-littlearm

Disassembly of section .text:

(...)

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

(...)

You can see the main() function in the .text section.

  • Left column – address of each instruction.
  • Middle column – raw machine code.
  • Right column – disassembled assembly.

Inspecting ELF executables is a huge topic with many viewpoints; the above is just a quick look.

2. Flashing the Firmware

Flashing means writing the appropriate sections of the ELF file into the microcontroller’s flash memory.
We’ll use OpenOCD for this.

Starting the OpenOCD Server

  1. Connect the STM32 board (via ST‑Link) to the host.
  2. Run the following command:
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
  • interface/stlink.cfg – configuration for the ST‑Link adapter.
  • target/stm32f4x.cfg – configuration for the STM32F4 family (includes the Cortex‑M4 core).

OpenOCD will start a server that can be accessed via Telnet (port 4444) or GDB (port 3333).
Typical output looks 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 '.
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

The target is now ready to be programmed or debugged.

3. Debugging with GDB

In another terminal, launch gdb‑multiarch (or arm‑none‑eabi‑gdb if you have it) and connect to the OpenOCD GDB server:

$ gdb-multiarch firmware.elf
(gdb) target remote localhost:3333

You can now set breakpoints, step through code, inspect registers, etc.:

(gdb) break main
(gdb) continue
(gdb) info registers
(gdb) disassemble main

Recap

  1. Inspect the ELF file with file, arm-none-eabi-size, and arm-none-eabi-objdump.

  2. Flash the firmware using OpenOCD:

    openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
  3. Debug with GDB (gdb-multiarch or arm-none-eabi-gdb) over the OpenOCD GDB server.

With these tools you can fully explore, program, and troubleshoot STM32F446RE firmware from the command line. Happy hacking!

1. OpenOCD ports

OpenOCD exposes two ports:

  • 4444 – Telnet interface
  • 3333 – GDB remote‑debug interface

You will connect GDB to the 3333 port.

2. Start a GDB session

Open a second terminal and launch GDB:

gdb-multiarch firmware.elf

You will be dropped into the GDB prompt.

Connect to OpenOCD

target remote localhost:3333

You now have full control over flashing and debugging.


3. Flash the Firmware

Run the following commands in the GDB session:

(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset halt

How the Commands Work

  • monitor … – Sent directly to OpenOCD.
  • load – GDB command that writes the ELF sections to the target.

Typical Output

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.

If you see the output above, the firmware has been successfully flashed 🎉.

4. One‑step flashing (no debugging)

If you only need to flash the firmware, you can skip the GDB session entirely:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
       -c "program firmware.elf verify reset exit"

This single command programs the device and then exits.

5. Basic GDB Debugging Commands

Below are some of the most common GDB commands you’ll use while debugging:

(gdb) break main          # set a breakpoint at `main`
(gdb) continue           # resume execution
(gdb) next               # step over the next line
(gdb) finish             # run until the current function returns
(gdb) print var          # display the value of `var`
(gdb) x/16wx *ptr        # examine memory at `*ptr`
(gdb) set var = 42       # change the value of `var`
(gdb) list               # show source code around the current line

Note: Debugging is a deep topic that can’t be covered fully in a short tutorial. For a comprehensive guide, see The Art of Debugging with GDB, DDD, and Eclipse by Norman Matloff and Peter Jay Salzman.

6. Summary

  • OpenOCD provides a GDB‑compatible port (default 3333).

  • Use gdb-multiarch (or any GDB) to connect:

    (gdb) target remote localhost:3333
  • Typical flash sequence:

    (gdb) monitor reset halt
    (gdb) load
    (gdb) monitor reset halt
  • For a quick flash without entering the debugger, run the one‑liner OpenOCD command (see the “Flashing” section).

  • Familiarize yourself with basic GDB commands (e.g., break, continue, step, print) for effective debugging.

I’ll cover additional topics and advanced usage in future sections. Happy hacking!

0 views
Back to Blog

Related posts

Read more »