Fix ARM32 NativeAOT SIGSEGV: restore VRS helpers and __unw_*_reg outside _LIBUNWIND_NATIVEAOT guard#128830
Draft
Copilot wants to merge 3 commits into
Draft
Fix ARM32 NativeAOT SIGSEGV: restore VRS helpers and __unw_*_reg outside _LIBUNWIND_NATIVEAOT guard#128830Copilot wants to merge 3 commits into
Copilot wants to merge 3 commits into
Conversation
Contributor
|
Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib |
Split the single #if !defined(_LIBUNWIND_NATIVEAOT) guard into two blocks so that _Unwind_VRS_Get, _Unwind_VRS_Get_Internal, _Unwind_VRS_Set, _Unwind_VRS_Pop, and ValueAsBitPattern are compiled into the NativeAOT build. These helpers are called by _Unwind_VRS_Interpret which is the ARM32 EHABI managed-frame unwinding path in NativeAOT. PR #128667 accidentally included them inside the guard, causing the linker to resolve them against libgcc_s.so.1 instead of the in-tree implementations that understand the NativeAOT ArmUnwindCursor shim, resulting in SIGSEGV during ARM32 stack unwinding. The genuinely-unused C++ exception dispatch functions (__aeabi_unwind_cpp_pr*, unwind_phase1/2, _Unwind_RaiseException, _Unwind_ForcedUnwind, etc.) that depend on __unw_step and other libunwind.cpp public API remain guarded. Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
… _LIBUNWIND_NATIVEAOT guard PR #128667 accidentally excluded _Unwind_VRS_Get/Set/Pop (and their __unw_get/set_reg/__unw_save_vfp_as_X dependencies) from the NativeAOT build while leaving _Unwind_VRS_Interpret (their caller) in place. The linker resolved the exported VRS symbols from libgcc_s.so.1 which treats ArmUnwindCursor* as a real libgcc context → SIGSEGV on ARM32. Unwind-EHABI.cpp: Split single NATIVEAOT guard into two blocks so ValueAsBitPattern and _Unwind_VRS_Set/Get_Internal/Get/Pop sit between the two guards (always compiled). libunwind.cpp: Split single NATIVEAOT guard into smaller inner guards keeping __unw_get_reg, __unw_set_reg, __unw_get_fpreg, __unw_set_fpreg, __unw_save_vfp_as_X unconditional so they are compiled for NativeAOT. The singleton, __unw_init_local, __unw_step, __unw_resume, DWARF/FDE functions, and Apple dynamic sections remain behind the guard. Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Fix SIGSEGV crashes during stack unwinding on ARM32
Fix ARM32 NativeAOT SIGSEGV: restore VRS helpers and __unw_*_reg outside _LIBUNWIND_NATIVEAOT guard
Jun 1, 2026
Member
|
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
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.
PR #128667's
_LIBUNWIND_NATIVEAOTguards accidentally excluded_Unwind_VRS_Get/Set/Pop(and their__unw_get/set_reg/__unw_save_vfp_as_Xdependencies) from the NativeAOT build while leaving their caller_Unwind_VRS_Interpretenabled. The linker resolved the missing exported VRS symbols fromlibgcc_s.so.1, whose implementations treat NativeAOT'sArmUnwindCursor*shim as a real libgcc context → memory corruption → SIGSEGV on ARM32. x64/arm64 are unaffected (they use DWARF templates directly onRegisters_REGDISPLAY, never calling_Unwind_VRS_*).Changes
Unwind-EHABI.cpp: Split single_LIBUNWIND_NATIVEAOTguard into two blocks.ValueAsBitPattern,_Unwind_VRS_Set,_Unwind_VRS_Get_Internal,_Unwind_VRS_Get,_Unwind_VRS_Popnow sit between the two guards (always compiled). Exception-dispatch functions that depend on__unw_step(__aeabi_unwind_cpp_pr*,unwind_phase1/2,_Unwind_RaiseException, etc.) remain guarded.libunwind.cpp: Split the single large_LIBUNWIND_NATIVEAOTguard into three inner guards, making__unw_get_reg,__unw_set_reg,__unw_get_fpreg,__unw_set_fpreg, and__unw_save_vfp_as_Xunconditional. The singleton,__unw_init_local,__unw_step,__unw_resume, DWARF/FDE functions, and Apple dynamic sections remain guarded.llvm-libunwind-version.txt: Recorded fix commit per repo convention for vendored source patches.Original prompt
Summary
PR #128667 ("Guard unused llvm-libunwind symbols to avoid duplicates on Android") introduced a regression that causes SIGSEGV crashes during stack unwinding on ARM32 (32-bit ARM / EHABI) NativeAOT targets only. x64 and arm64 are unaffected. This reproduces on Linux arm32 (glibc), not just Android.
Root cause
On ARM32, NativeAOT unwinds managed frames through the EHABI virtual-register-set interpreter
_Unwind_VRS_Interpretinsrc/native/external/llvm-libunwind/src/Unwind-EHABI.cpp. That function was deliberately kept enabled by PR #128667 (it sits above the new#if !defined(_LIBUNWIND_NATIVEAOT)guard, which starts at line 450).However,
_Unwind_VRS_Interpretcalls the helper functions_Unwind_VRS_Get,_Unwind_VRS_Set, and_Unwind_VRS_Pop(and theValueAsBitPatternhelper they reference). Those three helpers are defined below the new guard (around lines 915, 1045, 1060), inside the#if !defined(_LIBUNWIND_NATIVEAOT)block that spans lines 450–1219. So when_LIBUNWIND_NATIVEAOTis defined, the PR accidentally removed_Unwind_VRS_Get/Set/Popfrom the NativeAOT build while leaving their caller_Unwind_VRS_Interpretin place.As a result, the linker resolves the now-missing
_Unwind_VRS_Get/Set/Popcalls against the platformlibgcc_s.so.1(_Unwind_VRS_*@GCC_3.5). This was confirmed vianm -Don a final ARM32 executable, which shows:NativeAOT's
_Unwind_VRS_Interpretpasses its ownArmUnwindCursorshim cast to_Unwind_Context*(seesrc/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp,UnwindHelpers::StepFrame, the_LIBUNWIND_ARM_EHABIbranch). The in-tree_Unwind_VRS_Get/Set/Popunderstand this shim (cast tounw_cursor_t→AbstractUnwindCursor*→ virtual dispatch intoRegisters_REGDISPLAY). The libgcc versions expect a real libgcc_Unwind_Contextand dereference it at different offsets → garbage reads/writes → SIGSEGV during unwinding.x64/arm64 never call
_Unwind_VRS_*(they use the DWARF/compact unwind templates directly onRegisters_REGDISPLAY), which is exactly why only ARM32 regressed.Required fix
Narrow the
_LIBUNWIND_NATIVEAOTguard insrc/native/external/llvm-libunwind/src/Unwind-EHABI.cppso the helpers that_Unwind_VRS_Interpretdepends on remain compiled into the NativeAOT build:_Unwind_VRS_Get(and its internal helper_Unwind_VRS_Get_Internal)_Unwind_VRS_Set_Unwind_VRS_PopValueAsBitPatternhelper they referenceThese should be outside (above/before) the
#if !defined(_LIBUNWIND_NATIVEAOT)guard, alongside_Unwind_VRS_Interpretwhich already is.The genuinely-unused C++ exception-dispatch entry points must REMAIN behind the guard (they depend on
__unw_stepand other public API fromlibunwind.cppthat is excluded for NativeAOT):__aeabi_unwind_cpp_pr0/pr1/pr2unwind_phase1,unwind_phase2,unwind_phase2_forced_Unwind_RaiseException,_Unwind_Resume,_Unwind_Complete_Unwind_GetLanguageSpecificData,_Unwind_GetRegionStart,_Unwind_ForcedUnwind,_Unwind_DeleteException__gnu_unwind_frameIn practice this means: move the
#if !defined(_LIBUNWIND_NATIVEAOT)block boundaries so that_Unwind_VRS_Set,_Unwind_VRS_Get_Internal,_Unwind_VRS_Get,_Unwind_VRS_Pop, andValueAsBitPatternare NOT guarded out, while everything currently guarded that depends on__unw_stepstays guarded. Be careful withValueAsBitPattern(it is[[gnu::unused]]and only referenced by_LIBUNWIND_TRACE_APIin the VRS helpers) — keep it available wherever the VRS helpers are so there is no unused/missing-symbol issue.This is a patch to a vendored external source. Per repo convention, also record the patch commit in
src/native/external/llvm-libunwind-version.txt(append anApply https://github.com/dotnet/runtime/commit/<sha>line as the previous patches do), consistent with how PR #128667 recorded its change.Validation
After the change, the same
nm -Don a built ARM32 executable should no longer list_Unwind_VRS_Get/Set/Popas undefined@GCC_3.5imports — they should be resolved locally. Ensure the build still compiles for all NativeAOT unix targets (x64, arm64, arm32) and that the non-NativeAOT (regular llvm-libunwind) build is unaffected by the guard changes.References
src/native/external/llvm-libunwind/src/Unwind-EHABI.cppsrc/coreclr/nativeaot/Runtime/unix/UnwindHelpers.cpp(the_LIBUNWIND_ARM_EHABIbranch ofUnwindHelpers::StepFrame)src/native/external/llvm-libunwind-version.txtThe following is the prior conversation context from the user's chat exploration (may be truncated):
User: after this PR merged, I'm seeing SIGSEGV crash...
This pull request was created from Copilot chat.