Back to Blog
Embedded Systems

Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

Print statements are not a debugging strategy. Professional embedded debugging uses JTAG for live hardware inspection, GDB for breakpoints and watchpoints, logic analyzers for protocol issues, and RTT for non-intrusive tracing. Here is the complete workflow.

May 2, 2024
14 min read
JTAGGDBDebuggingEmbedded Systems

Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

The most common debugging approach in embedded development is adding printf statements until the bug disappears — usually because the printf timing changed the system enough to hide the race condition. This is not a professional approach. It wastes hours and hides Heisenbugs.

Here is the toolkit that actually finds bugs in production firmware.

JTAG: The Foundation of Hardware Debugging

JTAG (Joint Test Action Group) is a hardware interface that gives a debugger direct access to the processor's internal state — registers, memory, peripherals — without modifying the running code. On ARM Cortex-M, this is usually exposed as SWD (Serial Wire Debug), a 2-pin subset of JTAG.

ESP32 JTAG Setup:

The ESP32 exposes JTAG on GPIO 12-15. Use an FT2232H-based adapter (ESP-Prog, or generic FT2232H breakout). Install OpenOCD:

Install OpenOCD with ESP32 support

sudo apt install openocd

Start OpenOCD for ESP32

openocd -f interface/ftdi/esp32_devkitj_v1.cfg -f target/esp32.cfg

STM32 JTAG/SWD Setup:

ST-Link v2 is the standard for STM32. It uses SWD (SWDIO + SWDCLK + GND + 3.3V). Most STM32 Nucleo and Discovery boards include an on-board ST-Link, making connection trivial.

Start OpenOCD for STM32F4 with ST-Link

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

GDB Workflow: Breakpoints and Watchpoints

Once OpenOCD is running, connect GDB in a second terminal:

ESP32

xtensa-esp32-elf-gdb build/firmware.elf (gdb) target remote :3333 (gdb) monitor reset halt

STM32

arm-none-eabi-gdb build/firmware.elf (gdb) target remote :3333 (gdb) monitor reset halt

Hardware breakpoints: Cortex-M has 4–8 hardware breakpoints (FPB unit). ESP32 Xtensa has 2. Use them to stop at function entries:

(gdb) break mqtt_publish
(gdb) continue

Execution stops when mqtt_publish is called

(gdb) info registers # Inspect CPU registers (gdb) backtrace # Full call stack (gdb) print payload # Inspect variable

Watchpoints: Stop execution when a memory address is read or written — invaluable for tracking memory corruption:

(gdb) watch g_device_state.wifi_connected

GDB stops every time this variable changes

(gdb) continue

When triggered, GDB shows what instruction wrote it and its new value

Data watchpoints (Cortex-M DWT unit) can also trigger on peripheral register access, useful for diagnosing unexpected peripheral state changes.

Finding Stack Overflows and Heap Corruption

Stack overflows and heap corruption are the hardest bugs to reproduce and diagnose without a debugger.

Stack overflow with GDB:

// Enable stack canaries in your linker script or startup code
extern uint32_t _stack_start;
#define STACK_CANARY 0xDEADBEEF

void init_stack_canary(void) { // Fill bottom of stack with canary pattern uint32_t *p = &_stack_start; while (p < (uint32_t *)__get_MSP() - 64) { *p++ = STACK_CANARY; } }

void check_stack_canary(void) { uint32_t *p = &_stack_start; if (*p != STACK_CANARY) { // Stack has overflowed into canary region fault_handler("stack overflow detected"); } }

Set a GDB watchpoint on the canary address: when it changes, you catch the overflow at the exact instruction that caused it.

Heap corruption: Use a debug malloc that writes guard bytes before and after each allocation. Check them on every free:

#define GUARD_PATTERN  0xFEEDFACE
#define GUARD_SIZE     4

void *debug_malloc(size_t size) { uint8_t *raw = malloc(size + 2 * GUARD_SIZE); if (!raw) return NULL;

// Write guard bytes memcpy(raw, &GUARD_PATTERN, GUARD_SIZE); memcpy(raw + GUARD_SIZE + size, &GUARD_PATTERN, GUARD_SIZE);

return raw + GUARD_SIZE; }

void debug_free(void *ptr) { uint8_t *raw = (uint8_t *)ptr - GUARD_SIZE; uint32_t guard;

memcpy(&guard, raw, GUARD_SIZE); assert(guard == GUARD_PATTERN && "Pre-guard corrupted — buffer underflow");

// Check post-guard — need to know allocation size (store it in header) free(raw); }

Logic Analyzer for Protocol Debugging

When your I2C sensor does not respond, a logic analyzer tells you exactly what is happening on the wire. A Saleae Logic or DSLogic at $50–400 captures millions of samples with protocol decoding.

Connect probes to SDA, SCL, and GND. Run the I2C decoder. You will see exactly which address is being sent, whether the device ACKs, and which register is being accessed. Common findings:

  • Wrong pull-up resistor value causing signal rise time violations
  • Clock stretching by the sensor not being honored by the master
  • Wrong I2C address (0x76 vs 0x77 for BME280 based on SDO pin)
  • Missing repeated start between write and read phases
  • For SPI, look at CS timing, clock phase/polarity (CPOL/CPHA), and MISO setup time. For UART, check baud rate match and framing errors.

    Serial Tracing with SEGGER RTT

    UART printf is intrusive — it blocks for milliseconds, changing timing. SEGGER RTT (Real-Time Transfer) writes to a ring buffer in RAM that OpenOCD reads out via JTAG without halting the CPU. Zero timing impact on your firmware.

    #include "SEGGER_RTT.h"

    void init_rtt(void) { SEGGER_RTT_Init(); SEGGER_RTT_WriteString(0, "RTT initialized\r\n"); }

    // In ISR — safe, non-blocking, microseconds overhead void TIM2_IRQHandler(void) { SEGGER_RTT_printf(0, "TIM2 ISR: tick=%lu\r\n", HAL_GetTick()); // ... control loop }

    View output in J-Link RTT Viewer or OpenOCD's RTT support. You get printf-style debug output from inside ISRs without corrupting timing.

    Race Condition Detection

    Race conditions in FreeRTOS firmware are notoriously hard to find. Symptoms: data corruption that only appears under load, crashes that happen once every thousand cycles, behavior that changes when you add debug prints.

    Systematic approach:

  • 1. Identify all shared state (global variables, hardware peripherals)
  • 2. For each shared resource, trace all access paths in both tasks and ISRs
  • 3. Verify each access is protected by a mutex or is atomic (single-instruction read/write of aligned 32-bit value on Cortex-M)
  • 4. Use GDB watchpoints on suspected shared variables to catch concurrent access
  • The watchpoint fires on the second access to the shared memory address. If the call stacks of the two accesses do not agree on which task/ISR owns the resource, you have found your race.

    [Contact Code Caracal](/contact) — we build production firmware for clients across 15+ countries.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    IoT Dashboard · 12 min read

    Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    Embedded Systems · 14 min read

    IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 10 min read

    MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    IoT Engineering · 12 min read

    WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    IoT Engineering · 12 min read

    Building a Production IoT Gateway with Raspberry Pi and Node.js

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options