These are the commands and checks I return to when I need a small Mesa build and a debug loop I can trust.
Build directories
A plain setup leaves driver lists on auto:
meson setup build -Dbuildtype=debug
ninja -C build
meson configure build shows the result afterwards. Once I know the API and driver, I use a narrower build directory:
meson setup build-intel -Dbuildtype=debug \
-Dvulkan-drivers=intel -Dgallium-drivers=iris
meson setup build-mali -Dbuildtype=debug \
-Dvulkan-drivers=panfrost -Dgallium-drivers=panfrost
Keeping a broad build beside a small one keeps configuration issues obvious.
Ninja targets
ninja -C build is incremental. Naming a target is still useful when the build contains unrelated tools or drivers.
ninja -C build -t targets all | rg 'libnir|libvulkan'
ninja -C build src/compiler/nir/libnir.a.p/nir.c.o
Object paths depend on the Meson target, so I take them from -t targets all instead of guessing.
These commands help inspect the work:
ninja -C build -n <target>
ninja -C build -t commands <target>
ninja -C build -v <target>
-t commands is useful when I need exact flags for one object.
Using the local build
Mesa's documented easy path is Meson's development environment:
meson devenv -C build vulkaninfo
meson devenv -C build glxinfo -B
It sets VK_DRIVER_FILES and adds the build's driver directories to LD_LIBRARY_PATH. Relative paths are resolved from the build directory.
For manual control, install into a disposable prefix:
export MESA_INSTALLDIR="$PWD/install"
meson setup build-install -Dprefix="$MESA_INSTALLDIR" <other options>
meson install -C build-install
VK_DRIVER_FILES="$MESA_INSTALLDIR/share/vulkan/icd.d/<driver>_icd.x86_64.json" vulkaninfo
LD_LIBRARY_PATH="$MESA_INSTALLDIR/lib64" glxinfo -B
The library directory may be lib rather than lib64. I check the selected implementation in vulkaninfo or glxinfo -B.
DRI_PRIME_DEBUG=1 explains device selection. DRI_PRIME picks another device. MESA_LOADER_DRIVER_OVERRIDE is only for debugging.
Finding a code path
Mesa is several shared layers in one source tree. I start from an API entry point, a debug string, a stack trace, or an IR dump, then follow the boundary I care about.
rg 'vkCreateGraphicsPipelines' src/
rg 'NIR_PASS.*nir_opt_' src/
rg 'DEBUG|DUMP|VALIDATE' src/<driver>/
Searching for the literal text of a log message is often faster than guessing the subsystem. Generated files can hide definitions, so I also search build/.
Debug flags
Mesa has common controls such as MESA_DEBUG, MESA_LOG_FILE, NIR_DEBUG, and GALLIUM_HUD, plus driver-specific variables.
NIR_DEBUG=help <small-reproducer>
GALLIUM_HUD=help <small-reproducer>
rg 'getenv\(|debug_get_flags|_DEBUG' src/<driver>/
Many driver variables also accept help. I start with the smallest reproducer, redirect noisy output, and keep the full environment with the result.
For compiler work, I compare before and after at the earliest IR boundary. For runtime problems, I separate loader selection, CPU work, submission, and GPU execution.
Sanitizer builds
Undefined behaviour and memory errors on the CPU side can look like driver or GPU failures. Mesa's debugging guide suggests UBSan and thread sanitizer. I also keep an ASan/UBSan build:
meson setup build-san -Dbuildtype=debugoptimized \
-Db_sanitize=address,undefined <driver options>
ninja -C build-san
meson devenv -C build-san <small-reproducer>
meson setup build-tsan -Dbuildtype=debugoptimized \
-Db_sanitize=thread <driver options>
ninja -C build-tsan
meson devenv -C build-tsan <small-reproducer>
debugoptimized keeps debug information and assertions. I still reduce a sanitizer report to a small reproducer before treating the first stack trace as the cause.
Bisecting regressions
If the same reproducer is good at an older commit and bad now, git bisect is usually better than reading the commits in between:
git bisect start <bad-commit> <good-commit>
git bisect run ./repro.sh
git bisect reset
The script must make its result mechanical: 0 for good, 1–127 except 125 for bad, and 125 when the commit cannot be tested. It should build only what is needed.
The loop
The loop I try to keep is: reproduce, capture one boundary, change one thing, rebuild the narrowest target, and compare. A known-good build or worktree helps.
Build options and debug flags are developer interfaces. These notes describe the method I use; check meson configure, the current documentation, and the source tree for the exact names in your checkout.