952 lines
32 KiB
HTML
952 lines
32 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ESP32-C5 GDB Debugging Guide</title>
|
|
<style>
|
|
:root {
|
|
--primary-color: #0e7490;
|
|
--secondary-color: #357edd;
|
|
--bg-color: #f9f9f9;
|
|
--code-bg: #f0f0f0;
|
|
--border-color: #e0e0e0;
|
|
--text-color: #1a1a1a;
|
|
--success-color: #19a974;
|
|
--warning-color: #ff6300;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
line-height: 1.6;
|
|
color: var(--text-color);
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background: var(--bg-color);
|
|
}
|
|
|
|
header {
|
|
background: linear-gradient(135deg, #0891b2, var(--secondary-color));
|
|
color: white;
|
|
padding: 40px 30px;
|
|
border-radius: 8px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
header h1 {
|
|
margin: 0 0 10px 0;
|
|
font-size: 2.5em;
|
|
}
|
|
|
|
header p {
|
|
margin: 5px 0;
|
|
opacity: 0.9;
|
|
}
|
|
|
|
nav {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 30px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
nav h2 {
|
|
margin-top: 0;
|
|
color: var(--secondary-color);
|
|
}
|
|
|
|
nav ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
column-count: 2;
|
|
column-gap: 30px;
|
|
}
|
|
|
|
nav li {
|
|
margin: 8px 0;
|
|
}
|
|
|
|
nav a {
|
|
color: var(--secondary-color);
|
|
text-decoration: none;
|
|
transition: color 0.2s;
|
|
}
|
|
|
|
nav a:hover {
|
|
color: #0e7490;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
main {
|
|
background: white;
|
|
padding: 40px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
h2 {
|
|
color: var(--primary-color);
|
|
border-bottom: 2px solid var(--border-color);
|
|
padding-bottom: 10px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
h3 {
|
|
color: var(--secondary-color);
|
|
margin-top: 30px;
|
|
}
|
|
|
|
code {
|
|
background: var(--code-bg);
|
|
padding: 2px 6px;
|
|
border-radius: 3px;
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
pre {
|
|
background: var(--code-bg);
|
|
padding: 20px;
|
|
border-radius: 6px;
|
|
overflow-x: auto;
|
|
border-left: 4px solid var(--secondary-color);
|
|
}
|
|
|
|
pre code {
|
|
background: none;
|
|
padding: 0;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 20px 0;
|
|
background: white;
|
|
}
|
|
|
|
th {
|
|
background: var(--secondary-color);
|
|
color: white;
|
|
padding: 12px;
|
|
text-align: left;
|
|
font-weight: 600;
|
|
}
|
|
|
|
td {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
tr:hover {
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.alert {
|
|
padding: 15px 20px;
|
|
border-radius: 6px;
|
|
margin: 20px 0;
|
|
border-left: 4px solid;
|
|
}
|
|
|
|
.alert-info {
|
|
background: #e7f5ff;
|
|
border-color: var(--secondary-color);
|
|
color: #004085;
|
|
}
|
|
|
|
.alert-success {
|
|
background: #e7f9f0;
|
|
border-color: var(--success-color);
|
|
color: #155724;
|
|
}
|
|
|
|
.alert-warning {
|
|
background: #fff4e5;
|
|
border-color: var(--warning-color);
|
|
color: #856404;
|
|
}
|
|
|
|
.checkmark::before {
|
|
content: '✅ ';
|
|
}
|
|
|
|
.crossmark::before {
|
|
content: '❌ ';
|
|
}
|
|
|
|
.warning::before {
|
|
content: '⚠️ ';
|
|
}
|
|
|
|
footer {
|
|
text-align: center;
|
|
margin-top: 50px;
|
|
padding: 30px;
|
|
color: #666;
|
|
border-top: 1px solid var(--border-color);
|
|
}
|
|
|
|
.button {
|
|
display: inline-block;
|
|
background: var(--secondary-color);
|
|
color: white;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
text-decoration: none;
|
|
margin: 5px;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.button:hover {
|
|
background: #0e7490;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
body {
|
|
padding: 10px;
|
|
}
|
|
|
|
header {
|
|
padding: 30px 20px;
|
|
}
|
|
|
|
header h1 {
|
|
font-size: 2em;
|
|
}
|
|
|
|
main {
|
|
padding: 20px;
|
|
}
|
|
|
|
nav ul {
|
|
column-count: 1;
|
|
}
|
|
|
|
table {
|
|
font-size: 0.9em;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>ESP32-C5 GDB Debugging Guide</h1>
|
|
<p><strong>Author:</strong> Bob McMahon</p>
|
|
<p><strong>Hardware:</strong> ESP32-C5 DevKit (RISC-V)</p>
|
|
<p><strong>ESP-IDF:</strong> v6.0 or later</p>
|
|
<p><strong>Last Updated:</strong> December 2025</p>
|
|
</header>
|
|
|
|
<nav>
|
|
<h2>Table of Contents</h2>
|
|
<ul>
|
|
<li><a href="#introduction">Introduction</a></li>
|
|
<li><a href="#why-gdb">Why GDB Debugging?</a></li>
|
|
<li><a href="#capabilities">ESP32-C5 Debug Capabilities</a></li>
|
|
<li><a href="#prerequisites">Prerequisites</a></li>
|
|
<li><a href="#building">Building with Debug Symbols</a></li>
|
|
<li><a href="#starting">Starting a Debug Session</a></li>
|
|
<li><a href="#commands">Essential GDB Commands</a></li>
|
|
<li><a href="#strategies">Debugging Strategies</a></li>
|
|
<li><a href="#examples">Real-World Examples</a></li>
|
|
<li><a href="#troubleshooting">Troubleshooting</a></li>
|
|
<li><a href="#advanced">Advanced Techniques</a></li>
|
|
<li><a href="#resources">Resources</a></li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<main>
|
|
<section id="introduction">
|
|
<h2>Introduction</h2>
|
|
<p>The ESP32-C5 is Espressif's first RISC-V microcontroller with dual-band WiFi 6 (802.11ax) support. Unlike its Xtensa predecessors (ESP32, ESP32-S3), the ESP32-C5's RISC-V architecture and built-in USB-JTAG interface make debugging significantly easier.</p>
|
|
|
|
<p>This guide demonstrates how to use GDB (GNU Debugger) to debug ESP32-C5 firmware, focusing on real-world scenarios like troubleshooting WiFi driver issues, CSI configuration problems, and memory corruption.</p>
|
|
</section>
|
|
|
|
<section id="why-gdb">
|
|
<h2>Why GDB Debugging?</h2>
|
|
|
|
<p>Traditional debugging with <code>ESP_LOGI()</code> statements has limitations:</p>
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Method</th>
|
|
<th>Limitations</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Printf Debugging</strong></td>
|
|
<td>
|
|
• Alters timing and behavior<br>
|
|
• Cannot inspect internal driver state<br>
|
|
• Requires recompilation for each change<br>
|
|
• Output floods serial console
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>LED Blink Debugging</strong></td>
|
|
<td>
|
|
• Very limited information<br>
|
|
• Time-consuming iteration<br>
|
|
• Cannot show complex state
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="alert alert-success">
|
|
<strong>GDB debugging solves these problems:</strong>
|
|
<ul>
|
|
<li class="checkmark"><strong>Set breakpoints</strong> without modifying code</li>
|
|
<li class="checkmark"><strong>Inspect variables</strong> at any point in execution</li>
|
|
<li class="checkmark"><strong>Step through code</strong> line by line</li>
|
|
<li class="checkmark"><strong>Examine memory</strong> and registers</li>
|
|
<li class="checkmark"><strong>Watch variables</strong> for changes</li>
|
|
<li class="checkmark"><strong>View call stacks</strong> to understand program flow</li>
|
|
<li class="checkmark"><strong>Debug ESP-IDF internals</strong> (WiFi driver, FreeRTOS, etc.)</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="capabilities">
|
|
<h2>ESP32-C5 Debug Capabilities</h2>
|
|
|
|
<p>The ESP32-C5 has <strong>built-in USB-JTAG</strong> support, eliminating the need for external debug adapters:</p>
|
|
|
|
<h3>Hardware Features</h3>
|
|
<ul>
|
|
<li><strong>Built-in USB-JTAG</strong>: Debug over the same USB cable used for flashing</li>
|
|
<li><strong>4 Hardware Breakpoints</strong>: No speed penalty</li>
|
|
<li><strong>Unlimited Software Breakpoints</strong>: Via flash patching</li>
|
|
<li><strong>2 Watchpoints</strong>: Trigger on memory read/write</li>
|
|
<li><strong>Real-time Debugging</strong>: Debug live, running firmware</li>
|
|
</ul>
|
|
|
|
<h3>Comparison with Other ESP32 Chips</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Feature</th>
|
|
<th>ESP32 (Xtensa)</th>
|
|
<th>ESP32-S3 (Xtensa)</th>
|
|
<th>ESP32-C5 (RISC-V)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><strong>Debug Interface</strong></td>
|
|
<td>External JTAG required</td>
|
|
<td>Built-in USB-JTAG</td>
|
|
<td>Built-in USB-JTAG</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Debugger</strong></td>
|
|
<td>xt-gdb (Xtensa)</td>
|
|
<td>xt-gdb (Xtensa)</td>
|
|
<td>riscv32-esp-elf-gdb</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>Setup Complexity</strong></td>
|
|
<td>High (extra hardware)</td>
|
|
<td>Medium</td>
|
|
<td><strong>Low</strong> (just USB)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><strong>OpenOCD Support</strong></td>
|
|
<td>Mature</td>
|
|
<td>Mature</td>
|
|
<td>Good (ESP-IDF v6.0+)</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section id="prerequisites">
|
|
<h2>Prerequisites</h2>
|
|
|
|
<h3>Hardware</h3>
|
|
<ul>
|
|
<li><strong>ESP32-C5 DevKit</strong> with USB-C cable</li>
|
|
<li><strong>Host Computer</strong> running Linux, macOS, or Windows (WSL2)</li>
|
|
</ul>
|
|
|
|
<h3>Software</h3>
|
|
<ul>
|
|
<li><strong>ESP-IDF v6.0 or later</strong> (ESP32-C5 support)</li>
|
|
<li><strong>OpenOCD</strong> (included with ESP-IDF)</li>
|
|
<li><strong>GDB for RISC-V</strong> (riscv32-esp-elf-gdb, included with ESP-IDF)</li>
|
|
</ul>
|
|
|
|
<h3>Verify Installation</h3>
|
|
<pre><code># Check ESP-IDF version
|
|
idf.py --version
|
|
# Should show: ESP-IDF v6.0 or later
|
|
|
|
# Check GDB
|
|
riscv32-esp-elf-gdb --version
|
|
# Should show: GNU gdb (esp-gdb) 12.1 or later
|
|
|
|
# Check OpenOCD
|
|
openocd --version
|
|
# Should show: Open On-Chip Debugger 0.12.0-esp32 or later</code></pre>
|
|
</section>
|
|
|
|
<section id="building">
|
|
<h2>Building with Debug Symbols</h2>
|
|
|
|
<p>Debug symbols allow GDB to map machine code back to source code, showing variable names, function names, and line numbers.</p>
|
|
|
|
<h3>Method 1: Using menuconfig (Recommended)</h3>
|
|
<pre><code>cd ~/your-project
|
|
idf.py menuconfig</code></pre>
|
|
|
|
<p>Navigate to and configure:</p>
|
|
<pre><code>Component config
|
|
→ Compiler options
|
|
→ Optimization Level → Debug (-Og) ← Select this
|
|
→ [*] Generate debug symbols (-g) ← Enable
|
|
→ Debug information format → DWARF-4 ← Select</code></pre>
|
|
|
|
<p>Additional recommended settings:</p>
|
|
<pre><code>Component config
|
|
→ Compiler options
|
|
→ [*] Enable assertions (assert) ← Enable
|
|
→ [ ] Strip function/variable names ← DISABLE
|
|
|
|
Component config
|
|
→ FreeRTOS
|
|
→ [*] Enable stack overflow checks ← Enable
|
|
→ Check method → Canary bytes ← Select</code></pre>
|
|
|
|
<h3>Optimization Levels Explained</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Level</th>
|
|
<th>GCC Flag</th>
|
|
<th>Code Speed</th>
|
|
<th>Debug Quality</th>
|
|
<th>Use Case</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr style="background-color: #e7f9f0;">
|
|
<td><strong>Debug</strong></td>
|
|
<td><code>-Og</code></td>
|
|
<td>Medium</td>
|
|
<td><strong>Excellent</strong></td>
|
|
<td><strong>GDB debugging</strong> ✅</td>
|
|
</tr>
|
|
<tr>
|
|
<td>None</td>
|
|
<td><code>-O0</code></td>
|
|
<td>Slow</td>
|
|
<td>Excellent</td>
|
|
<td>Extreme debugging</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Size</td>
|
|
<td><code>-Os</code></td>
|
|
<td>Medium</td>
|
|
<td>Poor</td>
|
|
<td>Production</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Performance</td>
|
|
<td><code>-O2</code></td>
|
|
<td>Fast</td>
|
|
<td>Poor</td>
|
|
<td>Production</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="alert alert-warning">
|
|
<strong>For debugging, always use <code>-Og</code> (Debug level).</strong> It provides good performance while preserving all variable information for GDB.
|
|
</div>
|
|
|
|
<h3>Build Process</h3>
|
|
<pre><code>cd ~/your-project
|
|
|
|
# Clean previous build
|
|
idf.py fullclean
|
|
|
|
# Build with debug symbols
|
|
idf.py build
|
|
|
|
# Flash to device
|
|
idf.py -p /dev/ttyUSB0 flash</code></pre>
|
|
|
|
<h3>Verify Debug Symbols</h3>
|
|
<pre><code># Check if ELF file contains debug sections
|
|
riscv32-esp-elf-readelf -S build/your-project.elf | grep debug
|
|
|
|
# Expected output (debug sections present):
|
|
# [27] .debug_aranges PROGBITS 00000000 0f8a2c 004638 00 0 0 8
|
|
# [28] .debug_info PROGBITS 00000000 0fd064 19d4f4 00 0 0 1
|
|
# [29] .debug_abbrev PROGBITS 00000000 29a558 02b8f9 00 0 0 1
|
|
# [30] .debug_line PROGBITS 00000000 2c5e51 0e7a3c 00 0 0 1</code></pre>
|
|
</section>
|
|
|
|
<section id="starting">
|
|
<h2>Starting a Debug Session</h2>
|
|
|
|
<h3>Three-Step Debug Process</h3>
|
|
<ol>
|
|
<li><strong>Flash the firmware</strong> to the device</li>
|
|
<li><strong>Start OpenOCD</strong> to connect to the device</li>
|
|
<li><strong>Start GDB</strong> to control debugging</li>
|
|
</ol>
|
|
|
|
<h3>Step 1: Flash Firmware</h3>
|
|
<pre><code>cd ~/your-project
|
|
idf.py -p /dev/ttyUSB0 flash</code></pre>
|
|
|
|
<h3>Step 2: Start OpenOCD (Terminal 1)</h3>
|
|
<pre><code>cd ~/your-project
|
|
idf.py openocd</code></pre>
|
|
|
|
<div class="alert alert-info">
|
|
<strong>Leave this terminal running.</strong> OpenOCD acts as a bridge between GDB and the ESP32-C5.
|
|
</div>
|
|
|
|
<h3>Step 3: Start GDB (Terminal 2)</h3>
|
|
<pre><code>cd ~/your-project
|
|
idf.py gdb</code></pre>
|
|
|
|
<p>You're now in the GDB prompt and ready to debug!</p>
|
|
|
|
<h3>Quick Start Commands</h3>
|
|
<pre><code>(gdb) target remote :3333
|
|
(gdb) file build/your-project.elf
|
|
(gdb) monitor reset halt
|
|
(gdb) thbreak app_main
|
|
(gdb) continue</code></pre>
|
|
</section>
|
|
|
|
<section id="commands">
|
|
<h2>Essential GDB Commands</h2>
|
|
|
|
<h3>Navigation and Execution</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Command</th>
|
|
<th>Shortcut</th>
|
|
<th>Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>break <location></code></td>
|
|
<td><code>b</code></td>
|
|
<td>Set breakpoint</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>continue</code></td>
|
|
<td><code>c</code></td>
|
|
<td>Resume execution</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>next</code></td>
|
|
<td><code>n</code></td>
|
|
<td>Step over (skip function calls)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>step</code></td>
|
|
<td><code>s</code></td>
|
|
<td>Step into (enter functions)</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>finish</code></td>
|
|
<td><code>fin</code></td>
|
|
<td>Run until function returns</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Inspection</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Command</th>
|
|
<th>Description</th>
|
|
<th>Example</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>print <var></code></td>
|
|
<td>Print variable value</td>
|
|
<td><code>p my_variable</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>print *<ptr></code></td>
|
|
<td>Dereference pointer</td>
|
|
<td><code>p *config</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>x/<fmt> <addr></code></td>
|
|
<td>Examine memory</td>
|
|
<td><code>x/32xb 0x40000000</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>info locals</code></td>
|
|
<td>Show local variables</td>
|
|
<td><code>i lo</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>backtrace</code></td>
|
|
<td>Show call stack</td>
|
|
<td><code>bt</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>list</code></td>
|
|
<td>Show source code</td>
|
|
<td><code>l</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Breakpoints & Watchpoints</h3>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Command</th>
|
|
<th>Description</th>
|
|
<th>Example</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>break <func></code></td>
|
|
<td>Break on function entry</td>
|
|
<td><code>b esp_wifi_init</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>break <func> if <cond></code></td>
|
|
<td>Conditional breakpoint</td>
|
|
<td><code>b send if len > 1000</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>watch <var></code></td>
|
|
<td>Break when variable changes</td>
|
|
<td><code>watch my_counter</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>info breakpoints</code></td>
|
|
<td>List all breakpoints</td>
|
|
<td><code>i b</code></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>delete <num></code></td>
|
|
<td>Delete breakpoint</td>
|
|
<td><code>d 1</code></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<section id="strategies">
|
|
<h2>Debugging Strategies</h2>
|
|
|
|
<h3>Strategy 1: Breakpoint at Function Entry</h3>
|
|
<p><strong>Use case:</strong> Understand when and why a function is called.</p>
|
|
<pre><code>(gdb) break esp_wifi_set_csi_config
|
|
(gdb) continue
|
|
# When it breaks...
|
|
(gdb) info args
|
|
(gdb) print *config
|
|
(gdb) backtrace
|
|
(gdb) continue</code></pre>
|
|
|
|
<h3>Strategy 2: Conditional Breakpoints</h3>
|
|
<p><strong>Use case:</strong> Break only when specific conditions occur.</p>
|
|
<pre><code># Break only when error occurs
|
|
(gdb) break esp_wifi_set_csi_config if $a0 != 0
|
|
|
|
# Break only for specific SSID
|
|
(gdb) break wifi_connect if strcmp(ssid, "MyNetwork") == 0
|
|
|
|
# Break when buffer is full
|
|
(gdb) break send_packet if queue_size >= 100</code></pre>
|
|
|
|
<h3>Strategy 3: Step Through Algorithm</h3>
|
|
<p><strong>Use case:</strong> Understand complex logic step by step.</p>
|
|
<pre><code>(gdb) break process_csi_data
|
|
(gdb) continue
|
|
(gdb) next # Execute current line
|
|
(gdb) next # Next line
|
|
(gdb) step # Step into function call if any
|
|
(gdb) finish # Complete current function</code></pre>
|
|
|
|
<h3>Strategy 4: Watch for Variable Changes</h3>
|
|
<p><strong>Use case:</strong> Find where a variable gets corrupted.</p>
|
|
<pre><code>(gdb) watch connection_state
|
|
(gdb) continue
|
|
# GDB will break when variable changes
|
|
(gdb) backtrace
|
|
(gdb) print connection_state</code></pre>
|
|
</section>
|
|
|
|
<section id="examples">
|
|
<h2>Real-World Examples</h2>
|
|
|
|
<h3>Example 1: Debug CSI Configuration Failure</h3>
|
|
<p><strong>Problem:</strong> <code>esp_wifi_set_csi_config()</code> returns <code>ESP_FAIL</code> but we don't know why.</p>
|
|
|
|
<pre><code>(gdb) break esp_wifi_set_csi_config
|
|
Breakpoint 1 at 0x42012a4e
|
|
|
|
(gdb) continue
|
|
Breakpoint 1, esp_wifi_set_csi_config (config=0x3ffb0000)
|
|
|
|
# Examine the config structure
|
|
(gdb) print *config
|
|
$1 = {
|
|
enable = 1,
|
|
lltf_en = 1,
|
|
htltf_en = 1,
|
|
stbc_htltf2_en = 1,
|
|
ltf_merge_en = 1,
|
|
channel_filter_en = 1, ← Suspicious!
|
|
manu_scale = 0
|
|
}
|
|
|
|
# Step through to see where it fails
|
|
(gdb) step
|
|
(gdb) step
|
|
...
|
|
(gdb) print error_code
|
|
$2 = 259 ← ESP_FAIL (0x103)
|
|
|
|
# Found it! channel_filter_en must be 0 on ESP32-C5</code></pre>
|
|
|
|
<div class="alert alert-success">
|
|
<strong>Solution:</strong> Set <code>channel_filter_en = 0</code> in the code.
|
|
</div>
|
|
|
|
<h3>Example 2: Find Memory Corruption</h3>
|
|
<p><strong>Problem:</strong> A pointer is getting corrupted, causing crashes.</p>
|
|
|
|
<pre><code># Set watchpoint on the pointer
|
|
(gdb) watch *(void**)&my_buffer_ptr
|
|
Hardware watchpoint 2: *(void**)&my_buffer_ptr
|
|
|
|
# Run until it changes
|
|
(gdb) continue
|
|
Hardware watchpoint 2: *(void**)&my_buffer_ptr
|
|
Old value = (void *) 0x3ffb1000
|
|
New value = (void *) 0x00000000
|
|
|
|
# See what code changed it
|
|
(gdb) backtrace
|
|
#0 process_packet (data=0x3ffb0800) at network.c:142
|
|
#1 0x42008654 in network_task () at network.c:201
|
|
|
|
# Look at the source
|
|
(gdb) list
|
|
137 void process_packet(uint8_t *data) {
|
|
138 if (data == NULL) {
|
|
139 ESP_LOGE(TAG, "Null data!");
|
|
140 my_buffer_ptr = NULL; ← Found it!
|
|
141 return;
|
|
142 }</code></pre>
|
|
|
|
<div class="alert alert-success">
|
|
<strong>Solution:</strong> Fix the null-pointer handling logic.
|
|
</div>
|
|
|
|
<h3>Example 3: Understand WiFi Connection Failure</h3>
|
|
<p><strong>Problem:</strong> WiFi connects but immediately disconnects.</p>
|
|
|
|
<pre><code>(gdb) break event_handler
|
|
(gdb) condition 1 event_id == WIFI_EVENT_STA_DISCONNECTED
|
|
(gdb) continue
|
|
|
|
Breakpoint 1, event_handler (event_id=3, event_data=0x3ffb2000)
|
|
|
|
# Examine disconnect reason
|
|
(gdb) print *(wifi_event_sta_disconnected_t*)event_data
|
|
$1 = {
|
|
ssid = "ClubHouse",
|
|
ssid_len = 9,
|
|
bssid = {0xe0, 0x46, 0xee, 0x07, 0xdf, 0x01},
|
|
reason = 2, ← WIFI_REASON_AUTH_EXPIRE
|
|
rssi = -75
|
|
}
|
|
|
|
# Reason 2 = Authentication expired = weak signal or interference</code></pre>
|
|
|
|
<div class="alert alert-success">
|
|
<strong>Solution:</strong> Improve antenna placement or reduce distance to AP.
|
|
</div>
|
|
</section>
|
|
|
|
<section id="troubleshooting">
|
|
<h2>Troubleshooting</h2>
|
|
|
|
<h3>Problem: "No symbol table is loaded"</h3>
|
|
<div class="alert alert-warning">
|
|
<strong>Symptom:</strong>
|
|
<pre><code>(gdb) break app_main
|
|
Function "app_main" not defined.</code></pre>
|
|
</div>
|
|
|
|
<p><strong>Solutions:</strong></p>
|
|
<pre><code># 1. Rebuild with debug symbols
|
|
idf.py menuconfig # Set optimization to Debug (-Og)
|
|
idf.py fullclean build
|
|
|
|
# 2. Load correct ELF file in GDB
|
|
(gdb) file build/your-project.elf
|
|
|
|
# 3. Verify symbols exist
|
|
riscv32-esp-elf-nm build/your-project.elf | grep app_main</code></pre>
|
|
|
|
<h3>Problem: "Cannot access memory at address 0x..."</h3>
|
|
<p><strong>Causes:</strong> Variable optimized out, out of scope, or invalid pointer</p>
|
|
|
|
<p><strong>Solutions:</strong></p>
|
|
<pre><code># Check if variable exists
|
|
(gdb) info locals
|
|
(gdb) info args
|
|
|
|
# Examine raw memory
|
|
(gdb) print &my_variable
|
|
(gdb) x/4xw 0x3ffb0000</code></pre>
|
|
|
|
<h3>Problem: Breakpoint Not Hitting</h3>
|
|
<p><strong>Solutions:</strong></p>
|
|
<pre><code># Check breakpoint status
|
|
(gdb) info breakpoints
|
|
|
|
# Try software breakpoint
|
|
(gdb) delete 1
|
|
(gdb) break my_func</code></pre>
|
|
</section>
|
|
|
|
<section id="advanced">
|
|
<h2>Advanced Techniques</h2>
|
|
|
|
<h3>Technique 1: Scripting GDB</h3>
|
|
<p>Create a <code>.gdbinit</code> file to automate common tasks:</p>
|
|
|
|
<pre><code># ~/.gdbinit or project/.gdbinit
|
|
|
|
# Connect automatically
|
|
target remote :3333
|
|
file build/CSI.elf
|
|
|
|
# Define custom commands
|
|
define reset-and-break
|
|
monitor reset halt
|
|
thbreak app_main
|
|
continue
|
|
end
|
|
|
|
# Set common breakpoints
|
|
break esp_wifi_set_csi_config
|
|
break esp_wifi_connect</code></pre>
|
|
|
|
<h3>Technique 2: Debugging FreeRTOS Tasks</h3>
|
|
<pre><code># Show all tasks
|
|
(gdb) info threads
|
|
Id Target Id Frame
|
|
* 1 Remote target vTaskDelay ()
|
|
2 Remote target prvIdleTask ()
|
|
3 Remote target wifi_task ()
|
|
|
|
# Switch to different task
|
|
(gdb) thread 3
|
|
|
|
# See that task's stack
|
|
(gdb) backtrace</code></pre>
|
|
|
|
<h3>Technique 3: Live Variable Modification</h3>
|
|
<p>Change variables on-the-fly without recompiling:</p>
|
|
|
|
<pre><code>(gdb) break send_packet
|
|
(gdb) continue
|
|
|
|
# Change packet size before sending
|
|
(gdb) print packet_size
|
|
$1 = 1024
|
|
(gdb) set packet_size = 64
|
|
|
|
# Continue with modified value
|
|
(gdb) continue</code></pre>
|
|
</section>
|
|
|
|
<section id="resources">
|
|
<h2>Resources</h2>
|
|
|
|
<h3>Official Documentation</h3>
|
|
<ul>
|
|
<li><a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/api-guides/jtag-debugging/">ESP-IDF GDB Guide</a></li>
|
|
<li><a href="https://www.espressif.com/sites/default/files/documentation/esp32-c5_datasheet_en.pdf">ESP32-C5 Datasheet</a></li>
|
|
<li><a href="http://openocd.org/doc/html/index.html">OpenOCD Manual</a></li>
|
|
<li><a href="https://sourceware.org/gdb/current/onlinedocs/gdb/">GDB Manual</a></li>
|
|
</ul>
|
|
|
|
<h3>ESP32 Community</h3>
|
|
<ul>
|
|
<li><a href="https://esp32.com/">ESP32 Forum</a></li>
|
|
<li><a href="https://reddit.com/r/esp32">r/esp32 Subreddit</a></li>
|
|
<li><a href="https://github.com/espressif/esp-idf">Espressif GitHub</a></li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section id="summary">
|
|
<h2>Summary</h2>
|
|
|
|
<p>GDB debugging on the ESP32-C5 provides powerful insights into firmware behavior:</p>
|
|
|
|
<div class="alert alert-success">
|
|
<ul>
|
|
<li class="checkmark"><strong>Built-in USB-JTAG</strong> eliminates external hardware requirements</li>
|
|
<li class="checkmark"><strong>Hardware and software breakpoints</strong> for flexible debugging</li>
|
|
<li class="checkmark"><strong>Real-time variable inspection</strong> without printf statements</li>
|
|
<li class="checkmark"><strong>Watchpoints</strong> to catch memory corruption</li>
|
|
<li class="checkmark"><strong>Call stack analysis</strong> to understand program flow</li>
|
|
<li class="checkmark"><strong>ESP-IDF driver debugging</strong> to troubleshoot library issues</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<p><strong>Key takeaways:</strong></p>
|
|
<ol>
|
|
<li>Always build with <strong>Debug (-Og)</strong> optimization for best debug experience</li>
|
|
<li>Use <strong>conditional breakpoints</strong> to break only when needed</li>
|
|
<li>Combine <strong>watchpoints</strong> with breakpoints to find memory corruption</li>
|
|
<li><strong>Script common tasks</strong> in <code>.gdbinit</code> for faster debugging</li>
|
|
<li>The <strong>WiFi driver log</strong> is still the ground truth for connection status</li>
|
|
</ol>
|
|
|
|
<p>GDB debugging significantly reduces debug time compared to printf-based approaches, especially for complex issues like WiFi driver bugs, FreeRTOS task interactions, and memory corruption.</p>
|
|
</section>
|
|
</main>
|
|
|
|
<footer>
|
|
<p><strong>About this guide:</strong> Created based on real-world ESP32-C5 development experience, specifically debugging WiFi 6 CSI (Channel State Information) capture issues for the iperf WiFi Analyzer project.</p>
|
|
|
|
<p>
|
|
<a href="https://github.com/iperf2/iperf2" class="button">iperf2 GitHub</a>
|
|
<a href="https://sourceforge.net/projects/iperf2/" class="button">iperf2 SourceForge</a>
|
|
</p>
|
|
|
|
<p><strong>Hardware:</strong> ESP32-C5 DevKit<br>
|
|
<strong>Project:</strong> WiFi Collapse Detection using CSI</p>
|
|
|
|
<p>© 2025 Bob McMahon. Last updated: December 4, 2025</p>
|
|
</footer>
|
|
</body>
|
|
</html>
|