STIG Help

Build portability and the target-architecture baseline

The analyzer (stig-code-analyzer, linking PhASAR) is built once and run elsewhere: CI bakes it into the prebuilt toolchain image on one runner, then the build/test and release jobs run it on a different runner; release artifacts also ship to customer machines we don't control. A binary compiled for the build machine's exact CPU is therefore a portability hazard.

The rule: never -march=native for the analyzer

-march=native makes the compiler emit instructions for the CPU doing the compile (AVX2, BMI2, FMA, …). Run that binary on a CPU missing one of those and it SIGILLs at runtime — illegal instruction, process dies with rc=132 (128 + SIGILL(4)).

This bit CI. PhASAR's CMakeLists.txt silently defaults Release builds to -march=native (the PHASAR_TARGET_ARCH block — despite a comment claiming it is "disabled by default"), and the analyzer inherited it. The rc=132 matrix crashes (ifds-fieldsens-taint NO_REPORT, production combo degraded) were exactly this: the image was baked on a runner with BMI2 and the matrix ran on a runner without it.

It was not the host compiler. clang-18 builds a fully correct analyzer (matrix 94.4 %, rc=132×0) whenever build-host == run-host — a same-machine test can never reproduce a cross-runner crash, which is why an early "switch to clang-22 / Fedora" attempt looked like a fix but only moved the build and run onto the same CPU. The portability lever is -march, not the compiler version.

The fix: pin x86-64-v2

stig-code-analyzer/scripts/build-analyzer.sh pins a portable micro-architecture baseline for both native components:

Component

How

PhASAR

-DPHASAR_TARGET_ARCH=x86-64-v2 (overrides the Release→native default)

stig-code-analyzer

-march=x86-64-v2 in CMAKE_CXX_FLAGS

x86-64-v2 (SSE4.2 + POPCNT + CMPXCHG16B — universal on server/desktop CPUs since ~2009) is safe on every realistic CI runner and customer machine, and is ~free on performance for PhASAR's workload: pointer-chasing graph analysis with no auto-vectorizable hot loops (libphasar emits zero ymm/zmm).

Verifying portability

objdump -d the binary and count the genuinely-unsafe instructions — the VEX-encoded BMI1/BMI2 set, which faults on a CPU without those features:

objdump -d stig-code-analyzer | \ grep -coE '\b(bzhi|pdep|pext|mulx|sarx|shlx|shrx|rorx|andn|bextr|blsi|blsr|blsmsk)\b' # must be 0

Two classes are not a hazard and are expected to remain:

  • tzcnt/lzcnt — their REP-prefixed encodings execute as bsf/bsr on older CPUs, so they degrade instead of faulting.

  • AVX ymm/zmm inside LLVM's BLAKE3 hash (llvm_blake3_hash_many_avx2, …) — LLVM selects these at runtime via CPU-feature dispatch, so they only execute where supported.

A dynamically-linked dependency (e.g. libz3.so) is not part of the binary and is the distro's portability concern, not ours.

Versions (recap)

  • Host compiler (Job 1 — builds PhASAR/the analyzer): not pinned to one version across distros, and that's fine — any reasonably modern clang produces a correct analyzer. See LLVM toolchain. Fedora's libstdc++-16 forces host clang ≥ 22; Ubuntu noble's stock is clang-18; both work.

  • LLVM library + bitcode target: pinned to 18 via stig-code-analyzer/scripts/pins.env (LLVM_MAJOR).

  • Customer note: customer builds on Ubuntu/clang-18 — supported, because the fix is the portable -march, not a newer compiler.

23 October 2026