Skip to content

Commit

Permalink
Make FailFast a QCALL
Browse files Browse the repository at this point in the history
  • Loading branch information
AustinWise committed Feb 26, 2024
1 parent 664c8f9 commit aed8fd4
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;

namespace System
Expand Down Expand Up @@ -33,12 +34,20 @@ public static extern int ExitCode
set;
}

// Note: The CLR's Watson bucketization code looks at the caller of the FCALL method
// Note: The CLR's Watson bucketization code looks at the caller of the QCALL method
// to assign blame for crashes. Don't mess with this, such as by making it call
// another managed helper method, unless you consult with some CLR Watson experts.
[DoesNotReturn]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void FailFast(string? message);
[DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
public static void FailFast(string? message)
{
StackCrawlMark mark = StackCrawlMark.LookForMyCaller;
FailFast(new StackCrawlMarkHandle(ref mark), new StringHandleOnStack(ref message));
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_FailFast")]
[DoesNotReturn]
private static partial void FailFast(StackCrawlMarkHandle mark, StringHandleOnStack message);

// This overload of FailFast will allow you to specify the exception object
// whose bucket details *could* be used when undergoing the failfast process.
Expand All @@ -54,12 +63,28 @@ public static extern int ExitCode
// IP for bucketing. If the exception object is not preallocated, it will use the bucket
// details contained in the object (if any).
[DoesNotReturn]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void FailFast(string? message, Exception? exception);
[DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
public static void FailFast(string? message, Exception? exception)
{
StackCrawlMark mark = StackCrawlMark.LookForMyCaller;
FailFast(new StackCrawlMarkHandle(ref mark), new StringHandleOnStack(ref message), ObjectHandleOnStack.Create(ref exception));
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_FailFastWithException")]
[DoesNotReturn]
private static partial void FailFast(StackCrawlMarkHandle mark, StringHandleOnStack message, ObjectHandleOnStack exception);

[DoesNotReturn]
[DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
internal static void FailFast(string? message, Exception? exception, string? errorMessage)
{
StackCrawlMark mark = StackCrawlMark.LookForMyCaller;
FailFast(new StackCrawlMarkHandle(ref mark), new StringHandleOnStack(ref message), ObjectHandleOnStack.Create(ref exception), new StringHandleOnStack(ref errorMessage));
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Environment_FailFastWithExceptionAndSource")]
[DoesNotReturn]
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void FailFast(string? message, Exception? exception, string? errorMessage);
private static partial void FailFast(StackCrawlMarkHandle mark, StringHandleOnStack message, ObjectHandleOnStack exception, StringHandleOnStack errorMessage);

private static unsafe string[] InitializeCommandLineArgs(char* exePath, int argc, char** argv) // invoked from VM
{
Expand Down
122 changes: 88 additions & 34 deletions src/coreclr/classlibnative/bcltype/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,38 @@ extern "C" INT32 QCALLTYPE Environment_GetProcessorCount()
return processorCount;
}

struct FindFailFastCallerStruct {
StackCrawlMark* pStackMark;
UINT_PTR retAddress;
};

// This method is called by the GetMethod function and will crawl backward
// up the stack for integer methods.
static StackWalkAction FindFailFastCallerCallback(CrawlFrame* frame, VOID* data) {
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;

FindFailFastCallerStruct* pFindCaller = (FindFailFastCallerStruct*) data;

// The check here is between the address of a local variable
// (the stack mark) and a pointer to the EIP for a frame
// (which is actually the pointer to the return address to the
// function from the previous frame). So we'll actually notice
// which frame the stack mark was in one frame later. This is
// fine since we only implement LookForMyCaller.
_ASSERTE(*pFindCaller->pStackMark == LookForMyCaller);
if (!frame->IsInCalleesFrames(pFindCaller->pStackMark))
return SWA_CONTINUE;

pFindCaller->retAddress = GetControlPC(frame->GetRegisterSet());
return SWA_ABORT;
}

// FailFast is supported in BCL.small as internal to support failing fast in places where EEE used to be thrown.
//
// Static message buffer used by SystemNative::FailFast to avoid reliance on a
Expand All @@ -143,9 +175,10 @@ WCHAR *g_pFailFastBuffer = g_szFailFastBuffer;

#define FAIL_FAST_STATIC_BUFFER_LENGTH (sizeof(g_szFailFastBuffer) / sizeof(WCHAR))

// This is the common code for FailFast processing that is wrapped by the two

// This is the common code for FailFast processing that is wrapped by the
// FailFast FCalls below.
void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, UINT_PTR retAddress, STRINGREF refErrorSourceString)
void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, StackCrawlMark* stackMark, STRINGREF refErrorSourceString)
{
CONTRACTL
{
Expand All @@ -166,6 +199,11 @@ void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExce

GCPROTECT_BEGIN(gc);

FindFailFastCallerStruct findCallerData;
findCallerData.pStackMark = stackMark;
findCallerData.retAddress = 0;
StackWalkFunctions(GetThread(), FindFailFastCallerCallback, &findCallerData);

// Managed code injected FailFast maps onto the unmanaged version
// (EEPolicy::HandleFatalError) in the following manner: the exit code is
// always set to COR_E_FAILFAST and the address passed (usually a failing
Expand Down Expand Up @@ -267,7 +305,7 @@ void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExce
{
PTR_EHWatsonBucketTracker pUEWatsonBucketTracker = pThread->GetExceptionState()->GetUEWatsonBucketTracker();
_ASSERTE(pUEWatsonBucketTracker != NULL);
pUEWatsonBucketTracker->SaveIpForWatsonBucket(retAddress);
pUEWatsonBucketTracker->SaveIpForWatsonBucket(findCallerData.retAddress);
pUEWatsonBucketTracker->CaptureUnhandledInfoForWatson(TypeOfReportedError::FatalError, pThread, NULL);
if (pUEWatsonBucketTracker->RetrieveWatsonBuckets() == NULL)
{
Expand All @@ -282,69 +320,85 @@ void SystemNative::GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExce
if (gc.refExceptionForWatsonBucketing != NULL)
pThread->SetLastThrownObject(gc.refExceptionForWatsonBucketing);

EEPolicy::HandleFatalError(COR_E_FAILFAST, retAddress, pszMessage, NULL, errorSourceString, argExceptionString);
EEPolicy::HandleFatalError(COR_E_FAILFAST, findCallerData.retAddress, pszMessage, NULL, errorSourceString, argExceptionString);

GCPROTECT_END();
}

// Note: Do not merge this FCALL method with any other FailFast overloads.
// Watson uses the managed FailFast method with one String for crash dump bucketization.
FCIMPL1(VOID, SystemNative::FailFast, StringObject* refMessageUNSAFE)
extern "C" void QCALLTYPE Environment_FailFast(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message)
{
FCALL_CONTRACT;
QCALL_CONTRACT;

STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
BEGIN_QCALL;

HELPER_METHOD_FRAME_BEGIN_1(refMessage);
GCX_COOP();

// The HelperMethodFrame knows how to get the return address.
UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
STRINGREF refMessage = message.Get();
GCPROTECT_BEGIN(refMessage);

// Call the actual worker to perform failfast
GenericFailFast(refMessage, NULL, retaddr, NULL);
SystemNative::GenericFailFast(refMessage, NULL, mark, NULL);

HELPER_METHOD_FRAME_END();
GCPROTECT_END();

END_QCALL;
}
FCIMPLEND

FCIMPL2(VOID, SystemNative::FailFastWithException, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE)
extern "C" void QCALLTYPE Environment_FailFastWithException(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message, QCall::ObjectHandleOnStack exception)
{
FCALL_CONTRACT;
QCALL_CONTRACT;

STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
EXCEPTIONREF refException = (EXCEPTIONREF)refExceptionUNSAFE;
BEGIN_QCALL;

HELPER_METHOD_FRAME_BEGIN_2(refMessage, refException);
GCX_COOP();

struct
{
STRINGREF refMessage;
EXCEPTIONREF refException;
} gc;
gc.refMessage = message.Get();
gc.refException = (EXCEPTIONREF)exception.Get();

// The HelperMethodFrame knows how to get the return address.
UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
GCPROTECT_BEGIN(gc);

// Call the actual worker to perform failfast
GenericFailFast(refMessage, refException, retaddr, NULL);
SystemNative::GenericFailFast(gc.refMessage, gc.refException, mark, NULL);

HELPER_METHOD_FRAME_END();
GCPROTECT_END();

END_QCALL;
}
FCIMPLEND

FCIMPL3(VOID, SystemNative::FailFastWithExceptionAndSource, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE, StringObject* errorSourceUNSAFE)
extern "C" void QCALLTYPE Environment_FailFastWithExceptionAndSource(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message, QCall::ObjectHandleOnStack exception, QCall::StringHandleOnStack errorSource)
{
FCALL_CONTRACT;
QCALL_CONTRACT;

STRINGREF refMessage = (STRINGREF)refMessageUNSAFE;
EXCEPTIONREF refException = (EXCEPTIONREF)refExceptionUNSAFE;
STRINGREF errorSource = (STRINGREF)errorSourceUNSAFE;
BEGIN_QCALL;

HELPER_METHOD_FRAME_BEGIN_3(refMessage, refException, errorSource);
GCX_COOP();

struct
{
STRINGREF refMessage;
EXCEPTIONREF refException;
STRINGREF refErrorSource;
} gc;
gc.refMessage = message.Get();
gc.refException = (EXCEPTIONREF)exception.Get();
gc.refErrorSource = errorSource.Get();

// The HelperMethodFrame knows how to get the return address.
UINT_PTR retaddr = HELPER_METHOD_FRAME_GET_RETURN_ADDRESS();
GCPROTECT_BEGIN(gc);

// Call the actual worker to perform failfast
GenericFailFast(refMessage, refException, retaddr, errorSource);
SystemNative::GenericFailFast(gc.refMessage, gc.refException, mark, gc.refErrorSource);

HELPER_METHOD_FRAME_END();
GCPROTECT_END();

END_QCALL;
}
FCIMPLEND

FCIMPL0(FC_BOOL_RET, SystemNative::IsServerGC)
{
Expand Down
15 changes: 8 additions & 7 deletions src/coreclr/classlibnative/bcltype/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,23 @@ class SystemNative
static FCDECL1(VOID,SetExitCode,INT32 exitcode);
static FCDECL0(INT32, GetExitCode);

static FCDECL1(VOID, FailFast, StringObject* refMessageUNSAFE);
static FCDECL2(VOID, FailFastWithException, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE);
static FCDECL3(VOID, FailFastWithExceptionAndSource, StringObject* refMessageUNSAFE, ExceptionObject* refExceptionUNSAFE, StringObject* errorSourceUNSAFE);

static FCDECL0(FC_BOOL_RET, IsServerGC);

// Return a method info for the method were the exception was thrown
static FCDECL1(ReflectMethodObject*, GetMethodFromStackTrace, ArrayBase* pStackTraceUNSAFE);

private:

// Common processing code for FailFast
static void GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, UINT_PTR retAddress, STRINGREF errorSource);
static void GenericFailFast(STRINGREF refMesgString, EXCEPTIONREF refExceptionForWatsonBucketing, StackCrawlMark* stackCrawlMark, STRINGREF errorSource);
};

extern "C" void QCALLTYPE Environment_Exit(INT32 exitcode);

extern "C" void QCALLTYPE Environment_FailFast(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message);

extern "C" void QCALLTYPE Environment_FailFastWithException(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message, QCall::ObjectHandleOnStack exception);

extern "C" void QCALLTYPE Environment_FailFastWithExceptionAndSource(QCall::StackCrawlMarkHandle mark, QCall::StringHandleOnStack message, QCall::ObjectHandleOnStack exception, QCall::StringHandleOnStack errorSource);

// Returns the number of logical processors that can be used by managed code
extern "C" INT32 QCALLTYPE Environment_GetProcessorCount();

Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@ FCFuncStart(gEnvironmentFuncs)
FCFuncElement("get_TickCount64", SystemNative::GetTickCount64)
FCFuncElement("set_ExitCode", SystemNative::SetExitCode)
FCFuncElement("get_ExitCode", SystemNative::GetExitCode)

FCFuncElementSig("FailFast", &gsig_SM_Str_RetVoid, SystemNative::FailFast)
FCFuncElementSig("FailFast", &gsig_SM_Str_Exception_RetVoid, SystemNative::FailFastWithException)
FCFuncElementSig("FailFast", &gsig_SM_Str_Exception_Str_RetVoid, SystemNative::FailFastWithExceptionAndSource)
FCFuncEnd()

FCFuncStart(gExceptionFuncs)
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ static const Entry s_QCall[] =
DllImportEntry(Delegate_FindMethodHandle)
DllImportEntry(Delegate_InternalEqualMethodHandles)
DllImportEntry(Environment_Exit)
DllImportEntry(Environment_FailFast)
DllImportEntry(Environment_FailFastWithException)
DllImportEntry(Environment_FailFastWithExceptionAndSource)
DllImportEntry(Environment_GetProcessorCount)
DllImportEntry(ExceptionNative_GetMessageFromNativeResources)
DllImportEntry(RuntimeTypeHandle_CreateInstanceForAnotherGenericParameter)
Expand Down

0 comments on commit aed8fd4

Please sign in to comment.