Friday, June 13, 2025
Debugging your Audio Unit plugin with auvaltool (aka auval)

Sudara
Sudara is building Sine Machine, an additive synth in JUCE, writing about technical challenges along the way.
https://melatonin.devFor an Audio Unit v2 plugin to show up in DAWs on the Mac like Apple's Logic Pro, it must pass an Apple validation tool named auvaltool
.
That makes working with auvaltool
pretty much mandatory at some point when shipping cross-platform plugins.

Maybe you'll get lucky and not have to deal with it much. But for the rest of us, here's an in-depth guide.
auval
vs. auvaltool
?
A tiny bit of history: Older versions of macOS shipped with a tool called auval
. It later became a wrapper script that ran auvaltool
.
In more recent macOS versions (macOS >= 15) auval
is just a symlink to auvaltool
:
> ls -ahl /usr/bin/auval lrwxr-xr-x 1 root wheel 18B Apr 12 07:16 /usr/bin/auval -> /usr/bin/auvaltool
People still refer to the tool as auval
but I'll call it auvaltool
in this article.
Pluginval runs auvaltool
An easy way to use auvaltool
is via pluginval
, a open-source meta-validator for all audio plugin formats built by Tracktion.
In addition to other tests, pluginval runs auval
under the hood. I consider it mandatory for shipping cross-platform plugins and wrote more about it here.
To be precise, pluginval runs auval -strict
by default (when pluginval's strictness-level
is 5 or under).
It adds -stress 20
when strictness-level
is above 5, which does the following:
run additional multi-thread stress test for N simulated seconds of audio I/O
good for catching race condition bugs
(default without specifying optional duration argument is 600 seconds)
Note that you can FAIL pluginval on strictness-level
> 5 but still PASS auvaltool
run by Logic. That's because Logic's incantation doesn't include -stress
(ain't nobody got time for stress testing dozens of plugins at startup!). However, if you are failing -stress
you have some unresolved issues very much worth figuring out (don't we all).
Audio Units are registered to the system
Audio Units are unique in that macOS has a system registry for them. A service called AudioComponentRegistrar
is in charge of registering plugins.
It keeps an eye on /Library/Audio/Plug-Ins/Components/
and the user version ~/Library/Audio/Plug-Ins/Components/
— when something new is detected in these folders, it adds any valid AudioUnits to an internal database.
The registrar can be a source of difficulty for plugin devs. Sometimes your plugin doesn't show up and you don't know why. You've probably seen this snippet floating around or even asked customers to run this in Terminal:
killall -9 AudioComponentRegistrar
It basically just re-triggers the registrar to run by turning it on and off again.
Calling auvaltool on the command line
Due to this system registry, you can't directly pass a path to a .component
on the filesystem to auvaltool
. Instead you refer to its 4 digit manufacturer and plugin code. Then auvaltool
queries the registry to find it.
On the command line you'll specify -v TYPE SUBT MANU
to validate one specific plugin.
This is what it looks like for me:
auval -strict -v aumu SM22 Mela
aumu
is my TYPE
. In this example, I have a musical instrument. If you have an audio effect it's aufx
. It might also be aumf
if you have an audio effect with instrument capabilities. You can find all the types here.
SM22
is my JUCE PLUGIN_CODE
MELA
is my JUCE PLUGIN_MANUFACTURER_CODE
Figuring out your manufacturer and plugin code
You might remember these from when you first setup your JUCE plugin.
In CMake, they are the PLUGIN_MANUFACTURER_CODE
and PLUGIN_CODE
in your juce_add_plugin
call.
In Projucer, they are in the main project settings (click the gear icon).

Debugging your plugin's code in auval
Both pluginval
and auvaltool
expose edge cases in your binary that you can't easily reproduce otherwise.
For example, auvaltool
will run processBlock
calls from the message thread. This might break a few things. Or you might have memory that isn't fully initialized at startup that gets exposed.
It's possible to run pluginval
in a debugger and have it stop on breakpoints in your code.
Unfortunately, by default you can't attach a debugger to auvaltool
. If you try, you'll see this:
process exited with status -1 (attach failed (Not allowed to attach to process.
This is because auvaltool
doesn't have the correct entitlement to allow debugging,com.apple.security.get-task-allow bool true
.
> codesign -d --entitlements - /usr/bin/auval Executable=/usr/bin/auvaltool [Dict] [Key] com.apple.security.cs.allow-unsigned-executable-memory [Value] [Bool] true [Key] com.apple.security.cs.disable-library-validation [Value] [Bool] true [Key] com.apple.security.temporary-exception.audio-unit-host [Value] [Bool] true
In the past, the solution was to copy the binary into the user directory and adhoc codesign it, adding the required entitlement.
Unfortunately on recent versions of macOS, auvaltool
is an ARMe
build which has pointer authentication:
> lipo -archs /usr/bin/auvaltool x86_64 arm64e
Pointer authentication encodes a signature into pointers. It mixes in a key derived from the binary’s code signature. So resigning breaks the binary and the trick of resigning and adding the debug entitlement no longer works.
Option 1: Use the x86_64 "slice"
The auvaltool
binary is a universal binary, with both ARMe and x86_64 binaries in there. You can run the x86_64 binary, and assuming you built your .component
for x86_64 as well, then you can debug that binary.
You'll still need to make a copy of the tool and code sign it so that you can debug it:
cp /usr/bin/auvaltool . codesign -s - -f auvaltool # ahhoc sign ./auvaltool # this won't work on arm macs arch -x86_64 ./auvaltool # but this works
This route works only works if you build your AU for x86_64. Also, be aware that Rosetta 2 will be sunset in Fall 2027 (macOS 28) which means this route will no longer work at some point in the future.
Related short story: I contacted Apple Code-level support (Developer Technical Support) to see what the workarounds might be and if they were aware of the problem.
They instructed me to file an issue via apple Feedback (which I did), but confirmed that one workaround is to run the x86_64 binary.
Option 2: Disable SIP in Safe Mode
Unless the CoreAudio team responds to my Feedback request, the only other route to debug AU plugins is to boot into Safe Mode and disable System Integrity Protection.
To boot into Safe Mode on Apple Silicon:
- Turn your mac off.
- Press and hold the power button on your Mac until the system volume and the Options button appear.
- Click
Options
>Continue
>Select your volume
and log in as an admin. - From the menu bar select
Utilities
>Terminal
. - Type
csrutil disable
and when it's done, reboot.
After this, you will be able to debug /usr/bin/auvaltool
itself with no need to copy or make modifications.
On macOS 15, this route does not require copying and code signing the copy. In fact, that won't work. In other words, once SIP is disabled, you can just debug the main /usr/bin/auvaltool
.
Once you are done debugging, reboot into Safe Mode and run csrutil enable
to turn SIP back on.
Have your IDE debug auvaltool
Once you've dealt with SIP, it's pretty easy to tell your IDE to run and attach to auvaltool
in the debugger. You'll tell it to attach to /usr/bin/auvaltool
directly if you've disabled SIP, otherwise point it to your copied version of the binary. Pass it the arguments that identify your plugin.

Assuming you have your arguments in order, you should be able to set breakpoints and see assertions:

Errors and auvaltool
Make sure your component exists
In order for auval to run, your component needs to be in one of two locations:
~/Library/Audio/Plug-Ins/Components/
/Library/Audio/Plug-Ins/Components/
It cannot be in any other location.
AUv3 can mask AUv2 plugins
If you are building both with JUCE, note that auvaltool -al
may only list the AUv3 plugin. A workaround is to delete the .appex
to see the AUv2 again.
auvaltool error codes
AudioUnit errors often don't give much to go on, just an error code. If you use JUCE, you won't run into most of the error codes, as JUCE handles things (or errors itself).
However, here's a list of errors and their codes. You can pop an error code into OSStatus.com.
4099
This error code pops up a lot as either Cannot open component: 4099
or FATAL ERROR: Initialize: result: 4099
As Timothy notes on the forum, check your audio/midi inputs/outputs and busses match the type of audio unit you are registering.
Worst case resolutions
Remember, the component registry is a system cache of your plugins. If you are seeing issues persist when they shouldn't (for example, within Logic Pro), do one or more of the following:
- Remove the plugin completely from the system (look in both
~/Library
and/Library
variants of the components folder) and then runauvaltool
again. - Log out and back in again or run
killall -9 AudioComponentRegistrar
- In the worst case, change your plugin name/manufacturer.
Start selling through Moonbase
Sign up today to get started selling your software through Moonbase.