Mobile Dev

Android NDK arm64 16KB Page Alignment: Fix & Verify

Step-by-step guide to find, diagnose and fix libsc-native-lib.so 16KB page-size alignment for arm64-v8a. Tools, linker flags, packaging, CI checks and NDK r28+

1 answer 1 view

How can I ensure libsc-native-lib.so uses a 16 KB page-size alignment for arm64-v8a on Android? I need APK 16 KB page alignment: the lib that appears to come with the NDK/Android SDK is 16 KB aligned under armeabi-v7a but not for arm64-v8a. I’m using AGP 8.13.2, Gradle 8.13 and NDK r28. Where can I find these native libraries in the SDK/NDK, and how can I force the linker or the build/packaging process to select or produce 16 KB page-size–compliant native libs for arm64? What tools, build flags, or manual inspection/packaging steps can I use to diagnose and fix the alignment mismatch?

Android NDK r28 defaults to 16 KB–aligned shared libraries for 64‑bit ABIs, but prebuilt or dependency .so files can still be 4 KB aligned. To fix libsc-native-lib.so for arm64-v8a: locate the arm64 copy (NDK prebuilt, AAR or merged APK), inspect it with llvm-objdump or readelf, then rebuild or relink that library with the linker flag -Wl,-z,max-page-size=16384 (or add target_link_options / ndk-build flags) and ensure your Gradle/AGP uses NDK r28 via ndkVersion.


Contents


Quick checklist

  • Build a release APK/AAB and inspect lib/arm64-v8a/libsc-native-lib.so inside it (Android Studio → Build → Analyze APK or unzip the artifact).
  • Run llvm-objdump -p or readelf -l on the arm64 .so and confirm the PT_LOAD alignment is 0x4000 (16 KB).
  • If the arm64 .so is 4 KB: determine whether it’s (a) built by your code, (b) bundled in an AAR, or © a prebuilt from the NDK/SDK.
  • Fix by rebuilding/relinking with -Wl,-z,max-page-size=16384 for that library (best), or replace the packaged .so with a 16 KB–aligned copy (workaround).
  • Make sure your app/module uses the intended NDK (set android.ndkVersion in build.gradle) and Build Tools 35+. See the official guidance for details.

Find libsc-native-lib.so in SDK/NDK and APK

Where these files live (common places to search)

  • In the final APK/AAB: lib/arm64-v8a/libsc-native-lib.so (or inside split APKs). Use Android Studio → Build → Analyze APK or:
  • unzip app-release.apk -d /tmp/apk && ls /tmp/apk/lib/arm64-v8a/
  • In your app’s build output (merged native libs):
  • app/build/intermediates/merged_native_libs//out/lib/arm64-v8a/
  • In local dependency AARs (third‑party SDKs):
  • unzip path/to/dependency.aar -l | grep libsc-native-lib.so
  • In the NDK/toolchain/sysroot/prebuilt trees (prebuilt runtime libs):
  • $(NDK_ROOT)/toolchains/llvm/prebuilt//lib64 or under the NDK sysroot paths (see the NDK r28 notes).

Quick find commands (adjust paths for your host: linux-x86_64, darwin-x86_64, windows-x86_64):

# search SDK / NDK
find "$ANDROID_SDK_ROOT" "$ANDROID_NDK_ROOT" -type f -name "libsc-native-lib.so" -print

# inspect an AAR
unzip -l path/to/library.aar | grep libsc-native-lib.so

# inspect merged native libs after a build
ls -la app/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a/

If you find multiple copies (armeabi-v7a vs arm64-v8a), note their origins — arm64 is the one Google Play checks for 16 KB alignment on modern devices.


Diagnose alignment: tools and commands

Tools you’ll use

  • llvm-objdump (shipped with the Android NDK)
  • readelf (on Linux/macOS)
  • Android Studio APK Analyzer or unzip
  • Android check scripts (AOSP/NDK examples like check_elf_alignment.sh / atest elf_alignment_test)

How to check alignment (clear, reproducible commands)

  1. Extract the arm64 .so from the APK/AAR (example):
unzip -p app-release.apk lib/arm64-v8a/libsc-native-lib.so > /tmp/libsc-arm64.so
  1. Check with llvm-objdump from your NDK:
NDK=/path/to/android-ndk-r28
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump -p /tmp/libsc-arm64.so | grep -A1 LOAD

Look for an alignment value that equals 16384 or for text like ALIGNMENT 2**14 (some versions display the power-of-two).

  1. Or check with readelf:
readelf -l /tmp/libsc-arm64.so
# In the Program Headers table look at the "Align" column for LOAD entries.
# Align = 0x1000 => 4096 (bad)
# Align = 0x4000 => 16384 (good)

Example interpretation:

  • Align column shows 0x1000 → library is 4 KB aligned (problem for pure 16 KB devices).
  • Align column shows 0x4000 → library is 16 KB aligned (OK for arm64/x86_64 targets).

Quick script that fails on any non‑16KB .so inside an APK (adapt host paths):

bash
NDK=/path/to/android-ndk-r28
OBJDUMP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump
unzip -q app-release.apk -d /tmp/apk
for f in /tmp/apk/lib/*/*.so; do
 echo "Checking $f"
 $OBJDUMP -p "$f" | grep -q 'p_align' && \
 $OBJDUMP -p "$f" | grep -q 'p_align 16384' || \
 echo "BAD: $f is not 16KB aligned"
done

(If your version of llvm-objdump prints alignment differently, fall back to readelf parsing.)

References that explain the checks and recommended tools are in the Android docs and NDK changelog — see the Sources below.


Force 16 KB page-size alignment in Android NDK builds (arm64-v8a)

First rule: if you control the native source, relink/rebuild the library — that’s the correct fix. Changing ELF headers after-the-fact is unsafe.

  1. Ensure your project actually uses NDK r28 (or newer) and Build Tools 35+
  • In module build.gradle:
groovy
android {
 compileSdk 34
 buildToolsVersion "35.0.0"
 ndkVersion "28.0.12433566" // pin the r28 NDK you have installed
}

AGP + a pinned ndkVersion makes Gradle use the r28 toolchain across modules and CI.

  1. Add the linker option that forces a 16 KB max page size
  • Best placed in the native module’s CMakeLists.txt so it affects that library’s link step:
cmake
# target-specific (preferred)
target_link_options(my_native_target PRIVATE "-Wl,-z,max-page-size=16384")

# or globally (if you can't edit the target)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384")
# Application.mk
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true # r27 guidance; see NDK docs for variable semantics
# In Android.mk add to LOCAL_LDFLAGS for the module:
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
  1. If the native module is built by a dependency (Prefab / third‑party CMake), you must apply the flags to that module itself. Common problems:
  • Prefab consumers or prebuilt modules ignore flags added only at the app level. Add flags in the native library’s CMake, or rebuild the dependency with r28.
  • Some toolchains default to flexible behavior on older NDKs — explicitly pass the linker flag.
  1. When you can’t rebuild the module inside its own tree
  • Use app/src/main/jniLibs/arm64-v8a/libsc-native-lib.so to override dependency libs at packaging time. Put a 16 KB–aligned lib there (Gradle will merge/override). This is a stopgap until the real rebuild happens.
  1. Confirming success
  • Rebuild the AAB/APK, extract the arm64 .so and re-run the readelf/llvm-objdump checks. The Align column must read 0x4000.

Why this works: the linker option -Wl,-z,max-page-size=16384 tells the GNU/Clang linker to set PT_LOAD p_align to 16384 (2**14) when producing the ELF, which makes the shared object 16 KB–aligned. The NDK r28 change makes this the default for 64‑bit ABIs, but prebuilt or older-built binaries won’t benefit until rebuilt (see NDK r28 changelog).

Helpful links:


Third‑party / prebuilt libraries and packaging fixes

If the arm64 .so came prebuilt (inside an AAR or SDK), you have three practical options:

  1. Ask the SDK/vendor for a 16 KB–aligned arm64 build. That’s the cleanest solution.

  2. Rebuild the offending library from source using NDK r28+ or by adding -Wl,-z,max-page-size=16384 to its linker flags (recommended if you control the source).

  3. Replace the packaged arm64 .so at build time (workaround)

  • Unpack the AAR, swap in a fixed lib, rezip and publish a local AAR, or place the fixed .so in your app at:
  • app/src/main/jniLibs/arm64-v8a/libsc-native-lib.so
    Gradle will package the app’s jniLibs version ahead of the dependency’s library, so this effectively overrides it.
  • Example AAR repack workflow (quick hack):
bash
unzip library.aar -d /tmp/aar
cp fixed/lib/arm64-v8a/libsc-native-lib.so /tmp/aar/jni/arm64-v8a/libsc-native-lib.so
cd /tmp/aar
zip -r ../library-fixed.aar *

Caveats: repacking is a temporary fix and may violate vendor licensing; re-linking at source remains best.

Why you can’t just patch the ELF header reliably: changing p_align after linking is not guaranteed safe. Android’s guidance and practice is to relink with the correct max page size.


Automate checks in CI and pre-upload scripts

Fail the build if any 64-bit native library is not 16 KB aligned. Example GitHub Actions job (conceptual; adapt to your runners and NDK path):

yaml
jobs:
 check-16kb:
 runs-on: ubuntu-latest
 steps:
 - uses: actions/checkout@v3
 - name: Set up JDK
 uses: actions/setup-java@v3
 with: java-version: '17'
 - name: Build release AAB
 run: ./gradlew :app:bundleRelease
 - name: Unzip AAB and extract native libs
 run: |
 unzip app/build/outputs/bundle/release/app-release.aab -d /tmp/aab
 # adjust to exact path for base.apk inside the aab or unpack produced APKs
 - name: Check 16KB alignment
 run: |
 NDK=/opt/android-ndk-r28
 OBJDUMP=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump
 find /tmp/aab -name '*.so' | while read f; do
 echo "Checking $f"
 $OBJDUMP -p "$f" | grep -q 'p_align.*16384' || { echo "$f is NOT 16KB aligned"; exit 1; }
 done

You can adapt the script to use readelf parsing if your objdump prints alignment differently. Several community posts show reproducible GitHub Actions examples for this check.

See a community CI example and tooling tips here: https://medium.com/@shiva999421/androids-16kb-page-size-revolution-a-complete-developer-s-guide-for-react-native-apps-2c0cd43abdd5


Sources


Conclusion

In short: verify which copy of libsc-native-lib.so is actually ending up in lib/arm64-v8a of your APK/AAB, inspect it with llvm-objdump or readelf, then rebuild/relink that library with -Wl,-z,max-page-size=16384 (or add the equivalent target_link_options / ndk-build LDFLAGS). Make sure Gradle/AGP is pinned to NDK r28 (or newer) so that your Android NDK toolchain produces 16 KB–aligned arm64 binaries by default; if you can’t rebuild a third‑party .so, replace it at packaging time with a fixed copy or request a vendor update.

Authors
Verified by moderation
Moderation
Android NDK arm64 16KB Page Alignment: Fix & Verify