Fix Android arm64 runtime crash: SIGSEGV in Thread::LinkedList + GC stub calloc - #44
Open
zendrx wants to merge 4 commits into
Open
Fix Android arm64 runtime crash: SIGSEGV in Thread::LinkedList + GC stub calloc#44zendrx wants to merge 4 commits into
zendrx wants to merge 4 commits into
Conversation
Crystal's compiler driver adds -lgc to the linker regardless of flags. The host's x86_64 libgc.a is incompatible with aarch64-linux-android, causing dlopen to fail at runtime with GC_stackbottom unresolved. This fix: 1. Builds a minimal no-op libgc.a stub for aarch64-linux-android using the NDK toolchain, providing the GC symbols the linker expects 2. Builds PCRE2 from source for aarch64-linux-android (also required by Crystal's stdlib and missing for the cross-compile target) 3. Passes -Dwithout_gc to use gc/null.cr instead of gc/boehm.cr, avoiding Boehm GC compatibility issues with Android's bionic libc 4. Adds -L flags and -lgc -lpcre2-8 to the final link command Gradle version is left untouched as requested.
Boehm GC's GC_malloc zeroes allocated memory, which Crystal relies on for object initialization (unset pointers must be null). The previous stub used plain malloc which leaves memory uninitialized, causing random pointer dereferences and SIGSEGV at runtime when Crystal's runtime tries to use garbage pointer values. Also: remove -lgc from link flags since -Dwithout_gc already prevents the compiler driver from emitting it, and the stub is only needed for linker compatibility when the driver does emit it. Gradle version is left untouched.
crystal_android_main (the Android C→Crystal bridge) was calling Native::App.current, but @@current is never set because the user's main.cr entry point that calls App.start() doesn't run on Android — instead the framework calls crystal_android_main directly from a pthread created in native.c. This adds a registration mechanism: - App.registered_subclass / App.registered_subclass= lets the user declare which App subclass to use from their main.cr - App.start_registered starts the registered subclass The user's main.cr should now do: Native::App.registered_subclass = MyApp instead of (or guarded with) App.start(MyApp). This is backwards-compatible: desktop builds that call App.start() directly continue to work unchanged.
crystal_android_main was calling Native::App.current which raises a NilAssertionError because @@current is never set on Android (the user's main.cr entry point doesn't execute — the framework jumps directly to crystal_android_main from a pthread). The NilAssertionError then triggered a cascade crash: 1. Exception raise calls Crystal::System::Env::get 2. Which calls Crystal::once → Thread::current 3. Thread::current tries to push a Fiber to the thread's linked list 4. SIGSEGV because Crystal's thread-local storage isn't properly initialised on Android's bionic libc from a non-Crystal pthread Fix: call Native::App.start_registered which either starts the subclass the user registered via App.registered_subclass=, or falls back to an already-set @@current. This avoids the nil assertion and therefore the entire exception→Thread crash chain. Also wraps the startup in a begin/rescue with Android log output so any future runtime errors are visible in logcat instead of producing an opaque native crash.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
After fixing the
GC_stackbottomlinker error (PR #42), the app now crashes at runtime on Android arm64 with:Root Cause Analysis
This is a chain crash — three separate bugs amplify each other:
Bug 1:
Native::App.currentis nil on Androidcrystal_android_maincallsNative::App.currentwhich does@@current.not_nil!, but@@currentis never set because the user'smain.crentry point (which callsApp.start) doesn't execute on Android. Instead the framework jumps directly tocrystal_android_mainfrom apthreadcreated innative.c.Bug 2: Crystal's
Thread::currentcrashes on Android bionic libcWhen the
NilAssertionErrorfrom Bug 1 is raised, Crystal's exception handling callsCrystal::System::Env::get→Crystal::once→Thread::current.Thread::currenttries to push a Fiber to the thread's linked list, but Crystal's thread-local storage isn't properly initialised on Android's bionic libc from a non-Crystal pthread → SIGSEGV.Bug 3: GC stub uses
mallocinstead ofcallocBoehm GC's
GC_malloczeroes allocated memory, which Crystal relies on for object initialization (unset pointers must be null). The previous GC stub used plainmallocwhich leaves memory uninitialised, causing random garbage pointer dereferences.Fixes
1. App-subclass registration (
src/native/framework/app.cr)App.registered_subclass/App.registered_subclass=so users can declare their app class frommain.crApp.start_registeredwhich starts the registered subclass (used by Android bridge)App.start()directly still work2. Android bridge fix (
src/native/engine/android/bridge.cr)crystal_android_mainnow callsApp.start_registeredinstead ofApp.currentbegin/rescuewith__android_log_printso future errors go to logcat instead of producing an opaque native crash3. GC stub calloc fix (
src/native/cli/build.cr)GC_malloc,GC_malloc_atomic, andGC_malloc_uncollectablenow usecalloc(1, size)instead ofmalloc(size)to match Boehm GC's zeroing behaviourGC_realloczeroes new memory when the pointer moves4. PCRE2 cross-compile (carried from PR #42)
aarch64-linux-androidsince Crystal's stdlib regex support requires itUser-facing changes
Your
main.crshould now register the app class for Android:Gradle version is not modified.