Effective debugging is essential for building stable plugins. iPlug2 provides logging, assertions, and debugger integration across all platforms.
Build Configurations
iPlug2 projects include multiple build configurations:
- Optimizations disabled (
-O0)
- Debug symbols included
- Assertions enabled
- Logging enabled
- Use for development
- Optimizations enabled (
-O3)
- Debug symbols stripped
- Assertions disabled
- Minimal logging
- Use for distribution
- Similar to Debug
- Extra verbose tracing
- Function entry/exit logging
- Use for deep debugging
Always test in both Debug and Release. Bugs can appear in only one configuration due to timing differences and optimizations.
Logging
DBGMSG - Debug Messages
Simple printf-style logging:
#include "IPlug_include_in_plug_src.h"
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
DBGMSG("ProcessBlock called with %d frames\n", nFrames);
double gain = GetParam(kParamGain)->Value();
DBGMSG("Gain = %.2f\n", gain);
}
DBGMSG prints to:
- macOS/iOS: Xcode console
- Windows: Visual Studio Output window or DebugView
- Linux: stdout
DBGMSG is compiled out in Release builds. Use it freely without performance concerns.
TRACE - Function Entry/Exit
Trace function calls automatically:
void OnReset() override
{
TRACE; // Prints: "OnReset"
SetSampleRate(GetSampleRate());
DBGMSG("OnReset complete\n");
}
Output:
Log Levels
Control verbosity with preprocessor defines:
config.h or project settings
// Verbose logging
#define TRACER_BUILD 1
// Disable all logging
#define NDEBUG 1
Assertions
Standard Assertions
Catch logic errors with assert():
#include <cassert>
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
assert(nFrames > 0 && "Invalid frame count");
assert(nFrames <= GetBlockSize() && "Frame count exceeds buffer size");
// Process...
}
Assertions trigger breakpoints in debug builds, abort in release (if enabled).
WDL Assertions
WDL provides its own assertion macro:
#include "wdlstring.h"
WDL_ASSERT(condition); // Similar to assert()
Debugger Integration
Xcode (macOS)
Build Debug Configuration
Select Debug scheme in Xcode
Set Breakpoints
Click line numbers to add breakpoints
Choose Host
Product → Scheme → Edit Scheme → Run → Executable → Choose host DAW
Run
Cmd+R to launch host with debugger attached
Recommended Hosts for Debugging:
- REAPER - Fast loading, stable, free evaluation
- Logic Pro - AU/AUv3 testing
- Standalone - No host needed
Visual Studio (Windows)
Build Debug Configuration
Set configuration to Debug in toolbar
Set Breakpoints
Click margin to add breakpoints (F9)
Configure Debugging
Project → Properties → Debugging
- Command: Path to host (e.g.,
C:\Program Files\REAPER\reaper.exe)
- Working Directory: Host folder
Run
F5 to launch with debugger attached
Standalone Debug Menu
Standalone builds have a built-in debug menu (Debug builds only):
// Available in debug standalone builds
// Access via right-click or menu bar
Features:
- Screenshot capture
- UI bounds visualization
- Control inspector
- FPS display
From CLAUDE.md: “When building standalone app targets only debug builds have the debug menu with screenshot capabilities”
Common Issues
Audio Dropouts/Glitches
Clicks, pops, or stuttering audio
- Memory allocation in
ProcessBlock()
- Locks/mutexes on audio thread
- Expensive operations (trigonometry, etc.)
- File I/O on audio thread
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
auto start = std::chrono::high_resolution_clock::now();
// Your processing...
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
// In debug builds only:
DBGMSG("ProcessBlock took %lld μs (budget: %lld μs)\n",
duration.count(),
(long long)(1000000.0 * nFrames / GetSampleRate()));
}
- Pre-allocate all buffers in
OnReset()
- Use lock-free data structures (
IPlugQueue)
- Optimize hot loops
- Move file I/O to background thread
Crashes
Null Pointer
Buffer Overflow
Data Race
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
// ❌ Can crash if UI isn't open
GetUI()->GetControlWithTag(kTag)->SetDirty();
// ✅ Check first
if(auto* pUI = GetUI())
pUI->GetControlWithTag(kTag)->SetDirty();
}
void OnReset() override
{
// ❌ Buffer too small
mBuffer.Resize(512);
}
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
// Can crash if nFrames > 512
memcpy(mBuffer.Get(), inputs[0], nFrames * sizeof(sample));
}
Fix: Use GetBlockSize()mBuffer.Resize(GetBlockSize());
// ❌ Not thread-safe
void OnParamChange(int paramIdx) override
{
mGain = GetParam(kParamGain)->Value(); // Main thread
}
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
double gain = mGain; // Audio thread - RACE!
}
Fix: Use atomicsstd::atomic<double> mGain {1.0};
Memory Leaks
Use sanitizers to detect leaks:
# Run with Leaks instrument
open -a "Instruments.app"
# Profile → Leaks → Choose target
valgrind --leak-check=full --show-leak-kinds=all ./MyPlugin
# Debug → Performance Profiler → Memory Usage
Debugging Techniques
Conditional Breakpoints
Break only when condition is true:
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
for(int s = 0; s < nFrames; s++)
{
// Set breakpoint here with condition: s == 100
outputs[0][s] = inputs[0][s];
}
}
In Xcode: Right-click breakpoint → Edit Breakpoint → Condition: s == 100
In Visual Studio: Right-click breakpoint → Condition → s == 100
Data Breakpoints
Break when a memory location changes:
class MyPlugin : public Plugin
{
private:
double mGain = 1.0; // Watch this variable
};
- Xcode: Right-click variable in debugger → Watch “mGain”
- Visual Studio: Right-click variable → Break when value changes
Logging Audio Values
Visualize Audio in Console
void ProcessBlock(sample** inputs, sample** outputs, int nFrames) override
{
#ifdef _DEBUG
static int counter = 0;
if(++counter % 1000 == 0) // Every ~20ms at 48kHz
{
DBGMSG("L: %.3f R: %.3f\n", outputs[0][0], outputs[1][0]);
}
#endif
// Processing...
}
Capture Buffers to File
Dump Audio to WAV (Debug Builds Only)
#ifdef _DEBUG
#include <fstream>
void DumpBufferToFile(sample* buffer, int nFrames, const char* filename)
{
std::ofstream file(filename, std::ios::binary);
// Simple 16-bit PCM WAV header
struct WAVHeader
{
char riff[4] = {'R','I','F','F'};
int32_t fileSize;
char wave[4] = {'W','A','V','E'};
char fmt[4] = {'f','m','t',' '};
int32_t fmtSize = 16;
int16_t audioFormat = 1;
int16_t numChannels = 1;
int32_t sampleRate = 48000;
int32_t byteRate = 96000;
int16_t blockAlign = 2;
int16_t bitsPerSample = 16;
char data[4] = {'d','a','t','a'};
int32_t dataSize;
} header;
header.dataSize = nFrames * 2;
header.fileSize = 36 + header.dataSize;
file.write(reinterpret_cast<char*>(&header), sizeof(header));
for(int i = 0; i < nFrames; i++)
{
int16_t sample = static_cast<int16_t>(buffer[i] * 32767.0);
file.write(reinterpret_cast<char*>(&sample), sizeof(sample));
}
}
#endif
macOS
Console.app: View all system logs including plugin output
# Filter logs for your plugin
log stream --predicate 'processImagePath contains "MyPlugin"'
lldb commands:
# Print variable
(lldb) p mGain
# Print expression
(lldb) p GetParam(0)->Value()
# Continue execution
(lldb) c
Windows
DebugView: Capture OutputDebugString() messages
Download from Microsoft Sysinternals
Visual Studio Immediate Window:
// Evaluate during debugging
? mGain
? GetParam(0)->Value()
Linux
GDB commands:
# Attach to running process
gdb -p $(pidof reaper)
# Set breakpoint
(gdb) break MyPlugin::ProcessBlock
# Print backtrace
(gdb) bt
Best Practices
Start Simple
Test basic functionality before adding complexity
Use Standalone Build
Debug in standalone first - faster iteration than loading in a host
Enable All Warnings
Treat warnings as errors during development
Test Small Buffers
Set host to 32-sample buffers to catch buffer size assumptions
Profile Realtime Performance
Use Instruments (macOS) or Very Sleepy (Windows) to profile CPU usage
Debugging Checklist
When something goes wrong:
See Also
Keep a debug build of your plugin and a matching host (like REAPER) for quick testing. This setup pays for itself immediately.