From 65d64d8be20cd62657700c550c9cc7b8a63d82d9 Mon Sep 17 00:00:00 2001 From: Andrew Ayers Date: Fri, 24 Apr 2026 11:18:16 -0700 Subject: [PATCH 1/3] Fix SIGILL crash on ARM64 platforms with SME but no SVE (dotnet/runtime#122608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace CONTEXT_GetSveLengthFromOS() calls in signal context handling with direct reads of sve->vl from the kernel-provided signal frame. The CONTEXT_GetSveLengthFromOS function executes the SVE 'rdvl' instruction, which causes SIGILL on platforms that have SME (streaming SVE) but not standalone SVE — such as Apple M4 under macOS Virtualization.Framework with Podman/Colima. On these platforms, the Linux kernel includes an SVE_MAGIC record in signal frames (with vl=0 and minimal size) due to SME's streaming SVE mode, but the CPU does not support SVE instructions. When a signal fires (e.g. SIGUSR1 for activation injection), CONTEXTFromNativeContext sees the SVE record and calls rdvl, which triggers SIGILL. The SIGILL handler then tries to capture context again, hitting rdvl recursively. The fix uses sve->vl from the signal frame directly, which is always available when an SVE context record is present. On real SVE hardware, sve->vl equals what rdvl would return. On SME-only platforms, sve->vl is 0, so the SVE register save/restore is correctly skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/pal/src/thread/context.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index 522fe66aad1282..a60ce70e2e8675 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -896,7 +896,11 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) { //TODO-SVE: This only handles vector lengths of 128bits. - if (CONTEXT_GetSveLengthFromOS() == 16) + // Use sve->vl from the signal frame instead of CONTEXT_GetSveLengthFromOS() + // (which executes the SVE rdvl instruction) to avoid SIGILL on platforms + // that provide an SVE context record without supporting SVE instructions + // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). + if (sve->vl == 16) { _ASSERT((lpContext->XStateFeaturesMask & XSTATE_MASK_ARM64_SVE) == XSTATE_MASK_ARM64_SVE); @@ -1255,7 +1259,11 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) { //TODO-SVE: This only handles vector lengths of 128bits. - if (CONTEXT_GetSveLengthFromOS() == 16) + // Use sve->vl from the signal frame instead of CONTEXT_GetSveLengthFromOS() + // (which executes the SVE rdvl instruction) to avoid SIGILL on platforms + // that provide an SVE context record without supporting SVE instructions + // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). + if (sve->vl == 16) { _ASSERTE((sve->vl > 0) && (sve->vl % 16 == 0)); lpContext->Vl = sve->vl; From 5bf6f170615869f53cb74648181409ba6e908c4e Mon Sep 17 00:00:00 2001 From: Andrew Ayers Date: Fri, 24 Apr 2026 12:47:12 -0700 Subject: [PATCH 2/3] Address review feedback: remove redundant assert, derive vq from sve->vl - Remove the now-tautological _ASSERTE((sve->vl > 0) && (sve->vl % 16 == 0)) inside the 'if (sve->vl == 16)' block (janvorli). - In CONTEXTToNativeContext, derive vq from sve->vl (the signal frame's authoritative layout) instead of lpContext->Vl (copilot-reviewer). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/pal/src/thread/context.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index a60ce70e2e8675..f8a268ce4b47fe 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -904,7 +904,10 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) { _ASSERT((lpContext->XStateFeaturesMask & XSTATE_MASK_ARM64_SVE) == XSTATE_MASK_ARM64_SVE); - uint16_t vq = sve_vq_from_vl(lpContext->Vl); + // Derive vq from the signal frame's vl (the authoritative layout) + // rather than lpContext->Vl, to ensure offset calculations always + // match the actual frame even in non-debug builds. + uint16_t vq = sve_vq_from_vl(sve->vl); // Vector length should not have changed. _ASSERTE(lpContext->Vl == sve->vl); @@ -1265,7 +1268,6 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). if (sve->vl == 16) { - _ASSERTE((sve->vl > 0) && (sve->vl % 16 == 0)); lpContext->Vl = sve->vl; uint16_t vq = sve_vq_from_vl(sve->vl); From 3dd6e51b7740d5a93687ebcdc2fd6d44ce2a1fb0 Mon Sep 17 00:00:00 2001 From: Andrew Ayers Date: Fri, 24 Apr 2026 12:58:02 -0700 Subject: [PATCH 3/3] Address review feedback: clean up dead code and improve assertions - Remove unused CONTEXT_GetSveLengthFromOS definition (context2.S) and declaration (context.h) since all callers now use sve->vl (am11). - Reword comments to not reference the deleted function (am11). - Replace redundant assert with meaningful size check (janvorli/AndyAyersMS): _ASSERTE(sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(16))) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/pal/src/arch/arm64/context2.S | 8 -------- src/coreclr/pal/src/include/pal/context.h | 15 --------------- src/coreclr/pal/src/thread/context.cpp | 11 +++++------ 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/coreclr/pal/src/arch/arm64/context2.S b/src/coreclr/pal/src/arch/arm64/context2.S index 5cc537ab84bca9..40f46957025dc6 100644 --- a/src/coreclr/pal/src/arch/arm64/context2.S +++ b/src/coreclr/pal/src/arch/arm64/context2.S @@ -296,11 +296,3 @@ LEAF_END RestoreCompleteContext, _TEXT #endif // __APPLE__ -// Incoming: -// None -// -.arch_extension sve - LEAF_ENTRY CONTEXT_GetSveLengthFromOS, _TEXT - rdvl x0, 1 - ret lr - LEAF_END CONTEXT_GetSveLengthFromOS, _TEXT diff --git a/src/coreclr/pal/src/include/pal/context.h b/src/coreclr/pal/src/include/pal/context.h index 8658cdebb25cbc..9d5e3f01e5f59c 100644 --- a/src/coreclr/pal/src/include/pal/context.h +++ b/src/coreclr/pal/src/include/pal/context.h @@ -1622,21 +1622,6 @@ DWORD CONTEXTGetExceptionCodeForSignal(const siginfo_t *siginfo, #endif // HAVE_MACH_EXCEPTIONS else -#if defined(HOST_ARM64) -/*++ -Function : - CONTEXT_GetSveLengthFromOS - - Gets the SVE vector length -Parameters : - None -Return value : - The SVE vector length in bytes ---*/ -DWORD64 -CONTEXT_GetSveLengthFromOS( - ); -#endif // HOST_ARM64 #ifdef __cplusplus } diff --git a/src/coreclr/pal/src/thread/context.cpp b/src/coreclr/pal/src/thread/context.cpp index f8a268ce4b47fe..16964ce4e885ed 100644 --- a/src/coreclr/pal/src/thread/context.cpp +++ b/src/coreclr/pal/src/thread/context.cpp @@ -896,9 +896,8 @@ void CONTEXTToNativeContext(CONST CONTEXT *lpContext, native_context_t *native) if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) { //TODO-SVE: This only handles vector lengths of 128bits. - // Use sve->vl from the signal frame instead of CONTEXT_GetSveLengthFromOS() - // (which executes the SVE rdvl instruction) to avoid SIGILL on platforms - // that provide an SVE context record without supporting SVE instructions + // Use sve->vl from the signal frame to avoid SIGILL on platforms that + // provide an SVE context record without supporting SVE instructions // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). if (sve->vl == 16) { @@ -1262,12 +1261,12 @@ void CONTEXTFromNativeContext(const native_context_t *native, LPCONTEXT lpContex if (sve && sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(sve->vl))) { //TODO-SVE: This only handles vector lengths of 128bits. - // Use sve->vl from the signal frame instead of CONTEXT_GetSveLengthFromOS() - // (which executes the SVE rdvl instruction) to avoid SIGILL on platforms - // that provide an SVE context record without supporting SVE instructions + // Use sve->vl from the signal frame to avoid SIGILL on platforms that + // provide an SVE context record without supporting SVE instructions // (e.g. Apple M4 with SME streaming SVE under Virtualization.Framework). if (sve->vl == 16) { + _ASSERTE(sve->head.size >= SVE_SIG_CONTEXT_SIZE(sve_vq_from_vl(16))); lpContext->Vl = sve->vl; uint16_t vq = sve_vq_from_vl(sve->vl);