Fix handling of NullReferenceException in VSD on ARM (#25627)
authorJan Vorlicek <janvorli@microsoft.com>
Thu, 11 Jul 2019 18:11:12 +0000 (20:11 +0200)
committerGitHub <noreply@github.com>
Thu, 11 Jul 2019 18:11:12 +0000 (20:11 +0200)
* Fix handling of NullReferenceException in VSD on ARM

There was a problem with handling NullReferenceExceptionHandling stemming
from virtual dispatch stub on ARM Linux. While the primary exception was
handled correctly, if the exception was rethrown or another exception
was thrown from the catch handler, it was reported as unhandled even
though there was a proper handler.
The issue was caused by exception unwinding that was unable to unwind past
the frame of the virtual dispatch stub in this case. Such stub is generated
at runtime and there is no unwind info for it.

The fix is to explicitly check for the stub frame and skip it during first
and second pass of exception handling.

src/vm/arm/exceparm.cpp
src/vm/excep.cpp
src/vm/excep.h
src/vm/exceptionhandling.cpp
src/vm/i386/excepx86.cpp
src/vm/stackwalk.cpp

index 9b14d41..d5f3335 100644 (file)
@@ -102,8 +102,10 @@ AdjustContextForVirtualStub(
     PCODE callsite = GetAdjustedCallAddress(GetLR(pContext)); 
 
     // Lr must already have been saved before calling so it should not be necessary to restore Lr
-
-    pExceptionRecord->ExceptionAddress = (PVOID)callsite;
+    if (pExceptionRecord != NULL)
+    {
+        pExceptionRecord->ExceptionAddress = (PVOID)callsite;
+    }
     SetIP(pContext, callsite);
 
     return TRUE;
index 9576763..e5c73d3 100644 (file)
@@ -6716,6 +6716,39 @@ EXTERN_C void JIT_WriteBarrier_Debug_End();
 EXTERN_C void FCallMemcpy_End();
 #endif
 
+#ifdef VSD_STUB_CAN_THROW_AV
+//Return TRUE if pContext->Pc is in VirtualStub
+BOOL IsIPinVirtualStub(PCODE f_IP)
+{
+    LIMITED_METHOD_CONTRACT;
+
+    Thread * pThread = GetThread();
+
+    // We may not have a managed thread object. Example is an AV on the helper thread.
+    // (perhaps during StubManager::IsStub)
+    if (pThread == NULL)
+    {
+        return FALSE;
+    }
+
+    VirtualCallStubManager::StubKind sk;
+    VirtualCallStubManager::FindStubManager(f_IP, &sk, FALSE /* usePredictStubKind */);
+
+    if (sk == VirtualCallStubManager::SK_DISPATCH)
+    {
+        return TRUE;
+    }
+    else if (sk == VirtualCallStubManager::SK_RESOLVE)
+    {
+        return TRUE;
+    }
+
+    else {
+        return FALSE;
+    }
+}
+#endif // VSD_STUB_CAN_THROW_AV
+
 // Check if the passed in instruction pointer is in one of the
 // JIT helper functions.
 bool IsIPInMarkedJitHelper(UINT_PTR uControlPc)
index d659b7c..b672bca 100644 (file)
@@ -22,7 +22,14 @@ class Thread;
 #include <excepcpu.h>
 #include "interoputil.h"
 
+#if defined(_TARGET_ARM_) || defined(_TARGET_X86_)
+#define VSD_STUB_CAN_THROW_AV
+#endif // _TARGET_ARM_ || _TARGET_X86_
+
 BOOL IsExceptionFromManagedCode(const EXCEPTION_RECORD * pExceptionRecord);
+#ifdef VSD_STUB_CAN_THROW_AV
+BOOL IsIPinVirtualStub(PCODE f_IP);
+#endif // VSD_STUB_CAN_THROW_AV
 bool IsIPInMarkedJitHelper(UINT_PTR uControlPc);
 
 #if defined(FEATURE_HIJACK) && (!defined(_TARGET_X86_) || defined(FEATURE_PAL))
index 4de884f..ba50f71 100644 (file)
 #define USE_CURRENT_CONTEXT_IN_FILTER
 #endif // _TARGET_X86_
 
-#if defined(_TARGET_ARM_) || defined(_TARGET_X86_)
-#define VSD_STUB_CAN_THROW_AV
-#endif // _TARGET_ARM_ || _TARGET_X86_
-
 #if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
 // ARM/ARM64 uses Caller-SP to locate PSPSym in the funclet frame.
 #define USE_CALLER_SP_IN_FUNCLET
@@ -4661,6 +4657,15 @@ VOID DECLSPEC_NORETURN UnwindManagedExceptionPass1(PAL_SEHException& ex, CONTEXT
         // Check whether we are crossing managed-to-native boundary
         while (!ExecutionManager::IsManagedCode(controlPc))
         {
+#ifdef VSD_STUB_CAN_THROW_AV
+            if (IsIPinVirtualStub(controlPc))
+            {
+                AdjustContextForVirtualStub(NULL, frameContext);
+                controlPc = GetIP(frameContext);
+                break;
+            }
+#endif // VSD_STUB_CAN_THROW_AV
+
             UINT_PTR sp = GetSP(frameContext);
 
             BOOL success = PAL_VirtualUnwind(frameContext, NULL);
@@ -5144,39 +5149,6 @@ BOOL IsSafeToCallExecutionManager()
            GCStress<cfg_instr_ngen>::IsEnabled();
 }
 
-#ifdef VSD_STUB_CAN_THROW_AV
-//Return TRUE if pContext->Pc is in VirtualStub
-static BOOL IsIPinVirtualStub(PCODE f_IP)
-{
-    LIMITED_METHOD_CONTRACT;
-
-    Thread * pThread = GetThread();
-
-    // We may not have a managed thread object. Example is an AV on the helper thread.
-    // (perhaps during StubManager::IsStub)
-    if (pThread == NULL)
-    {
-        return FALSE;
-    }
-
-    VirtualCallStubManager::StubKind sk;
-    VirtualCallStubManager::FindStubManager(f_IP, &sk, FALSE /* usePredictStubKind */);
-
-    if (sk == VirtualCallStubManager::SK_DISPATCH)
-    {
-        return TRUE;
-    }
-    else if (sk == VirtualCallStubManager::SK_RESOLVE)
-    {
-        return TRUE;
-    }
-
-    else {
-        return FALSE;
-    }
-}
-#endif // VSD_STUB_CAN_THROW_AV
-
 BOOL IsSafeToHandleHardwareException(PCONTEXT contextRecord, PEXCEPTION_RECORD exceptionRecord)
 {
     PCODE controlPc = GetIP(contextRecord);
index 3519173..9f344db 100644 (file)
@@ -3662,7 +3662,10 @@ AdjustContextForVirtualStub(
     }
 
     PCODE callsite = GetAdjustedCallAddress(*dac_cast<PTR_PCODE>(GetSP(pContext)));
-    pExceptionRecord->ExceptionAddress = (PVOID)callsite;
+    if (pExceptionRecord != NULL)
+    {
+        pExceptionRecord->ExceptionAddress = (PVOID)callsite;
+    }
     SetIP(pContext, callsite);
 
 #if defined(GCCOVER_TOLERATE_SPURIOUS_AV)
index 0eb0952..f0ad195 100644 (file)
@@ -769,6 +769,16 @@ UINT_PTR Thread::VirtualUnwindToFirstManagedCallFrame(T_CONTEXT* pContext)
 #ifndef FEATURE_PAL
         uControlPc = VirtualUnwindCallFrame(pContext);
 #else // !FEATURE_PAL
+
+#ifdef VSD_STUB_CAN_THROW_AV
+        if (IsIPinVirtualStub(uControlPc))
+        {
+            AdjustContextForVirtualStub(NULL, pContext);
+            uControlPc = GetIP(pContext);
+            break;
+        }
+#endif // VSD_STUB_CAN_THROW_AV
+
         BOOL success = PAL_VirtualUnwind(pContext, NULL);
         if (!success)
         {