Debugging Guide
The ESP32 Distance Sensor project supports full GDB debugging in both emulator (QEMU) and hardware modes. This guide focuses on QEMU debugging since it's now 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:
- Open the Debug view (Ctrl+Shift+D / Cmd+Shift+D)
- Select "QEMU: GDB" from the dropdown
- 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 Distance Sensor Starting...");
// Conditional breakpoint: Right-click → Add Conditional Breakpoint
if (distance < 10) {
// Break only when distance < 10
}
}
Watch Variables
Monitor variables in real-time:
- Watch Panel: Add expressions to watch
- Variables Panel: Inspect local and global variables
- Hover: Mouse over variables to see current values
Call Stack
View the complete function call hierarchy:
#0 distance_sensor_read() at distance_sensor.c:45
#1 sensor_task() at main.c:123
#2 vPortTaskWrapper() at port.c:168
#3 0x400d1234 in ?? ()
Step Through Code
Key | Action | Description |
---|---|---|
F10 | Step Over | Execute current line, don't enter functions |
F11 | Step Into | Enter function calls |
Shift+F11 | Step Out | Continue until current function returns |
F5 | Continue | Run until next breakpoint |
Debug Configuration
The project includes two debug configurations in .vscode/launch.json
:
QEMU: GDB (Recommended)
{
"name": "QEMU: GDB",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/distance.elf",
"miDebuggerServerAddress": "localhost:3333",
"cwd": "${workspaceFolder}",
"setupCommands": [
{ "text": "target remote localhost:3333" },
{ "text": "file build/distance.elf" },
{ "text": "thbreak app_main" },
{ "text": "continue" }
]
}
Features:
- Connects to QEMU GDB server
- Breaks at
app_main()
automatically - Full source-level debugging
- Works in GitHub Codespaces
ESP32 Hardware (Alternative)
For debugging on real ESP32 hardware with JTAG adapter:
{
"name": "ESP32: OpenOCD",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/distance.elf",
"miDebuggerServerAddress": "localhost:3333",
"cwd": "${workspaceFolder}",
"setupCommands": [
{ "text": "target remote localhost:3333" },
{ "text": "mon reset halt" },
{ "text": "flushregs" },
{ "text": "thbreak app_main" },
{ "text": "continue" }
]
}
Requirements:
- ESP-PROG or similar JTAG adapter
- OpenOCD running with appropriate configuration
- Hardware connection (see ESP-IDF JTAG debugging guide)
Common Debugging Scenarios
Debug Network Stack
Set breakpoints in network components:
// In wifi_manager_sim.c
esp_err_t wifi_manager_init_sim(void)
{
ESP_LOGI(TAG, "Initializing network stack..."); // Breakpoint here
// Step through network initialization
esp_err_t ret = netif_uart_tunnel_init(&tunnel_config);
return ret;
}
Debug UART Tunnel
Watch packet flow in the IP tunnel:
// In netif_uart_tunnel_sim.c
static void uart_rx_task(void *arg)
{
while (1) {
// Breakpoint here to see incoming packets
int len = uart_read_bytes(UART_NUM, len_buf, 2, portMAX_DELAY);
// Add to watch: len_buf[0], len_buf[1]
}
}
Debug Web Server
Trace HTTP request handling:
// In web_server.c
static esp_err_t root_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Handling root request"); // Breakpoint here
// Watch variables: req->uri, req->method
httpd_resp_send(req, index_html_start, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
Debug Sensor Reading
Monitor distance measurements:
// In distance_sensor.c
esp_err_t distance_sensor_read(uint16_t *distance_cm)
{
// Breakpoint here
gpio_set_level(TRIGGER_PIN, 1);
esp_rom_delay_us(10);
gpio_set_level(TRIGGER_PIN, 0);
// Watch: *distance_cm after measurement
*distance_cm = calculated_distance;
return ESP_OK;
}
Advanced Debugging
GDB Command Line
Access GDB directly for advanced commands:
# In another terminal
xtensa-esp32-elf-gdb build/distance.elf
# Connect to QEMU
(gdb) target remote localhost:3333
# Load symbols
(gdb) file build/distance.elf
# Set breakpoint
(gdb) break app_main
# Continue execution
(gdb) continue
# Print variables
(gdb) print distance_cm
# Backtrace
(gdb) bt
# Examine memory
(gdb) x/16xb 0x3ffb0000
Memory Inspection
View memory regions:
# Check heap status
(gdb) call heap_caps_print_heap_info(MALLOC_CAP_8BIT)
# Inspect task stack
(gdb) info threads
(gdb) thread 2
(gdb) bt
FreeRTOS Task Debugging
List all tasks:
(gdb) info threads
# Switch to specific task
(gdb) thread 3
# View task stack
(gdb) bt full
Debugging Tips
1. Enable Verbose Logging
Before debugging, increase log verbosity:
// In component file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
// Or in sdkconfig
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
2. Use Assertions
Add runtime checks:
#include "esp_assert.h"
void process_data(uint8_t *buffer) {
assert(buffer != NULL);
ESP_ERROR_CHECK(process_buffer(buffer));
}
3. Watchpoints
Break when variable changes:
(gdb) watch distance_cm
(gdb) continue
# Breaks when distance_cm is modified
4. Conditional Breakpoints
Break only when condition is true:
(gdb) break distance_sensor_read if distance_cm < 10
Or in VS Code: Right-click breakpoint → Edit Breakpoint → Add condition
Troubleshooting
Debugger Won't Connect
Problem: VS Code can't connect to GDB server
Solutions:
# 1. Check if QEMU is running
ps aux | grep qemu
# 2. Verify GDB port is open
nc -zv localhost 3333
# 3. Restart QEMU
./tools/stop_qemu.sh
./tools/run-qemu-network.sh
# 4. Wait for QEMU to be ready (script does this automatically)
No Symbols / Source Code
Problem: Debugger shows assembly instead of C code
Solutions:
# 1. Ensure project is built with debug symbols
idf.py menuconfig
# Component config → Compiler options → Optimization Level → Debug (-Og)
# 2. Rebuild
idf.py fullclean
idf.py build
# 3. Verify ELF file exists
ls -lh build/distance.elf
Breakpoints Not Working
Problem: Breakpoints are ignored or show as "unverified"
Solutions:
- Ensure file is actually compiled (not excluded by #ifdef)
- Check optimization level (too high optimization can skip code)
- Verify you're debugging the correct build
- Try
thbreak
(temporary hardware breakpoint) instead
Slow Debugging
Problem: Stepping through code is very slow
This is normal in QEMU! QEMU emulation is slower than real hardware.
Tips to improve:
- Use breakpoints instead of stepping
- Run to cursor (Right-click → Run to Cursor)
- Skip uninteresting functions with Step Over
Next Steps
- QEMU Emulator Guide - Learn more about QEMU setup
- Network Internals - Debug network issues
- ESP-IDF JTAG Debugging - Official Guide