-
Notifications
You must be signed in to change notification settings - Fork 0
Windows Memory Diagnostics
This build system supports producing debug-symbol builds specifically
tuned for investigating memory issues with Visual Studio's Native
Memory diagnostic tools. This page covers the two relevant build flags,
how to get and use cv2pdb, and the actual profiling workflow.
This is a diagnostic build path — not something to ship. Expect a real
performance cost, particularly with NO_INLINE=1 set.
DEBUG_SYMBOLS=1 ./build-libmpv-mingw64.sh v0.41.0Keeps -g (debug info) through the build, disables stripping, and
triggers cv2pdb PDB generation for the resulting DLLs. This is not
a full unoptimized debug build — FFmpeg/mpv/libplacebo are still built
with their normal optimization level (debugoptimized buildtype where
applicable), so runtime timing and reproduction behavior stay close to
a normal release build. Only debug info retention changes.
DEBUG_SYMBOLS=1 NO_INLINE=1 ./build-libmpv-mingw64.sh v0.41.0Additionally disables compiler inlining (-fno-inline and related
flags), while keeping other -O2 optimizations. Use this alongside
DEBUG_SYMBOLS=1, not instead of it.
Why this matters: cv2pdb's DWARF-to-PDB conversion frequently cannot
cleanly separate an inlined callee from its caller. With inlining left
on, Visual Studio's Native Memory profiler call-stack view shows a lot
of [anon_XXXXXXX] / Unknown frames instead of real function names —
frequently one function's code gets folded into a neighboring one's
symbol range, making the profiler's attribution actively misleading
rather than just incomplete. NO_INLINE=1 forces every function
boundary to stay real and separately addressable, so the profiler's
symbol names become trustworthy.
Expect meaningfully slower playback with this set — fine for a short diagnostic session, not for extended testing or anything you'd ship.
cv2pdb converts the DWARF debug info that GCC/MinGW produces into a
PDB file Visual Studio can actually use — MinGW-built DLLs don't
produce PDBs natively, so without this step VS has no symbols for
them at all, debug build or not.
Upstream: rainers/cv2pdb
There's no simple installer. Two practical ways to get a working
cv2pdb.exe:
- GitHub Releases — rainers/cv2pdb/releases has prebuilt binaries attached to recent releases. This is the simplest path if a recent-enough release exists for your needs.
-
Bundled with Visual D — the Visual D
Visual Studio extension (a D-language plugin) ships
cv2pdb.exeas part of its installation, even if you have no interest in D itself. Historically this was the most reliable way to get a working build before GitHub Releases were consistently published.
Once you have cv2pdb.exe, put it somewhere on PATH (or reference
it directly) — the build script invokes it automatically when
DEBUG_SYMBOLS=1 is set; no separate manual conversion step is
needed as part of the normal build flow.
- Build with
DEBUG_SYMBOLS=1 NO_INLINE=1(see above). - Deploy the resulting DLLs (and their generated
.pdbfiles) alongside your application executable. - In Visual Studio: Debug → Windows → Show Diagnostic Tools (or just start debugging — the Diagnostic Tools window can open automatically depending on your settings).
- Open the Memory Usage tab.
- Take a snapshot at a meaningful baseline point (e.g. right after startup, or right before the action you want to investigate).
- Let the scenario run (playback, a stop/restart cycle, whatever you're investigating).
- Take a second snapshot.
- Set Compare With Baseline to the first snapshot.
- Switch to the Stacks view (not Types) — this groups allocations by call stack, which is what actually lets you trace growth back to a specific function.
- Sort by Size Diff (Bytes), descending, and drill into the largest contributors.
- With
NO_INLINE=1, every frame in the call stack should resolve to a real function name — noUnknown, no[anon_*]. If you still see unresolved frames, double check the PDB actually matches the DLL being debugged (rebuild + redeploy, don't mix an old DLL with a new PDB or vice versa). - "Heap Size (Diff)" in a snapshot comparison reflects net-still-live bytes — an allocation made and freed between the two snapshots does not show up in the diff. A large diff means something is genuinely still allocated at the second snapshot, not just that a lot of allocation activity happened in between.
- A large number attributed to one call stack does not by itself mean that function is broken — it may be legitimate, bounded buffering (demuxer cache, decode queues) that simply hasn't been exercised long enough yet to plateau. Compare multiple snapshots over a longer session before concluding something is unbounded — several real investigations here initially looked like leaks in a short test and turned out to cleanly plateau given enough time.
The very first snapshot in a session, with no baseline set, shows total current allocations — not net growth. A large number here during the first ~10-30 seconds is normal (startup buffering) and isn't itself evidence of anything. Always compare against a second snapshot, taken after a meaningful gap, before drawing conclusions.
Same caveat as the patch-content caching gap described on the
Custom Patches
page: the build script's stamp caching doesn't distinguish a
DEBUG_SYMBOLS=1/NO_INLINE=1 build from a normal one by content,
only by tag and the debug-symbols suffix. If you've previously built a
given tag without these flags, force a clean rebuild before adding
them:
rm -rf work/deps-ffmpeg7 work/deps-ffmpeg6 # whichever applies
git -C work/src/ffmpeg reset --hard
git -C work/src/ffmpeg clean -fd