Embedded LLD: Native sanitizer linking (FreeBSD) #5424
Replies: 1 comment
|
Implemented in #5426. The spike came back clean: the fragment capture works unmodified against FreeBSD base clang (clang 19.1.7), so no FreeBSD-specific CMake handling was needed — just opening the two genexe gates to native FreeBSD. Beyond the original scope, the PR also makes the self-hosted tools (pony-lsp/lint/doc) link under sanitizers. FreeBSD's One FreeBSD-specific runtime knob is needed when the instrumented ponyc compiles optimized code: |
Uh oh!
There was an error while loading. Please reload this page.
This continues the native sanitizer work from #5399 and #5409. That pair moved native sanitizer linking on Linux off the legacy
system()compiler driver and onto embedded LLD. This one does FreeBSD.The Phase 7 progress note on #4941 lists what still drops to the legacy linker on its own: native sanitizer on macOS, native sanitizer on the BSDs, and FreeBSD
use=dtrace. Native sanitizer on the BSDs is three platforms wearing one description. They don't all behave the same against their own compiler driver, so they move one at a time rather than in a single sweep. FreeBSD is the first.What happens today
When ponyc is built with sanitizers, every program it links pulls in the sanitizer runtime, because the instrumented
libponyrtit links references__asan_*and the ubsan symbols. Embedded LLD doesn't go through a compiler driver, so there's no-fsanitize=to pull that runtime in. The Linux fix, from #5409, was to ask the compiler driver at ponyc build time for the exact link fragment it emits for-fsanitize=...and splice that fragment into the embedded ELF link. Two sites ingenexe.ccgate it to Linux: the splice itself (target_is_linux && !is_cross_compiling) and the routing inlink_exe, where the BSD branch reaches embedded LLD only when cross-compiling, so a native sanitizer build on FreeBSD falls through to the driver.Most of the machinery is already platform-agnostic. The fragment capture in the top-level
CMakeLists.txtisn't guarded by platform. It runs on any sanitizer-enabled configure, FreeBSD included, and the FreeBSD ELF recipe inlink_exe_lld_elfalready exists and is exercised on every non-sanitizer native FreeBSD build (#5368). So the change is small: open the two Linux gates to FreeBSD and leave DragonFly and OpenBSD where they are.The thing to validate first
The fragment capture diffs two
-###link lines, the baseline and the sanitized one, and assumes the sanitized line is an ordered supersequence of the baseline after one volatile LTO token is filtered out. That assumption held against Linux clang and gcc. It hasn't been run against FreeBSD's base clang.This is the load-bearing unknown. If FreeBSD clang emits its link line differently, with a per-invocation volatile token the filter doesn't catch, or a different ordering, or a multi-line shape that defeats the regex that finds the probe-object line, the capture fails loudly at configure time. It already runs on a FreeBSD sanitizer configure today; nothing exercises it because no FreeBSD CI builds with sanitizers. So step one is a spike in a FreeBSD environment matching the tier-3 image: configure a sanitizer build, confirm the capture succeeds and writes a fragment that actually names a sanitizer runtime, token-diff the two raw
-###outputs by hand rather than trusting that a non-empty capture is a correct one, build instrumentedlibponyrtand ponyc, then link and run a small program and confirm the sanitizer symbols resolve. And confirm the negative: empty the fragment and watch the link fail on undefined__asan_*symbols, so the smoke test that follows has teeth.If the capture needs FreeBSD-specific handling, that's an added step in the CMake block, and we'll know before writing any of the CI.
The code change
Two gates in
src/libponyc/codegen/genexe.cc, assuming the spike comes back clean:The splice gate gains FreeBSD:
(target_is_linux || target_is_freebsd) && !is_cross_compiling. Its comment block currently explains that native BSD sanitizer linking via embedded LLD is unverified and deliberately gated out. That reasoning was written for the Linux-only state and has to be rewritten, not just nudged. The fragment lands immediately before the object file, and the justification for that placement, that the runtime archives precede the instrumented object and the shared libraries are position-insensitive, was reasoned against the Linux library set. FreeBSD base clang pulls a different set, so the spike confirms the placement is still correct rather than assuming it.The routing gate in
link_exeopens to native FreeBSD while leaving DragonFly and OpenBSD on the driver: the sanitizer sub-gate becomesis_cross_compiling || target_is_freebsd. Non-sanitizer builds and cross builds are untouched; the only behavior that moves is native FreeBSD sanitizer linking. The branch sits under#if !defined(USE_DYNAMIC_TRACE), so a FreeBSDuse=dtracebuild still takes the legacy path, which is fine, sanitizer and dtrace builds don't mix.The smoke test
A basic one, in the tier-3 FreeBSD job, which runs weekly rather than per-PR. Build ponyc with the same sanitizer set the Linux weekly job uses (
pool_memalign,address_sanitizer,undefined_behavior_sanitizer), then compile, link, and run a small Pony program that allocates enough for the address sanitizer to engage, and assert a clean exit. The link is the thing under test. A broken splice shows up as undefined sanitizer symbols at link time, not as a runtime surprise.A few details the Linux weekly job already settled and FreeBSD inherits: the instrumented ponyc runs during its own build, compiling packages, so the build steps carry
ASAN_OPTIONS=detect_leaks=0and the ubsan options too, not just the run.detect_leaks=0also sidesteps FreeBSD's spotty LeakSanitizer support. The smoke lives in the tier-3 job because that's where the FreeBSD VM infrastructure already is. It runs before the existing dtrace smoke, since both clean and reconfigure the debug build and the order has to be deliberate rather than left to chance.In scope
Out of scope, tracked elsewhere
use=dtraceon embedded LLD. Already tracked, and independent of sanitizers.system()linker and the--linker/--link-ldcmdoptions, which can't finish until the sanitizer follow-ups above are all off legacy.Design: #4941
All reactions