Skip to content

Commit 830c6de

Browse files
authored
Fix exception propagation over HW exception frame on macOS arm64 (#63596)
* Fix exception propagation over HW exception frame on macOS arm64 There is a problem unwinding over the PAL_DispatchExceptionWrapper to the actual hardware exception location. The unwinder is unable to get distinct LR and PC in that frame and sets both of them to the same value. This is caused by the fact that the PAL_DispatchExceptionWrapper is just an injected fake frame and there was no real call. Calls always return with LR and PC set to the same value. The fix unifies the hardware exception frame unwinding with Linux where we had problems unwinding over signal handler trampoline, so PAL_VirtualUnwind skips the trampoline and now also the PAL_DispatchExceptionWrapper frame by copying the context of the exception as the unwound context. * Reenable DllImportGenerator.Unit.Tests
1 parent 6d56d48 commit 830c6de

File tree

7 files changed

+90
-30
lines changed

7 files changed

+90
-30
lines changed

src/coreclr/pal/src/exception/machexception.cpp

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -369,28 +369,22 @@ void PAL_DispatchException(PCONTEXT pContext, PEXCEPTION_RECORD pExRecord, MachE
369369
{
370370
CPalThread *pThread = InternalGetCurrentThread();
371371

372-
CONTEXT *contextRecord;
373-
EXCEPTION_RECORD *exceptionRecord;
374-
AllocateExceptionRecords(&exceptionRecord, &contextRecord);
372+
CONTEXT *contextRecord = pContext;
373+
g_hardware_exception_context_locvar_offset = (int)((char*)&contextRecord - (char*)__builtin_frame_address(0));
375374

376-
*contextRecord = *pContext;
377-
*exceptionRecord = *pExRecord;
378-
379-
contextRecord->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
375+
pContext->ContextFlags |= CONTEXT_EXCEPTION_ACTIVE;
380376
bool continueExecution;
381-
382377
{
383-
// The exception object takes ownership of the exceptionRecord and contextRecord
384-
PAL_SEHException exception(exceptionRecord, contextRecord);
378+
PAL_SEHException exception(pExRecord, pContext, true);
385379

386380
TRACE("PAL_DispatchException(EC %08x EA %p)\n", pExRecord->ExceptionCode, pExRecord->ExceptionAddress);
387381

388382
continueExecution = SEHProcessException(&exception);
389383
if (continueExecution)
390384
{
391385
// Make a copy of the exception records so that we can free them before restoring the context
392-
*pContext = *contextRecord;
393-
*pExRecord = *exceptionRecord;
386+
*pContext = *exception.ExceptionPointers.ContextRecord;
387+
*pExRecord = *exception.ExceptionPointers.ExceptionRecord;
394388
}
395389

396390
// The exception records are destroyed by the PAL_SEHException destructor now.

src/coreclr/pal/src/exception/seh-unwind.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,9 @@ void GetContextPointers(unw_cursor_t *cursor, unw_context_t *unwContext, KNONVOL
496496

497497
#ifndef HOST_WINDOWS
498498

499-
extern int g_common_signal_handler_context_locvar_offset;
499+
// Frame pointer relative offset of a local containing a pointer to the windows style context of a location
500+
// where a hardware exception occured.
501+
int g_hardware_exception_context_locvar_offset = 0;
500502

501503
BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextPointers)
502504
{
@@ -506,19 +508,17 @@ BOOL PAL_VirtualUnwind(CONTEXT *context, KNONVOLATILE_CONTEXT_POINTERS *contextP
506508

507509
DWORD64 curPc = CONTEXTGetPC(context);
508510

509-
#ifndef __APPLE__
510-
// Check if the PC is the return address from the SEHProcessException in the common_signal_handler.
511-
// If that's the case, extract its local variable containing the windows style context of the hardware
511+
// Check if the PC is the return address from the SEHProcessException.
512+
// If that's the case, extract its local variable containing a pointer to the windows style context of the hardware
512513
// exception and return that. This skips the hardware signal handler trampoline that the libunwind
513-
// cannot cross on some systems.
514+
// cannot cross on some systems. On macOS, it skips a similar trampoline we create in HijackFaultingThread.
514515
if ((void*)curPc == g_SEHProcessExceptionReturnAddress)
515516
{
516-
CONTEXT* signalContext = (CONTEXT*)(CONTEXTGetFP(context) + g_common_signal_handler_context_locvar_offset);
517-
memcpy_s(context, sizeof(CONTEXT), signalContext, sizeof(CONTEXT));
517+
CONTEXT* exceptionContext = *(CONTEXT**)(CONTEXTGetFP(context) + g_hardware_exception_context_locvar_offset);
518+
memcpy_s(context, sizeof(CONTEXT), exceptionContext, sizeof(CONTEXT));
518519

519520
return TRUE;
520521
}
521-
#endif
522522

523523
if ((context->ContextFlags & CONTEXT_EXCEPTION_ACTIVE) != 0)
524524
{

src/coreclr/pal/src/exception/signal.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,6 @@ struct sigaction g_previous_sigabrt;
116116

117117
#if !HAVE_MACH_EXCEPTIONS
118118

119-
// Offset of the local variable containing pointer to windows style context in the common_signal_handler function.
120-
// This offset is relative to the frame pointer.
121-
int g_common_signal_handler_context_locvar_offset = 0;
122-
123119
// TOP of special stack for handling stack overflow
124120
volatile void* g_stackOverflowHandlerStack = NULL;
125121

@@ -942,11 +938,12 @@ static bool common_signal_handler(int code, siginfo_t *siginfo, void *sigcontext
942938
#if !HAVE_MACH_EXCEPTIONS
943939
sigset_t signal_set;
944940
CONTEXT signalContextRecord;
941+
CONTEXT* signalContextRecordPtr = &signalContextRecord;
945942
EXCEPTION_RECORD exceptionRecord;
946943
native_context_t *ucontext;
947944

948945
ucontext = (native_context_t *)sigcontext;
949-
g_common_signal_handler_context_locvar_offset = (int)((char*)&signalContextRecord - (char*)__builtin_frame_address(0));
946+
g_hardware_exception_context_locvar_offset = (int)((char*)&signalContextRecordPtr - (char*)__builtin_frame_address(0));
950947

951948
if (code == (SIGSEGV | StackOverflowFlag))
952949
{

src/coreclr/pal/src/include/pal/seh.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,10 @@ CorUnix::PAL_ERROR SEHDisable(CorUnix::CPalThread *pthrCurrent);
145145

146146
}
147147

148+
// Offset of the local variable containing pointer to windows style context in the common_signal_handler / PAL_DispatchException function.
149+
// This offset is relative to the frame pointer.
150+
extern int g_hardware_exception_context_locvar_offset;
151+
152+
148153
#endif /* _PAL_SEH_HPP_ */
149154

src/libraries/tests.proj

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,11 +334,6 @@ Roslyn4.0.Tests.csproj" />
334334
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Security.Cryptography.OpenSsl/tests/System.Security.Cryptography.OpenSsl.Tests.csproj" />
335335
</ItemGroup>
336336

337-
<ItemGroup Condition="'$(TargetOS)' == 'OSX' and '$(TargetArchitecture)' == 'arm64' and '$(RuntimeFlavor)' != 'Mono' and '$(RunDisabledAppleSiliconTests)' != 'true'">
338-
<!-- Issue: https://github.com/dotnet/runtime/issues/63439 -->
339-
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime.InteropServices\tests\DllImportGenerator.UnitTests\DllImportGenerator.Unit.Tests.csproj" />
340-
</ItemGroup>
341-
342337
<ItemGroup Condition="'$(TargetArchitecture)' == 's390x' and '$(RunDisableds390xTests)' != 'true'">
343338
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Drawing.Common\tests\System.Drawing.Common.Tests.csproj" />
344339
</ItemGroup>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
6+
public class Program
7+
{
8+
private interface IFoo
9+
{
10+
bool IsValid { get; }
11+
}
12+
13+
private class Foo : IFoo
14+
{
15+
public bool IsValid { get; set; }
16+
}
17+
18+
public static int Main(string[] args)
19+
{
20+
bool warmup = new Foo().IsValid;
21+
CatchIgnore(() =>
22+
CatchRethrow(() =>
23+
{
24+
IFoo[] foos = {new Foo(), null};
25+
foreach (var foo in foos)
26+
{
27+
bool check = foo.IsValid;
28+
}
29+
}));
30+
31+
return 100;
32+
}
33+
34+
public static void CatchRethrow(Action action)
35+
{
36+
try
37+
{
38+
action.Invoke();
39+
}
40+
catch (Exception e)
41+
{
42+
Console.Out.WriteLine("catch");
43+
Console.Out.Flush();
44+
throw new Exception("catch", e);
45+
}
46+
}
47+
48+
public static void CatchIgnore(Action action)
49+
{
50+
try
51+
{
52+
action.Invoke();
53+
}
54+
catch (Exception)
55+
{
56+
Console.Out.WriteLine("ignore");
57+
Console.Out.Flush();
58+
}
59+
}
60+
}
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+
<CLRTestPriority>1</CLRTestPriority>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<Compile Include="test62058.cs" />
8+
</ItemGroup>
9+
</Project>

0 commit comments

Comments
 (0)