Fix ARM32 NativeAOT SIGSEGV: restore VRS helpers and __unw_*_reg outside _LIBUNWIND_NATIVEAOT guard#128830
Fix ARM32 NativeAOT SIGSEGV: restore VRS helpers and __unw_*_reg outside _LIBUNWIND_NATIVEAOT guard#128830Copilot wants to merge 3 commits into
Conversation
|
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>
|
/azp run runtime-nativeaot-outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
sbomer
left a comment
There was a problem hiding this comment.
I believe this will cause duplicate symbols when linking an arm32 android app, on the unconditional symbols that are no longer guarded. It doesn't cause problems on arm64/x64 because those symbols aren't used, and the libunwind.cpp.o from the runtime archive doesn't get extracted.
|
|
||
| #if !defined(_LIBUNWIND_NATIVEAOT) | ||
|
|
||
| #if !defined(__USING_SJLJ_EXCEPTIONS__) && !defined(__wasm__) |
There was a problem hiding this comment.
Perhaps we can omit these just for android to make both linux-arm and android-arm happy:
| #if !defined(__USING_SJLJ_EXCEPTIONS__) && !defined(__wasm__) | |
| #if defined(_LIBUNWIND_NATIVEAOT) && !defined(__ANDROID__) | |
| #if !defined(__USING_SJLJ_EXCEPTIONS__) && !defined(__wasm__) |
There was a problem hiding this comment.
Could work, but that seems a little fragile to me (I now think the ifdef approach was fragile to begin with), so trying another idea in #128927.
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.