Skip to content

Commit 1d709c9

Browse files
[release/7.0] Preserve last error for patchpoint helpers (#76200)
* Preserve last error for patchpoint helpers In stress modes (and eventually perhaps in normal uses) the jit may insert patchpoint helper calls in regions where last error is live. So the helpers need to preserve last error. Because some invocations of the helpers may transition to OSR methods instead of returning, we can't use the normal macros for this. Fixes #75828. * fix build issues, review feedback * add test case Co-authored-by: Andy Ayers <[email protected]>
1 parent 915dc27 commit 1d709c9

File tree

3 files changed

+149
-87
lines changed

3 files changed

+149
-87
lines changed

src/coreclr/vm/jithelpers.cpp

Lines changed: 110 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4916,6 +4916,9 @@ HCIMPLEND
49164916

49174917
void JIT_Patchpoint(int* counter, int ilOffset)
49184918
{
4919+
// BEGIN_PRESERVE_LAST_ERROR;
4920+
DWORD dwLastError = ::GetLastError();
4921+
49194922
// This method may not return normally
49204923
STATIC_CONTRACT_GC_NOTRIGGER;
49214924
STATIC_CONTRACT_MODE_COOPERATIVE;
@@ -4929,6 +4932,8 @@ void JIT_Patchpoint(int* counter, int ilOffset)
49294932
LoaderAllocator* allocator = pMD->GetLoaderAllocator();
49304933
OnStackReplacementManager* manager = allocator->GetOnStackReplacementManager();
49314934
PerPatchpointInfo * ppInfo = manager->GetPerPatchpointInfo(ip);
4935+
PCODE osrMethodCode = NULL;
4936+
bool isNewMethod = false;
49324937

49334938
// In the current prototype, counter is shared by all patchpoints
49344939
// in a method, so no matter what happens below, we don't want to
@@ -4955,12 +4960,12 @@ void JIT_Patchpoint(int* counter, int ilOffset)
49554960
{
49564961
LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: invalid patchpoint [%d] (0x%p) in Method=0x%pM (%s::%s) at offset %d\n",
49574962
ppId, ip, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset));
4958-
return;
4963+
4964+
goto DONE;
49594965
}
49604966

49614967
// See if we have an OSR method for this patchpoint.
4962-
PCODE osrMethodCode = ppInfo->m_osrMethodCode;
4963-
bool isNewMethod = false;
4968+
osrMethodCode = ppInfo->m_osrMethodCode;
49644969

49654970
if (osrMethodCode == NULL)
49664971
{
@@ -4983,7 +4988,7 @@ void JIT_Patchpoint(int* counter, int ilOffset)
49834988
{
49844989
LOG((LF_TIEREDCOMPILATION, LL_INFO10, "Jit_Patchpoint: ignoring patchpoint [%d] (0x%p) in Method=0x%pM (%s::%s) at offset %d\n",
49854990
ppId, ip, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset));
4986-
return;
4991+
goto DONE;
49874992
}
49884993
#endif
49894994

@@ -5024,15 +5029,15 @@ void JIT_Patchpoint(int* counter, int ilOffset)
50245029
// Defer, if we haven't yet reached the limit
50255030
if (hitCount < hitLimit)
50265031
{
5027-
return;
5032+
goto DONE;
50285033
}
50295034

50305035
// Third, make sure no other thread is trying to create the OSR method.
50315036
LONG oldFlags = ppInfo->m_flags;
50325037
if ((oldFlags & PerPatchpointInfo::patchpoint_triggered) == PerPatchpointInfo::patchpoint_triggered)
50335038
{
50345039
LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip));
5035-
return;
5040+
goto DONE;
50365041
}
50375042

50385043
LONG newFlags = oldFlags | PerPatchpointInfo::patchpoint_triggered;
@@ -5041,7 +5046,7 @@ void JIT_Patchpoint(int* counter, int ilOffset)
50415046
if (!triggerTransition)
50425047
{
50435048
LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: (lost race) AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip));
5044-
return;
5049+
goto DONE;
50455050
}
50465051

50475052
// Time to create the OSR method.
@@ -5071,7 +5076,7 @@ void JIT_Patchpoint(int* counter, int ilOffset)
50715076
" marking patchpoint invalid for Method=0x%pM il offset %d\n", ip, pMD, ilOffset);
50725077

50735078
InterlockedOr(&ppInfo->m_flags, (LONG)PerPatchpointInfo::patchpoint_invalid);
5074-
return;
5079+
goto DONE;
50755080
}
50765081

50775082
// We've successfully created the osr method; make it available.
@@ -5083,115 +5088,126 @@ void JIT_Patchpoint(int* counter, int ilOffset)
50835088
// If we get here, we have code to transition to...
50845089
_ASSERTE(osrMethodCode != NULL);
50855090

5086-
Thread *pThread = GetThread();
5091+
{
5092+
Thread *pThread = GetThread();
50875093

50885094
#ifdef FEATURE_HIJACK
5089-
// We can't crawl the stack of a thread that currently has a hijack pending
5090-
// (since the hijack routine won't be recognized by any code manager). So we
5091-
// Undo any hijack, the EE will re-attempt it later.
5092-
pThread->UnhijackThread();
5095+
// We can't crawl the stack of a thread that currently has a hijack pending
5096+
// (since the hijack routine won't be recognized by any code manager). So we
5097+
// Undo any hijack, the EE will re-attempt it later.
5098+
pThread->UnhijackThread();
50935099
#endif
50945100

5095-
// Find context for the original method
5096-
CONTEXT *pFrameContext = NULL;
5101+
// Find context for the original method
5102+
CONTEXT *pFrameContext = NULL;
50975103
#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
5098-
DWORD contextSize = 0;
5099-
ULONG64 xStateCompactionMask = 0;
5100-
DWORD contextFlags = CONTEXT_FULL;
5101-
if (Thread::AreCetShadowStacksEnabled())
5102-
{
5103-
xStateCompactionMask = XSTATE_MASK_CET_U;
5104-
contextFlags |= CONTEXT_XSTATE;
5105-
}
5104+
DWORD contextSize = 0;
5105+
ULONG64 xStateCompactionMask = 0;
5106+
DWORD contextFlags = CONTEXT_FULL;
5107+
if (Thread::AreCetShadowStacksEnabled())
5108+
{
5109+
xStateCompactionMask = XSTATE_MASK_CET_U;
5110+
contextFlags |= CONTEXT_XSTATE;
5111+
}
51065112

5107-
// The initialize call should fail but return contextSize
5108-
BOOL success = g_pfnInitializeContext2 ?
5109-
g_pfnInitializeContext2(NULL, contextFlags, NULL, &contextSize, xStateCompactionMask) :
5110-
InitializeContext(NULL, contextFlags, NULL, &contextSize);
5113+
// The initialize call should fail but return contextSize
5114+
BOOL success = g_pfnInitializeContext2 ?
5115+
g_pfnInitializeContext2(NULL, contextFlags, NULL, &contextSize, xStateCompactionMask) :
5116+
InitializeContext(NULL, contextFlags, NULL, &contextSize);
51115117

5112-
_ASSERTE(!success && (GetLastError() == ERROR_INSUFFICIENT_BUFFER));
5118+
_ASSERTE(!success && (GetLastError() == ERROR_INSUFFICIENT_BUFFER));
51135119

5114-
PVOID pBuffer = _alloca(contextSize);
5115-
success = g_pfnInitializeContext2 ?
5116-
g_pfnInitializeContext2(pBuffer, contextFlags, &pFrameContext, &contextSize, xStateCompactionMask) :
5117-
InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize);
5118-
_ASSERTE(success);
5120+
PVOID pBuffer = _alloca(contextSize);
5121+
success = g_pfnInitializeContext2 ?
5122+
g_pfnInitializeContext2(pBuffer, contextFlags, &pFrameContext, &contextSize, xStateCompactionMask) :
5123+
InitializeContext(pBuffer, contextFlags, &pFrameContext, &contextSize);
5124+
_ASSERTE(success);
51195125
#else // TARGET_WINDOWS && TARGET_AMD64
5120-
CONTEXT frameContext;
5121-
frameContext.ContextFlags = CONTEXT_FULL;
5122-
pFrameContext = &frameContext;
5126+
CONTEXT frameContext;
5127+
frameContext.ContextFlags = CONTEXT_FULL;
5128+
pFrameContext = &frameContext;
51235129
#endif // TARGET_WINDOWS && TARGET_AMD64
51245130

5125-
// Find context for the original method
5126-
RtlCaptureContext(pFrameContext);
5131+
// Find context for the original method
5132+
RtlCaptureContext(pFrameContext);
51275133

51285134
#if defined(TARGET_WINDOWS) && defined(TARGET_AMD64)
5129-
if (Thread::AreCetShadowStacksEnabled())
5130-
{
5131-
pFrameContext->ContextFlags |= CONTEXT_XSTATE;
5132-
SetXStateFeaturesMask(pFrameContext, xStateCompactionMask);
5133-
SetSSP(pFrameContext, _rdsspq());
5134-
}
5135+
if (Thread::AreCetShadowStacksEnabled())
5136+
{
5137+
pFrameContext->ContextFlags |= CONTEXT_XSTATE;
5138+
SetXStateFeaturesMask(pFrameContext, xStateCompactionMask);
5139+
SetSSP(pFrameContext, _rdsspq());
5140+
}
51355141
#endif // TARGET_WINDOWS && TARGET_AMD64
51365142

5137-
// Walk back to the original method frame
5138-
pThread->VirtualUnwindToFirstManagedCallFrame(pFrameContext);
5143+
// Walk back to the original method frame
5144+
pThread->VirtualUnwindToFirstManagedCallFrame(pFrameContext);
51395145

5140-
// Remember original method FP and SP because new method will inherit them.
5141-
UINT_PTR currentSP = GetSP(pFrameContext);
5142-
UINT_PTR currentFP = GetFP(pFrameContext);
5146+
// Remember original method FP and SP because new method will inherit them.
5147+
UINT_PTR currentSP = GetSP(pFrameContext);
5148+
UINT_PTR currentFP = GetFP(pFrameContext);
51435149

5144-
// We expect to be back at the right IP
5145-
if ((UINT_PTR)ip != GetIP(pFrameContext))
5146-
{
5147-
// Should be fatal
5148-
STRESS_LOG2(LF_TIEREDCOMPILATION, LL_FATALERROR, "Jit_Patchpoint: patchpoint (0x%p) TRANSITION"
5149-
" unexpected context IP 0x%p\n", ip, GetIP(pFrameContext));
5150-
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
5151-
}
5150+
// We expect to be back at the right IP
5151+
if ((UINT_PTR)ip != GetIP(pFrameContext))
5152+
{
5153+
// Should be fatal
5154+
STRESS_LOG2(LF_TIEREDCOMPILATION, LL_FATALERROR, "Jit_Patchpoint: patchpoint (0x%p) TRANSITION"
5155+
" unexpected context IP 0x%p\n", ip, GetIP(pFrameContext));
5156+
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
5157+
}
51525158

5153-
// Now unwind back to the original method caller frame.
5154-
EECodeInfo callerCodeInfo(GetIP(pFrameContext));
5155-
ULONG_PTR establisherFrame = 0;
5156-
PVOID handlerData = NULL;
5157-
RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(pFrameContext), callerCodeInfo.GetFunctionEntry(),
5158-
pFrameContext, &handlerData, &establisherFrame, NULL);
5159+
// Now unwind back to the original method caller frame.
5160+
EECodeInfo callerCodeInfo(GetIP(pFrameContext));
5161+
ULONG_PTR establisherFrame = 0;
5162+
PVOID handlerData = NULL;
5163+
RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(pFrameContext), callerCodeInfo.GetFunctionEntry(),
5164+
pFrameContext, &handlerData, &establisherFrame, NULL);
51595165

5160-
// Now, set FP and SP back to the values they had just before this helper was called,
5161-
// since the new method must have access to the original method frame.
5162-
//
5163-
// TODO: if we access the patchpointInfo here, we can read out the FP-SP delta from there and
5164-
// use that to adjust the stack, likely saving some stack space.
5166+
// Now, set FP and SP back to the values they had just before this helper was called,
5167+
// since the new method must have access to the original method frame.
5168+
//
5169+
// TODO: if we access the patchpointInfo here, we can read out the FP-SP delta from there and
5170+
// use that to adjust the stack, likely saving some stack space.
51655171

51665172
#if defined(TARGET_AMD64)
5167-
// If calls push the return address, we need to simulate that here, so the OSR
5168-
// method sees the "expected" SP misalgnment on entry.
5169-
_ASSERTE(currentSP % 16 == 0);
5170-
currentSP -= 8;
5173+
// If calls push the return address, we need to simulate that here, so the OSR
5174+
// method sees the "expected" SP misalgnment on entry.
5175+
_ASSERTE(currentSP % 16 == 0);
5176+
currentSP -= 8;
51715177

51725178
#if defined(TARGET_WINDOWS)
5173-
DWORD64 ssp = GetSSP(pFrameContext);
5174-
if (ssp != 0)
5175-
{
5176-
SetSSP(pFrameContext, ssp - 8);
5177-
}
5179+
DWORD64 ssp = GetSSP(pFrameContext);
5180+
if (ssp != 0)
5181+
{
5182+
SetSSP(pFrameContext, ssp - 8);
5183+
}
51785184
#endif // TARGET_WINDOWS
51795185

5180-
pFrameContext->Rbp = currentFP;
5186+
pFrameContext->Rbp = currentFP;
51815187
#endif // TARGET_AMD64
51825188

5183-
SetSP(pFrameContext, currentSP);
5189+
SetSP(pFrameContext, currentSP);
51845190

5185-
// Note we can get here w/o triggering, if there is an existing OSR method and
5186-
// we hit the patchpoint.
5187-
const int transitionLogLevel = isNewMethod ? LL_INFO10 : LL_INFO1000;
5188-
LOG((LF_TIEREDCOMPILATION, transitionLogLevel, "Jit_Patchpoint: patchpoint [%d] (0x%p) TRANSITION to ip 0x%p\n", ppId, ip, osrMethodCode));
5191+
// Note we can get here w/o triggering, if there is an existing OSR method and
5192+
// we hit the patchpoint.
5193+
const int transitionLogLevel = isNewMethod ? LL_INFO10 : LL_INFO1000;
5194+
LOG((LF_TIEREDCOMPILATION, transitionLogLevel, "Jit_Patchpoint: patchpoint [%d] (0x%p) TRANSITION to ip 0x%p\n", ppId, ip, osrMethodCode));
51895195

5190-
// Install new entry point as IP
5191-
SetIP(pFrameContext, osrMethodCode);
5196+
// Install new entry point as IP
5197+
SetIP(pFrameContext, osrMethodCode);
51925198

5193-
// Transition!
5194-
ClrRestoreNonvolatileContext(pFrameContext);
5199+
// Restore last error (since call below does not return)
5200+
// END_PRESERVE_LAST_ERROR;
5201+
::SetLastError(dwLastError);
5202+
5203+
// Transition!
5204+
ClrRestoreNonvolatileContext(pFrameContext);
5205+
}
5206+
5207+
DONE:
5208+
5209+
// END_PRESERVE_LAST_ERROR;
5210+
::SetLastError(dwLastError);
51955211
}
51965212

51975213
// Jit helper invoked at a partial compilation patchpoint.
@@ -5205,6 +5221,9 @@ void JIT_Patchpoint(int* counter, int ilOffset)
52055221
//
52065222
void JIT_PartialCompilationPatchpoint(int ilOffset)
52075223
{
5224+
// BEGIN_PRESERVE_LAST_ERROR;
5225+
DWORD dwLastError = ::GetLastError();
5226+
52085227
// This method will not return normally
52095228
STATIC_CONTRACT_GC_NOTRIGGER;
52105229
STATIC_CONTRACT_MODE_COOPERATIVE;
@@ -5356,6 +5375,10 @@ void JIT_PartialCompilationPatchpoint(int ilOffset)
53565375
// Install new entry point as IP
53575376
SetIP(&frameContext, osrMethodCode);
53585377

5378+
// Restore last error (since call below does not return)
5379+
// END_PRESERVE_LAST_ERROR;
5380+
::SetLastError(dwLastError);
5381+
53595382
// Transition!
53605383
RtlRestoreContext(&frameContext, NULL);
53615384
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
// Verify that the Jit_Patchpoint helper inserted for OSR preserves last error
8+
9+
class Runtime_75828
10+
{
11+
public static int Main()
12+
{
13+
Marshal.SetLastSystemError(42);
14+
15+
int expected = 5_000_000 + 42;
16+
17+
int result = 0;
18+
for (int i = 0; i < 10_000_000; i++)
19+
{
20+
result += i % 2;
21+
}
22+
23+
result += Marshal.GetLastSystemError();
24+
25+
Console.WriteLine($"got {result} expected {expected}");
26+
27+
return result == expected ? 100 : -1;
28+
}
29+
}
30+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<Optimize>True</Optimize>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="$(MSBuildProjectName).cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)