Skip to content

Android: Rustler 0.37 init crashes with undefined symbol: enif_priv_data #7

@GenericJam

Description

@GenericJam

Symptom

A project with a Rustler-backed NIF that's enabled for Android (e.g. `mob.exs` `:static_nifs` entry with `archs: [:all]` or `[:android_arm64, :android_arm32]`) deploys cleanly but the BEAM dies on app launch with:

```
thread '' panicked at rustler-0.37.4/src/sys/nif_filler.rs:27:70:
called `Result::unwrap()` on an `Err` value:
DlSym { source: "undefined symbol: enif_priv_data" }
```

Reproduced today against `nif_combo` on the Android emulator (`emulator-5554`, sdk_gphone64_arm64) under OTP 29.0 (`@otp_hash 550d7b78`).

Root cause

`rustler-0.37.4/src/sys/nif_filler.rs` populates its NIF dispatch table by calling:

```rust
let lib = unsafe { Library::open(None::<&OsStr>, RTLD_NOW | RTLD_GLOBAL) };
// then for each enif_* symbol:
self.lib.get::(name.as_bytes()).unwrap();
```

`Library::open(None, ...)` opens the main executable's symbol scope (effectively `dlopen(NULL, ...)`). On iOS/macOS that scope sees everything in the binary, including the BEAM's `enif_` functions. On Android, the app library (`libnif_combo.so` here) is loaded by the Java runtime with `RTLD_LOCAL`, so its statically-linked-in `enif_` symbols are NOT in the global scope. The first `get("enif_priv_data")` returns `DlSym` error, `.unwrap()` panics, the BEAM dies.

This is the same shape of issue we hit and worked around for the BEAM's own crypto NIF dispatch (load the static `crypto_nif_init` via `erts_static_nif_tab` instead of dlopen `crypto.so`). Rustler's filler doesn't know about that machinery and falls into the same RTLD_LOCAL hole.

Confirmed scope

  • C NIF (handwritten, no rustler init filler) ✓ works on Android
  • Pythonx ✓ works on Android (no rustler)
  • Zigler-backed NIF ✓ works on Android (after the Bionic-stdlib fix in zigler#zigler-android-armv7)
  • Rustler-backed NIF ✗ panics at init

iOS device + iOS sim are unaffected (Rustler runs cleanly there).

Fix candidates (ordered by intrusiveness)

  1. Rustler upstream patch — extend `nif_filler.rs` with an Android branch that resolves symbols via a different mechanism (e.g. an init-time function pointer table passed in by the host instead of dlsym).

  2. Pre-load via RTLD_GLOBAL from JNI — before `mob_start_beam()`, have the JNI-side code re-`dlopen` its own .so with `RTLD_GLOBAL` so its symbols become visible to subsequent `dlopen(NULL)` calls. Cheap; doesn't require touching rustler.

  3. Linker flags on libnif_combo.so — pass `-Wl,--export-dynamic` (or equivalent) at the link step so `enif_*` symbols land in the dynamic symbol table even though the shared library is loaded RTLD_LOCAL. Need to verify whether dlopen(NULL) on Android sees the dynsym table of the calling library; if yes, this is a one-line zig build change.

  4. Provide a custom init helper — expose a `mob_rustler_init(table_ptr)` from `libbeam.a` that hands rustler the dispatch table directly, then patch rustler to call that helper on Android.

Option 2 is probably the smallest viable patch. Option 3 is the cleanest if it works.

Reference

  • The other agent's `mob_dev-android-armv7` merge note claimed "Rustler and rustler-related dlsym fallback already arch-agnostic" but that turns out to refer to mob_dev's wiring (per-ABI cross-compile, target atom split), not the rustler runtime symbol resolution. Verifying today against nif_combo with all three Android targets enabled, the Rustler crash is reproducible.
  • Companion issue #6 is unrelated — that's the C → Zig port for HeroesLament's vendor_usb PR.

Workaround for now

Set `archs: [:ios]` on Rust NIF entries in `mob.exs` until this is fixed. C, Zig, and Python NIFs all work on Android.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions