Skip to main content
The Android dynamic linker (linker for 32-bit, linker64 for 64-bit) is responsible for loading shared libraries (.so files) and resolving symbols at runtime. Understanding how it works is essential for debugging loading issues and optimizing your native code.

How the dynamic linker works

When your application loads a native library, the dynamic linker:
  1. Locates the library - Searches for the .so file in the application’s library path
  2. Loads dependencies - Recursively loads any libraries marked as NEEDED in the ELF header
  3. Resolves symbols - Maps function and variable references to their implementations
  4. Executes initialization - Runs any constructors and initialization functions

Library search paths

The dynamic linker searches for libraries in this order:
  1. DT_RUNPATH or DT_RPATH (if set in the ELF file)
  2. LD_LIBRARY_PATH environment variable (not available for normal apps)
  3. The application’s native library directory (e.g., /data/app/<package>/lib/arm64)
  4. System library directories (/system/lib64, /vendor/lib64)
Never rely on LD_LIBRARY_PATH for production apps. This variable is not available to normal applications for security reasons.

Loading shared libraries

Static dependencies (NEEDED entries)

Libraries can declare static dependencies using NEEDED entries in their ELF header. These are automatically loaded when the parent library loads:
Output example:
Each NEEDED entry increases load time. Only link against libraries you actually use. Use --as-needed linker flag to strip unnecessary dependencies.

Dynamic loading with dlopen()

You can load libraries at runtime using dlopen():

dlopen() flags

  • RTLD_NOW - Resolve all symbols immediately (recommended)
  • RTLD_LAZY - Defer symbol resolution until needed (can cause crashes later)
  • RTLD_LOCAL - Don’t make symbols available to subsequently loaded libraries
  • RTLD_GLOBAL - Make symbols available globally (use carefully)
Always use RTLD_NOW instead of RTLD_LAZY. Lazy loading can cause hard-to-debug crashes when symbols are accessed.

Symbol resolution and visibility

Symbol visibility

Control which symbols are exported from your library:
Or use a version script to control visibility:
Link with: -Wl,--version-script=version_script.txt

Symbol interposition

When multiple libraries export the same symbol, the first one loaded wins. This can cause unexpected behavior:
Use namespaces or static linking to avoid symbol conflicts.

Linker namespaces

Starting with Android 7.0 (API level 24), the dynamic linker uses namespaces to isolate system libraries from application libraries. This prevents apps from using private system APIs.

Namespace restrictions

  • Apps cannot load non-public system libraries
  • System libraries are in a separate namespace from app libraries
  • Attempting to load restricted libraries results in errors
Example error:
Do not attempt to use private system libraries. They may change or disappear between Android versions, breaking your app.

Common loading issues

Missing dependencies

Solutions:
  • Ensure all dependencies are packaged in your APK
  • Check for ABI mismatches (mixing 32-bit and 64-bit libraries)
  • Verify library names in NEEDED entries match actual filenames

Symbol not found

Solutions:
  • Verify the symbol exists: nm -D libexample.so | grep SomeFunction
  • Check for C++ name mangling issues (use extern "C")
  • Ensure the library exporting the symbol is loaded first

Text relocations

Text relocations are not allowed on Android 6.0+ (API level 23) for security reasons. Solution: Rebuild with position-independent code:

Performance considerations

Minimize library count

Each library has overhead:
  • Load time
  • Memory for ELF headers and tables
  • Symbol lookup time
Consider merging small libraries into larger ones.

Use symbol versioning

Symbol versioning allows you to maintain ABI compatibility while changing implementations:

Preload critical libraries

Load frequently-used libraries at app startup to avoid delays later:

Debugging linker issues

Enable linker debugging

Set environment variables to get detailed linker logs:

Check library dependencies

Additional resources

For more detailed information about dynamic linker changes across Android versions: