Intercept a 32-bit build on a 64-bit host
A build that runs 32-bit compilers or 32-bit build tools on a 64-bit
host records nothing from them: the build still exits 0, no error
appears, and compile_commands.json just comes out short or empty. The
preload library a normal Bear install ships is built for the host’s
64-bit ELF class, and the dynamic linker cannot inject a 64-bit shared
library into a 32-bit process (see How Bear
works
for what preload does and why the ELF class has to match). Exit
status covers why a missing
preload library never turns into a non-zero exit: the dynamic linker
treats it as something to warn about and skip, not to fail on, so
nothing about the run tells you a source file went unrecorded.
On glibc Linux this is fixable: build a second, 32-bit preload library,
install it alongside the normal 64-bit one, and point bear-driver at
both with a single INTERCEPT_LIBDIR setting. INTERCEPT_LIBDIR names
the directory bear-driver looks in for the preload library, relative
to its own bin/, and on glibc it accepts the literal string $LIB as
that name. The dynamic linker itself expands $LIB at load time, per
process: lib64 when loading into a 64-bit process, lib when loading
into a 32-bit one. One install then serves both kinds of process,
provided both libraries exist on disk.
Get a 32-bit toolchain and Rust target
# Fedora
sudo dnf install glibc-devel.i686 libgcc.i686 libatomic.i686
# Debian / Ubuntu
sudo apt install gcc-multilib
rustup target add i686-unknown-linux-gnu
For any other distribution, install whatever it calls its 32-bit development packages; the two lines above are only confirmed for Fedora.
Work around the missing cross driver
The preload library’s build script compiles a small C shim with
cc-rs, which looks for a i686-linux-gnu-gcc cross driver. Fedora (and
any distribution that provides multilib through gcc -m32 rather than a
separate cross-driver binary) has no such executable, so the build fails
with:
error occurred in cc-rs: failed to find tool "i686-linux-gnu-gcc"
There is no supported fix for this yet; the workaround is a two-line
shim on PATH that execs gcc -m32, pointed to by
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER:
cat >/tmp/i686-linux-gnu-gcc <<'EOF'
#!/bin/sh
exec gcc -m32 "$@"
EOF
chmod +x /tmp/i686-linux-gnu-gcc
export PATH="/tmp:${PATH}"
export CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER=/tmp/i686-linux-gnu-gcc
Build the 32-bit preload library
INTERCEPT_LIBDIR='$LIB' cargo build -p intercept-preload \
--target i686-unknown-linux-gnu
This produces target/i686-unknown-linux-gnu/debug/libexec.so, a 32-bit
libexec.so distinct from the normal 64-bit one.
Build and install the 64-bit Bear, then place both libraries
Build and install the regular 64-bit Bear with the same $LIB setting:
INTERCEPT_LIBDIR='$LIB' cargo build
SRCDIR=target/debug PREFIX=<prefix> INTERCEPT_LIBDIR='$LIB' ./scripts/install.sh
(See Install Bear for what PREFIX, DESTDIR,
and INTERCEPT_LIBDIR each control; the $LIB value here is what makes
this recipe work and is on top of what that page documents.)
install.sh does not understand $LIB as a dynamic-linker token: it
creates a directory literally named $LIB under <prefix>/libexec/bear/
and installs the 64-bit library there. Finishing the layout is a manual
step, and there is currently no supported way to have the build or
install scripts do it for you:
cd <prefix>/libexec/bear
mkdir -p lib lib64
mv 'target/i686-unknown-linux-gnu/debug/libexec.so' lib/libexec.so
mv '$LIB/libexec.so' lib64/libexec.so
rmdir '$LIB'
The result differs from the ordinary layout in exactly one place: the
single $INTERCEPT_LIBDIR directory becomes two, one per ELF class, and
no directory named $LIB is left behind.
$PREFIX/
|-- bin/
| `-- bear (shell script)
|-- libexec/
| `-- bear/
| |-- bin/
| | |-- bear-driver
| | `-- bear-wrapper
| |-- lib/
| | `-- libexec.so (ELF 32-bit)
| `-- lib64/
| `-- libexec.so (ELF 64-bit)
`-- share/ (unchanged)
Check the two libraries really are different ELF classes, and that the
32-bit one is the one under lib:
$ file <prefix>/libexec/bear/lib/libexec.so <prefix>/libexec/bear/lib64/libexec.so
libexec/bear/lib/libexec.so: ELF 32-bit LSB shared object
libexec/bear/lib64/libexec.so: ELF 64-bit LSB shared object
Swapping them produces the same silent, short database as having no
32-bit library at all: a 32-bit process looks only in lib, finds an
object of the wrong class, and skips it. That output is worth including
in any report about a multilib install. The rest of the tree, and what
PREFIX and DESTDIR mean, is in Install Bear.
Confirm it worked
Run the build under bear -- <build> as usual. If it drives any 32-bit
compiler or a 32-bit build tool that execs a compiler on its own behalf,
compare the entry count in compile_commands.json with and without
lib/libexec.so in place: removing the 32-bit library drops exactly the
entries that 32-bit process would have contributed, while the build
itself still exits 0. With the library missing, the dynamic linker also
prints the warning covered in Troubleshooting: LD_PRELOAD
errors - cannot be preloaded (cannot open shared object file): ignored. - which is the tell that this
is the missing-32-bit-library case rather than some other silent gap.
Limitations
- This works only on glibc Linux. musl, macOS, and the BSDs have no
$LIB-style token: they need a concrete library directory name instead, so one install cannot serve two ELF classes the same way. - The
cc-rsshim above is a workaround for a missing cross driver, not a fix; a distribution that shipsi686-linux-gnu-gccdirectly does not need it. scripts/install.shhas no option to produce thelib/lib64split itself. Repeat the manual step after every reinstall.
This is a different problem from a vendor cross-toolchain whose compiler driver is itself a 32-bit binary; that case, and wrapper mode as its fix, is covered in Generate compile_commands.json when cross-compiling.
Related pages
- Generate compile_commands.json when cross-compiling for the general cross-prefix and wrong-ELF-class story.
- Install Bear for
PREFIX,DESTDIR, andINTERCEPT_LIBDIR. - Exit status for why Bear exits
0even when a preload library never loaded. - Troubleshooting for
LD_PRELOADerrors and other silent interception failures. - Recipes for the rest of the task pages.