|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Verifies native libraries (`.so`) bundled inside an Android APK/AAB have |
| 4 | +# their ELF LOAD segments aligned to at least 16 KB (`p_align >= 0x4000`). |
| 5 | +# |
| 6 | +# Android 15+ devices with a 16 KB page size (and Google Play's 16 KB |
| 7 | +# requirement) reject apps that ship a `.so` aligned to only 4 KB. Libraries |
| 8 | +# built from source with NDK r27 and earlier default to 4 KB, so this check |
| 9 | +# guards against a regression like https://github.com/getsentry/sentry-react-native/issues/6394 |
| 10 | +# where `libsentry-tm-perf-logger.so` shipped misaligned. |
| 11 | +# |
| 12 | +# By default every `.so` is checked. Pass a name filter (a regex matched |
| 13 | +# against each library's path) to restrict the check to the libraries this |
| 14 | +# repo actually controls — third-party/React Native libraries are aligned by |
| 15 | +# their own build (RN ships arm64 at 16 KB but x86 at 4 KB, so a whole-APK |
| 16 | +# check is not meaningful on the x86 builds CI produces). |
| 17 | +# |
| 18 | +# Usage: scripts/check-android-16kb-alignment.sh <path-to-apk-or-aab> [name-filter-regex] |
| 19 | +# e.g. scripts/check-android-16kb-alignment.sh app.apk 'libsentry' |
| 20 | +# |
| 21 | +# `readelf` is resolved from (in order): $READELF, `llvm-readelf`, `readelf`. |
| 22 | + |
| 23 | +set -euo pipefail |
| 24 | + |
| 25 | +REQUIRED_ALIGN=16384 # 16 KB, expressed in bytes |
| 26 | + |
| 27 | +apk="${1:-}" |
| 28 | +name_filter="${2:-}" |
| 29 | +if [[ -z "$apk" || ! -f "$apk" ]]; then |
| 30 | + echo "usage: $0 <path-to-apk-or-aab> [name-filter-regex]" >&2 |
| 31 | + exit 2 |
| 32 | +fi |
| 33 | + |
| 34 | +readelf_bin="${READELF:-}" |
| 35 | +if [[ -z "$readelf_bin" ]]; then |
| 36 | + if command -v llvm-readelf >/dev/null 2>&1; then |
| 37 | + readelf_bin="llvm-readelf" |
| 38 | + elif command -v readelf >/dev/null 2>&1; then |
| 39 | + readelf_bin="readelf" |
| 40 | + else |
| 41 | + echo "error: neither 'llvm-readelf' nor 'readelf' found on PATH (set \$READELF to override)" >&2 |
| 42 | + exit 2 |
| 43 | + fi |
| 44 | +fi |
| 45 | + |
| 46 | +workdir="$(mktemp -d)" |
| 47 | +trap 'rm -rf "$workdir"' EXIT |
| 48 | + |
| 49 | +# Extract only the native libraries. APK and AAB both store them under a |
| 50 | +# top-level `lib/` (APK) or `base/lib/` (AAB) directory. |
| 51 | +unzip -qq -o "$apk" 'lib/*' 'base/lib/*' -d "$workdir" 2>/dev/null || true |
| 52 | + |
| 53 | +libs=() |
| 54 | +while IFS= read -r lib; do |
| 55 | + if [[ -n "$name_filter" && ! "$lib" =~ $name_filter ]]; then |
| 56 | + continue |
| 57 | + fi |
| 58 | + libs+=("$lib") |
| 59 | +done < <(find "$workdir" -type f -name '*.so' | sort) |
| 60 | +if [[ ${#libs[@]} -eq 0 ]]; then |
| 61 | + if [[ -n "$name_filter" ]]; then |
| 62 | + echo "error: no .so libraries matching /$name_filter/ found inside $apk" >&2 |
| 63 | + else |
| 64 | + echo "error: no .so libraries found inside $apk" >&2 |
| 65 | + fi |
| 66 | + exit 2 |
| 67 | +fi |
| 68 | + |
| 69 | +misaligned=() |
| 70 | +for lib in "${libs[@]}"; do |
| 71 | + rel="${lib#"$workdir"/}" |
| 72 | + # Smallest LOAD-segment alignment in this library, in bytes. `readelf` |
| 73 | + # prints the align column as a hex literal (e.g. `0x4000`); bash arithmetic |
| 74 | + # parses the `0x` prefix directly, so we avoid gawk-only `strtonum`. |
| 75 | + min_align=0 |
| 76 | + while IFS= read -r align_hex; do |
| 77 | + [[ -z "$align_hex" ]] && continue |
| 78 | + align=$((align_hex)) |
| 79 | + if [[ "$min_align" -eq 0 || "$align" -lt "$min_align" ]]; then |
| 80 | + min_align="$align" |
| 81 | + fi |
| 82 | + done < <("$readelf_bin" -lW "$lib" | awk '$1 == "LOAD" { print $NF }') |
| 83 | + |
| 84 | + if [[ "$min_align" -eq 0 ]]; then |
| 85 | + echo "warn: $rel — no LOAD segments found, skipping" >&2 |
| 86 | + continue |
| 87 | + fi |
| 88 | + |
| 89 | + if [[ "$min_align" -lt "$REQUIRED_ALIGN" ]]; then |
| 90 | + misaligned+=("$rel (align=$min_align)") |
| 91 | + printf 'FAIL: %-48s align=%d (need >= %d)\n' "$rel" "$min_align" "$REQUIRED_ALIGN" |
| 92 | + else |
| 93 | + printf 'OK: %-48s align=%d\n' "$rel" "$min_align" |
| 94 | + fi |
| 95 | +done |
| 96 | + |
| 97 | +echo |
| 98 | +if [[ ${#misaligned[@]} -gt 0 ]]; then |
| 99 | + echo "✖ ${#misaligned[@]} library/libraries are not 16 KB aligned:" >&2 |
| 100 | + for m in "${misaligned[@]}"; do echo " - $m" >&2; done |
| 101 | + echo " Pass -Wl,-z,max-page-size=16384 when linking, or build with NDK r28+." >&2 |
| 102 | + exit 1 |
| 103 | +fi |
| 104 | + |
| 105 | +echo "✔ all ${#libs[@]} libraries are >= 16 KB aligned" |
0 commit comments