docs/requirements/interception-preload-mechanism.md
When the user runs bear -- make on Linux, Bear intercepts every process
execution that happens during the build by injecting a shared library into
the build process. The user does not need to modify the build system or
install special compiler wrappers -- Bear works transparently with any
build tool that spawns compiler processes.
On macOS the same mechanism uses DYLD_INSERT_LIBRARIES instead of
LD_PRELOAD. On both platforms the effect is the same: Bear sees every
exec call and reports it to the collector for semantic analysis.
exec family calls, posix_spawn, popen, and system are
interceptedlibsandbox.so) are
preserved in the preload variableLD_PRELOAD), macOS
(DYLD_INSERT_LIBRARIES)Statically linked executables: a statically linked executable does not load the dynamic linker, so the preload mechanism cannot intercept it -- a fundamental limitation of the approach.
Environment stripped before execvp (and other exec variants
without an explicit environment argument): the preload library restores
the preload variable only when the call passes an explicit envp. If
the build removes LD_PRELOAD from its environment and then spawns
children via execvp, grandchild processes may not be intercepted.
Wrong ELF class during cross-compilation (issue #236): the preload library is built for the host architecture, so when the build invokes a cross-compiler targeting a different architecture the dynamic linker rejects the library with "wrong ELF class". The build still completes, but the cross-compiled commands are not intercepted.
glibc symbol-version mismatch in cross-compilation (discussion #707): the preload library is linked against the host's glibc. If it is loaded into a compiler running against an older-glibc sysroot and references a newer glibc symbol version, that invocation fails with a "version not found" error and the command is not recorded.
macOS SIP (issue #558): System Integrity Protection strips
DYLD_INSERT_LIBRARIES for protected executables, so preload cannot
intercept them. Which mode Bear selects as a result is owned by
interception-mode-selection.
Preload conflicts with sandboxes (issue #699): a co-resident
LD_PRELOAD sandbox library that hooks the same exec family can
re-assert its own preload variable downstream in the exec chain and drop
Bear's entry, so a grandchild past that point is not intercepted. Bear
cannot prevent this without refusing to delegate to the other library,
which would disable the sandbox and alter the build.
Affects all child processes (issue #444): LD_PRELOAD applies to
every process spawned during the build, not just compilers, which can
disturb non-compiler tools sensitive to preloaded libraries. The
semantic analysis layer filters non-compiler commands from the output,
but the preload injection itself cannot be selective.
Given a project with a single C source file on Linux:
When the user runs
bear -- cc -c test.c, thencompile_commands.jsonis created with one entry fortest.c, and the build exit code is preserved (zero for success).
Given a build system that clears the environment:
When a build script runs
env -i cc -c test.cand the compiler is launched viaexecve(or another function with an explicitenvp), then the preload library restoresLD_PRELOADin the child, and the compilation is still intercepted and appears in the output.
Given a build whose steps spawn the compiler through functions other
than plain exec:
When one step launches
cc -c a.cviaposix_spawn, anothercc -c b.cviapopen, and a thirdcc -c c.cviasystem, then all three compilations are intercepted and appear incompile_commands.json.
Given a build during which the collector is unreachable:
When the preload library cannot deliver its report for
cc -c test.c, then the compiler still runs, the build's output and exit code are unchanged, and the only effect is that the entry fortest.cis missing from the compilation database.
Given a parallel build with multiple source files:
When the user runs
bear -- make -j4on a project with four source files, then all four compilations appear incompile_commands.json, and no reports are lost.
Given a build whose last compiler reports immediately before the build process exits:
When that final report races the shutdown of interception, then it is still delivered before interception stops, and that last compilation still appears in
compile_commands.json(no entry is lost to the shutdown race -- see issue #704).
Given a build that invokes non-compiler commands:
When the build runs
cp,mkdir, andcc -c test.c, then all three executions are reported to the collector, but only theccinvocation appears in the final compilation database (non-compiler commands are filtered by semantic analysis, not by the preload library).
Given an existing LD_PRELOAD value in the environment:
When the user has
LD_PRELOAD=/usr/lib/libsandbox.soset before running Bear, then the effectiveLD_PRELOADcontains Bear's library first, followed by/usr/lib/libsandbox.so, and both libraries are preserved in child processes.
Given preload mode active on a macOS host with SIP disabled (which
invocations select preload is owned by interception-mode-selection):
When the build spawns a compiler, then
DYLD_INSERT_LIBRARIESis set in the environment of the build's child processes, and compiler invocations are intercepted the same way as on Linux.
cc1, cc1plus, collect2, etc.)
are intercepted and reported but filtered out during semantic analysis,
not in the preload library itself. See output-json-compilation-database
for details on which commands appear in the output.interception-wrapper-mechanism (alternative
interception mode); which mode runs and when is owned by
interception-mode-selection.