Debugging Guide

The ESP32 Template supports full GDB debugging in both emulator (QEMU) and hardware modes. This guide focuses on QEMU debugging since it’s fully supported in GitHub Codespaces.

Quick Start: Debugging in QEMU

1. Start QEMU Debug Server

The QEMU emulator automatically starts with GDB support enabled:

./tools/run-qemu-network.sh

This starts QEMU in debug mode, waiting for a debugger connection on port 3333.

2. Start Debugging in VS Code

Simply press F5 or use the Debug panel:

  1. Open the Debug view (Ctrl+Shift+D / Cmd+Shift+D)

  2. Select “QEMU: GDB” from the dropdown

  3. Click the green play button or press F5

VS Code will:

  • ✅ Connect to QEMU’s GDB server (port 3333)

  • ✅ Load symbols from the built ELF file

  • ✅ Break at app_main()

  • ✅ Show full call stack and variables

Debugging Features

Breakpoints

Set breakpoints by clicking in the editor gutter (left of line numbers):

void app_main(void)
{
    // Breakpoint here: Click in gutter at line number
    ESP_LOGI(TAG, "ESP32 Application Starting...");

    // Conditional breakpoint: Right-click → Add Conditional Breakpoint
    if (value < 10) {
        // Break only when value < 10
    }
}

Breakpoint Types:

  • Line Breakpoint: Break when reaching specific line

  • Conditional Breakpoint: Break only when condition is true

  • Logpoint: Log message without stopping (useful for tracing)

Step Through Code

Use VS Code debug toolbar or keyboard shortcuts:

  • F10 - Step Over (execute current line, don’t enter functions)

  • F11 - Step Into (enter function calls)

  • Shift+F11 - Step Out (return from current function)

  • F5 - Continue (run until next breakpoint)

Inspect Variables

Variables Panel: Shows local variables and function parameters automatically

Watch Panel: Add custom expressions to monitor

// Example: Monitor a struct member
config.led_count

// Example: Monitor array element
buffer[5]

// Example: Evaluate expression
(value * 2) > threshold

Call Stack

The Call Stack panel shows the complete function call hierarchy:

#0  my_function() at main.c:42
#1  process_data() at main.c:128
#2  app_main() at main.c:200
#3  main_task() at cpu_start.c:...

Click any frame to inspect its local variables and source code.

Debug Console

Execute GDB commands directly in the Debug Console:

# Print variable
p my_variable

# Print in hex
p/x my_variable

# Print array
p my_array[0]@10

# Call function
call my_debug_function()

Hardware Debugging

Debugging on Real ESP32

For hardware debugging, you need a JTAG adapter. The template doesn’t include hardware debugging configuration by default (focus is on QEMU).

For production projects, see ESP-IDF JTAG Debugging documentation.

Debug Configuration

QEMU Debug Configuration

The .vscode/launch.json contains the QEMU debug configuration:

{
    "name": "QEMU: GDB",
    "type": "cppdbg",
    "request": "launch",
    "program": "${workspaceFolder}/build/esp32-template.elf",
    "cwd": "${workspaceFolder}",
    "MIMode": "gdb",
    "miDebuggerPath": "xtensa-esp32-elf-gdb",
    "miDebuggerServerAddress": "localhost:3333"
}

Key Settings:

  • program: Path to ELF file with debug symbols

  • miDebuggerPath: GDB executable (ESP32 toolchain)

  • miDebuggerServerAddress: QEMU GDB server (port 3333)

Advanced Debugging

Debugging FreeRTOS Tasks

View FreeRTOS task information:

# In Debug Console
info threads

# Switch to specific task
thread 3

Memory Inspection

Inspect memory directly:

# View memory at address (hex)
x/16x 0x3FFB0000

# View memory as string
x/s 0x3FFB0000

# Examine stack
x/32x $sp

Performance Analysis

Measure execution time:

// Add timing code
uint64_t start = esp_timer_get_time();

// Your code here
my_function();

uint64_t elapsed = esp_timer_get_time() - start;
ESP_LOGI(TAG, "Execution time: %llu us", elapsed);

Set breakpoints before and after, inspect elapsed value.

Common Debugging Scenarios

Crash Analysis

When ESP32 crashes, look for:

  1. Stack trace in serial output

  2. Program Counter (PC) - use addr2line to find source line

  3. Register dump - shows CPU state at crash

# Convert crash address to source line
xtensa-esp32-elf-addr2line -e build/esp32-template.elf 0x400d1234

Memory Leaks

Monitor heap usage:

ESP_LOGI(TAG, "Free heap: %d bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "Min free heap: %d bytes", esp_get_minimum_free_heap_size());

Stack Overflow

Check stack high water mark:

UBaseType_t stack_left = uxTaskGetStackHighWaterMark(NULL);
ESP_LOGI(TAG, "Stack space left: %d words", stack_left);

Troubleshooting

GDB Won’t Connect

Problem: VS Code can’t connect to QEMU

Solutions:

  1. Ensure QEMU is running: ps aux | grep qemu

  2. Check port 3333 is available: lsof -i :3333

  3. Restart QEMU: ./tools/stop_qemu.sh && ./tools/run-qemu-network.sh

No Debug Symbols

Problem: Can’t see source code or variable names

Solutions:

  1. Ensure debug build: idf.py menuconfig → Component config → Compiler options → Optimization Level → Debug (-Og)

  2. Rebuild: idf.py fullclean build

Breakpoint Not Hit

Problem: Breakpoint is set but never triggers

Checks:

  1. Code is actually executed (not in dead code path)

  2. Compiler didn’t optimize code away (check disassembly)

  3. Breakpoint is in correct file (check paths match)

Tips and Best Practices

Efficient Debugging

Use Logpoints: Don’t stop execution, just log information

Conditional Breakpoints: Break only when specific conditions occur

Watch Expressions: Monitor key variables without manual inspection

Call Stack Navigation: Quickly find where problems originate

Logging vs Debugging

Use Logging For:

  • Production code monitoring

  • Long-running operations

  • Intermittent issues

  • Field diagnostics

Use Debugging For:

  • Development and troubleshooting

  • Complex state inspection

  • Step-by-step execution analysis

  • One-time investigation

Debug Optimization

During Development:

  • Use Debug (-Og) optimization for best debugging experience

  • Enable all debug symbols

For Release:

  • Switch to Release optimization (-O2 or -Os)

  • Disable verbose logging

  • Keep ESP_LOGI for important events

Resources

Next Steps