Skip to content

Commit ade5420

Browse files
antonisclaude
andauthored
fix(android): Force 16 KB ELF alignment for libsentry-tm-perf-logger.so (#6396)
* fix(android): Force 16 KB ELF alignment for libsentry-tm-perf-logger.so The native library added in 8.17.0 (#6307) for Turbo Module performance tracking is compiled from source with the app's NDK. On NDK r27 and earlier the linker defaults to 4 KB segment alignment, so this library was the lone misaligned `.so` in the APK (all prebuilt libs are already 16 KB aligned). This tripped Android 15+'s 16 KB page size compatibility check and Google Play's 16 KB requirement for New Architecture apps — even when `enableTurboModuleTracking` was left disabled, since the library is packaged whenever the New Architecture is enabled. Pass `-Wl,-z,max-page-size=16384` to the CMake target. Verified with NDK 27 against the RN 0.86 source tree: LOAD segment alignment goes from 0x1000 (4 KB) to 0x4000 (16 KB), with no file-size change. Backward compatible with 4 KB-page devices (16 KB is a multiple of 4 KB) and a no-op on NDK r28+ where 16 KB is already the default. Fixes #6394 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Update changelog * test(android): Add CI check for 16 KB native library alignment Guards against regressing #6394. Adds scripts/check-android-16kb-alignment.sh which unzips a built APK/AAB and asserts every bundled .so has ELF LOAD segments aligned to at least 16 KB (p_align >= 0x4000), failing otherwise. Wired into the sample-application workflow right after the New Architecture Android app build, where a real APK is produced. The script is portable across the macOS (bash 3.2) and Ubuntu CI (mawk) shells and resolves readelf from $READELF, then llvm-readelf, then readelf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: Fix grammar in changelog entry Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(android): Scope 16 KB alignment check to Sentry-owned libraries The initial CI run failed because the check scanned every .so in the APK, including React Native and third-party libraries (libreactnative, libhermesvm, libreanimated, librnscreens, ...) which are 4 KB aligned in the x86 build CI produces. RN aligns arm64 to 16 KB but not x86, and those libraries are outside this repo's control. Add an optional name-filter argument to the check script and pass 'libsentry' in CI so the guard verifies only the libraries this repo ships (libsentry-tm-perf-logger.so et al.). A regression that drops the max-page-size flag from our CMake target is still caught — verified locally: with the filter, our lib at 4 KB still fails the check. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c681cc5 commit ade5420

4 files changed

Lines changed: 134 additions & 0 deletions

File tree

.github/workflows/sample-application.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,13 @@ jobs:
187187
./scripts/set-dsn-aos.mjs
188188
./scripts/build-android.sh -PreactNativeArchitectures=x86
189189
190+
- name: Check 16 KB native library alignment
191+
# Only Sentry-owned libraries are checked: third-party/React Native
192+
# libraries are aligned by their own build (RN ships arm64 at 16 KB but
193+
# x86 at 4 KB, and CI builds x86), so a whole-APK check is not
194+
# meaningful here. Guards against regressing #6394.
195+
run: ./scripts/check-android-16kb-alignment.sh ${{ env.REACT_NATIVE_SAMPLE_PATH }}/app.apk 'libsentry'
196+
190197
- name: Archive Android App
191198
if: ${{ matrix.rn-architecture == 'new' && matrix.build-type == 'production' }}
192199
run: |

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
> make sure you follow our [migration guide](https://docs.sentry.io/platforms/react-native/migration/) first.
77
<!-- prettier-ignore-end -->
88
9+
## Unreleased
10+
11+
### Fixes
12+
13+
- Force 16 KB ELF alignment for `libsentry-tm-perf-logger.so` so it does not break 16 KB page size compatibility on Android 15+ ([#6396](https://github.com/getsentry/sentry-react-native/pull/6396))
14+
915
## 8.17.0
1016

1117
> [!WARNING]

packages/core/android/src/main/jni/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ target_link_libraries(
6262
ReactAndroid::reactnative
6363
)
6464

65+
# Force 16 KB ELF LOAD-segment alignment. The NDK linker only defaults to a
66+
# 16 KB `max-page-size` from r28 onwards; RN 0.86 / Expo SDK 57 still build
67+
# with NDK 27, whose default is 4 KB. Every other `.so` bundled by RN/AGP is
68+
# already 16 KB aligned, so without this flag `libsentry-tm-perf-logger.so`
69+
# is the lone misaligned library and trips Android 15+'s 16 KB page-size
70+
# compatibility check (and Google Play's 16 KB requirement) for the whole
71+
# app — even for hosts that never opt in to `enableTurboModuleTracking`,
72+
# since the library is packaged whenever the New Architecture is enabled.
73+
# On NDK r28+ this option is a redundant no-op.
74+
# https://github.com/getsentry/sentry-react-native/issues/6394
75+
target_link_options(
76+
sentry-tm-perf-logger
77+
PRIVATE
78+
"-Wl,-z,max-page-size=16384"
79+
)
80+
6581
# Note: we deliberately do NOT pass `-Wl,--strip-all` (or similar) here.
6682
# Android Gradle Plugin's `StripDebugSymbolsTask` already strips the .so for
6783
# the packaged APK while preserving the unstripped artefact under
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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

Comments
 (0)