This repository was archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Expand file tree
/
Copy pathexcepx86.cpp
More file actions
3707 lines (3057 loc) · 154 KB
/
excepx86.cpp
File metadata and controls
3707 lines (3057 loc) · 154 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
/* EXCEP.CPP:
*
*/
#include "common.h"
#include "frames.h"
#include "excep.h"
#include "object.h"
#include "field.h"
#include "dbginterface.h"
#include "cgensys.h"
#include "comutilnative.h"
#include "sigformat.h"
#include "siginfo.hpp"
#include "gcheaputilities.h"
#include "eedbginterfaceimpl.h" //so we can clearexception in COMPlusThrow
#include "perfcounters.h"
#include "eventtrace.h"
#include "eetoprofinterfacewrapper.inl"
#include "eedbginterfaceimpl.inl"
#include "dllimportcallback.h"
#include "threads.h"
#include "eeconfig.h"
#include "vars.hpp"
#include "generics.h"
#include "asmconstants.h"
#include "virtualcallstub.h"
#ifndef WIN64EXCEPTIONS
MethodDesc * GetUserMethodForILStub(Thread * pThread, UINT_PTR uStubSP, MethodDesc * pILStubMD, Frame ** ppFrameOut);
#if !defined(DACCESS_COMPILE)
#define FORMAT_MESSAGE_BUFFER_LENGTH 1024
BOOL ComPlusFrameSEH(EXCEPTION_REGISTRATION_RECORD*);
PEXCEPTION_REGISTRATION_RECORD GetPrevSEHRecord(EXCEPTION_REGISTRATION_RECORD*);
extern "C" {
// in asmhelpers.asm:
VOID STDCALL ResumeAtJitEHHelper(EHContext *pContext);
int STDCALL CallJitEHFilterHelper(size_t *pShadowSP, EHContext *pContext);
VOID STDCALL CallJitEHFinallyHelper(size_t *pShadowSP, EHContext *pContext);
typedef void (*RtlUnwindCallbackType)(void);
BOOL CallRtlUnwind(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
RtlUnwindCallbackType callback,
EXCEPTION_RECORD *pExceptionRecord,
void *retval);
BOOL CallRtlUnwindSafe(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
RtlUnwindCallbackType callback,
EXCEPTION_RECORD *pExceptionRecord,
void *retval);
}
static inline BOOL
CPFH_ShouldUnwindStack(const EXCEPTION_RECORD * pCER) {
LIMITED_METHOD_CONTRACT;
_ASSERTE(pCER != NULL);
// We can only unwind those exceptions whose context/record we don't need for a
// rethrow. This is complus, and stack overflow. For all the others, we
// need to keep the context around for a rethrow, which means they can't
// be unwound.
if (IsComPlusException(pCER) || pCER->ExceptionCode == STATUS_STACK_OVERFLOW)
return TRUE;
else
return FALSE;
}
static inline BOOL IsComPlusNestedExceptionRecord(EXCEPTION_REGISTRATION_RECORD* pEHR)
{
LIMITED_METHOD_CONTRACT;
if (pEHR->Handler == (PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler)
return TRUE;
return FALSE;
}
EXCEPTION_REGISTRATION_RECORD *TryFindNestedEstablisherFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
{
LIMITED_METHOD_CONTRACT;
while (pEstablisherFrame->Handler != (PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler) {
pEstablisherFrame = pEstablisherFrame->Next;
if (pEstablisherFrame == EXCEPTION_CHAIN_END) return 0;
}
return pEstablisherFrame;
}
#ifdef _DEBUG
// stores last handler we went to in case we didn't get an endcatch and stack is
// corrupted we can figure out who did it.
static MethodDesc *gLastResumedExceptionFunc = NULL;
static DWORD gLastResumedExceptionHandler = 0;
#endif
//---------------------------------------------------------------------
// void RtlUnwindCallback()
// call back function after global unwind, rtlunwind calls this function
//---------------------------------------------------------------------
static void RtlUnwindCallback()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(!"Should never get here");
}
BOOL NExportSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
{
LIMITED_METHOD_CONTRACT;
if ((LPVOID)pEHR->Handler == (LPVOID)UMThunkPrestubHandler)
{
return TRUE;
}
return FALSE;
}
BOOL FastNExportSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
{
LIMITED_METHOD_CONTRACT;
if ((LPVOID)pEHR->Handler == (LPVOID)FastNExportExceptHandler)
return TRUE;
return FALSE;
}
BOOL ReverseCOMSEH(EXCEPTION_REGISTRATION_RECORD* pEHR)
{
LIMITED_METHOD_CONTRACT;
#ifdef FEATURE_COMINTEROP
if ((LPVOID)pEHR->Handler == (LPVOID)COMPlusFrameHandlerRevCom)
return TRUE;
#endif // FEATURE_COMINTEROP
return FALSE;
}
//
// Returns true if the given SEH handler is one of our SEH handlers that is responsible for managing exceptions in
// regions of managed code.
//
BOOL IsUnmanagedToManagedSEHHandler(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
{
WRAPPER_NO_CONTRACT;
//
// ComPlusFrameSEH() is for COMPlusFrameHandler & COMPlusNestedExceptionHandler.
// FastNExportSEH() is for FastNExportExceptHandler.
// NExportSEH() is for UMThunkPrestubHandler.
//
return (ComPlusFrameSEH(pEstablisherFrame) || FastNExportSEH(pEstablisherFrame) || NExportSEH(pEstablisherFrame) || ReverseCOMSEH(pEstablisherFrame));
}
Frame *GetCurrFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame)
{
Frame *pFrame;
WRAPPER_NO_CONTRACT;
_ASSERTE(IsUnmanagedToManagedSEHHandler(pEstablisherFrame));
if (NExportSEH(pEstablisherFrame))
pFrame = ((ComToManagedExRecord *)pEstablisherFrame)->GetCurrFrame();
else
pFrame = ((FrameHandlerExRecord *)pEstablisherFrame)->GetCurrFrame();
_ASSERTE(GetThread() == NULL || GetThread()->GetFrame() <= pFrame);
return pFrame;
}
EXCEPTION_REGISTRATION_RECORD* GetNextCOMPlusSEHRecord(EXCEPTION_REGISTRATION_RECORD* pRec) {
WRAPPER_NO_CONTRACT;
if (pRec == EXCEPTION_CHAIN_END)
return EXCEPTION_CHAIN_END;
do {
_ASSERTE(pRec != 0);
pRec = pRec->Next;
} while (pRec != EXCEPTION_CHAIN_END && !IsUnmanagedToManagedSEHHandler(pRec));
_ASSERTE(pRec == EXCEPTION_CHAIN_END || IsUnmanagedToManagedSEHHandler(pRec));
return pRec;
}
/*
* GetClrSEHRecordServicingStackPointer
*
* This function searchs all the Frame SEH records, and finds the one that is
* currently signed up to do all exception handling for the given stack pointer
* on the given thread.
*
* Parameters:
* pThread - The thread to search on.
* pStackPointer - The stack location that we are finding the Frame SEH Record for.
*
* Returns
* A pointer to the SEH record, or EXCEPTION_CHAIN_END if none was found.
*
*/
PEXCEPTION_REGISTRATION_RECORD
GetClrSEHRecordServicingStackPointer(Thread *pThread,
void *pStackPointer)
{
ThreadExceptionState* pExState = pThread->GetExceptionState();
//
// We can only do this if there is a context in the pExInfo. There are cases (most notably the
// EEPolicy::HandleFatalError case) where we don't have that. In these cases we will return
// no enclosing handler since we cannot accurately determine the FS:0 entry which services
// this stack address.
//
// The side effect of this is that for these cases, the debugger cannot intercept
// the exception
//
CONTEXT* pContextRecord = pExState->GetContextRecord();
if (pContextRecord == NULL)
{
return EXCEPTION_CHAIN_END;
}
void *exceptionSP = dac_cast<PTR_VOID>(GetSP(pContextRecord));
//
// Now set the establishing frame. What this means in English is that we need to find
// the fs:0 entry that handles exceptions for the place on the stack given in stackPointer.
//
PEXCEPTION_REGISTRATION_RECORD pSEHRecord = GetFirstCOMPlusSEHRecord(pThread);
while (pSEHRecord != EXCEPTION_CHAIN_END)
{
//
// Skip any SEHRecord which is not a CLR record or was pushed after the exception
// on this thread occurred.
//
if (IsUnmanagedToManagedSEHHandler(pSEHRecord) && (exceptionSP <= (void *)pSEHRecord))
{
Frame *pFrame = GetCurrFrame(pSEHRecord);
//
// Arcane knowledge here. All Frame records are stored on the stack by the runtime
// in ever decreasing address space. So, we merely have to search back until
// we find the first frame record with a higher stack value to find the
// establishing frame for the given stack address.
//
if (((void *)pFrame) >= pStackPointer)
{
break;
}
}
pSEHRecord = GetNextCOMPlusSEHRecord(pSEHRecord);
}
return pSEHRecord;
}
#ifdef _DEBUG
// We've deteremined during a stack walk that managed code is transitioning to unamanaged (EE) code. Check that the
// state of the EH chain is correct.
//
// For x86, check that we do INSTALL_COMPLUS_EXCEPTION_HANDLER before calling managed code. This check should be
// done for all managed code sites, not just transistions. But this will catch most problem cases.
void VerifyValidTransitionFromManagedCode(Thread *pThread, CrawlFrame *pCF)
{
WRAPPER_NO_CONTRACT;
_ASSERTE(ExecutionManager::IsManagedCode(GetControlPC(pCF->GetRegisterSet())));
// Cannot get to the TEB of other threads. So ignore them.
if (pThread != GetThread())
{
return;
}
// Find the EH record guarding the current region of managed code, based on the CrawlFrame passed in.
PEXCEPTION_REGISTRATION_RECORD pEHR = GetCurrentSEHRecord();
while ((pEHR != EXCEPTION_CHAIN_END) && ((ULONG_PTR)pEHR < GetRegdisplaySP(pCF->GetRegisterSet())))
{
pEHR = pEHR->Next;
}
// VerifyValidTransitionFromManagedCode can be called before the CrawlFrame's MethodDesc is initialized.
// Fix that if necessary for the consistency check.
MethodDesc * pFunction = pCF->GetFunction();
if ((!IsUnmanagedToManagedSEHHandler(pEHR)) && // Will the assert fire? If not, don't waste our time.
(pFunction == NULL))
{
_ASSERTE(pCF->GetRegisterSet());
PCODE ip = GetControlPC(pCF->GetRegisterSet());
pFunction = ExecutionManager::GetCodeMethodDesc(ip);
_ASSERTE(pFunction);
}
// Great, we've got the EH record that's next up the stack from the current SP (which is in managed code). That
// had better be a record for one of our handlers responsible for handling exceptions in managed code. If its
// not, then someone made it into managed code without setting up one of our EH handlers, and that's really
// bad.
CONSISTENCY_CHECK_MSGF(IsUnmanagedToManagedSEHHandler(pEHR),
("Invalid transition into managed code!\n\n"
"We're walking this thread's stack and we've reached a managed frame at Esp=0x%p. "
"(The method is %s::%s) "
"The very next FS:0 record (0x%p) up from this point on the stack should be one of "
"our 'unmanaged to managed SEH handlers', but its not... its something else, and "
"that's very bad. It indicates that someone managed to call into managed code without "
"setting up the proper exception handling.\n\n"
"Get a good unmanaged stack trace for this thread. All FS:0 records are on the stack, "
"so you can see who installed the last handler. Somewhere between that function and "
"where the thread is now is where the bad transition occurred.\n\n"
"A little extra info: FS:0 = 0x%p, pEHR->Handler = 0x%p\n",
GetRegdisplaySP(pCF->GetRegisterSet()),
pFunction ->m_pszDebugClassName,
pFunction ->m_pszDebugMethodName,
pEHR,
GetCurrentSEHRecord(),
pEHR->Handler));
}
#endif
//================================================================================
// There are some things that should never be true when handling an
// exception. This function checks for them. Will assert or trap
// if it finds an error.
static inline void
CPFH_VerifyThreadIsInValidState(Thread* pThread, DWORD exceptionCode, EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame) {
WRAPPER_NO_CONTRACT;
if ( exceptionCode == STATUS_BREAKPOINT
|| exceptionCode == STATUS_SINGLE_STEP) {
return;
}
#ifdef _DEBUG
// check for overwriting of stack
CheckStackBarrier(pEstablisherFrame);
// trigger check for bad fs:0 chain
GetCurrentSEHRecord();
#endif
if (!g_fEEShutDown) {
// An exception on the GC thread, or while holding the thread store lock, will likely lock out the entire process.
if (::IsGCThread() || ThreadStore::HoldingThreadStore())
{
_ASSERTE(!"Exception during garbage collection or while holding thread store");
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE);
}
}
}
#ifdef FEATURE_HIJACK
void
CPFH_AdjustContextForThreadSuspensionRace(CONTEXT *pContext, Thread *pThread)
{
WRAPPER_NO_CONTRACT;
PCODE f_IP = GetIP(pContext);
if (Thread::IsAddrOfRedirectFunc((PVOID)f_IP)) {
// This is a very rare case where we tried to redirect a thread that was
// just about to dispatch an exception, and our update of EIP took, but
// the thread continued dispatching the exception.
//
// If this should happen (very rare) then we fix it up here.
//
_ASSERTE(pThread->GetSavedRedirectContext());
SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()));
STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 1 setting IP = %x\n", pContext->Eip);
}
if (f_IP == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) {
// This is a very rare case where we tried to redirect a thread that was
// just about to dispatch an exception, and our update of EIP took, but
// the thread continued dispatching the exception.
//
// If this should happen (very rare) then we fix it up here.
//
SetIP(pContext, GetIP(pThread->m_OSContext));
STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 2 setting IP = %x\n", pContext->Eip);
}
// We have another even rarer race condition:
// - A) On thread A, Debugger puts an int 3 in the code stream at address X
// - A) We hit it and the begin an exception. The eip will be X + 1 (int3 is special)
// - B) Meanwhile, thread B redirects A's eip to Y. (Although A is really somewhere
// in the kernel, it looks like it's still in user code, so it can fall under the
// HandledJitCase and can be redirected)
// - A) The OS, trying to be nice, expects we have a breakpoint exception at X+1,
// but does -1 on the address since it knows int3 will leave the eip +1.
// So the context structure it will pass to the Handler is ideally (X+1)-1 = X
//
// ** Here's the race: Since thread B redirected A, the eip is actually Y (not X+1),
// but the kernel still touches it up to Y-1. So there's a window between when we hit a
// bp and when the handler gets called that this can happen.
// This causes an unhandled BP (since the debugger doesn't recognize the bp at Y-1)
//
// So what to do: If we land at Y-1 (ie, if f_IP+1 is the addr of a Redirected Func),
// then restore the EIP back to X. This will skip the redirection.
// Fortunately, this only occurs in cases where it's ok
// to skip. The debugger will recognize the patch and handle it.
if (Thread::IsAddrOfRedirectFunc((PVOID)(f_IP + 1))) {
_ASSERTE(pThread->GetSavedRedirectContext());
SetIP(pContext, GetIP(pThread->GetSavedRedirectContext()) - 1);
STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 3 setting IP = %x\n", pContext->Eip);
}
if (f_IP + 1 == GetEEFuncEntryPoint(THROW_CONTROL_FOR_THREAD_FUNCTION)) {
SetIP(pContext, GetIP(pThread->m_OSContext) - 1);
STRESS_LOG1(LF_EH, LL_INFO100, "CPFH_AdjustContextForThreadSuspensionRace: Case 4 setting IP = %x\n", pContext->Eip);
}
}
#endif // FEATURE_HIJACK
static inline void
CPFH_UpdatePerformanceCounters() {
WRAPPER_NO_CONTRACT;
COUNTER_ONLY(GetPerfCounters().m_Excep.cThrown++);
}
//******************************************************************************
EXCEPTION_DISPOSITION COMPlusAfterUnwind(
EXCEPTION_RECORD *pExceptionRecord,
EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
ThrowCallbackType& tct)
{
WRAPPER_NO_CONTRACT;
// Note: we've completed the unwind pass up to the establisher frame, and we're headed off to finish our
// cleanup and end up back in jitted code. Any more FS0 handlers pushed from this point on out will _not_ be
// unwound. We go ahead and assert right here that indeed there are no handlers below the establisher frame
// before we go any further.
_ASSERTE(pEstablisherFrame == GetCurrentSEHRecord());
Thread* pThread = GetThread();
_ASSERTE(tct.pCurrentExceptionRecord == pEstablisherFrame);
NestedHandlerExRecord nestedHandlerExRecord;
nestedHandlerExRecord.Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, GetCurrFrame(pEstablisherFrame));
// ... and now, put the nested record back on.
INSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
// We entered COMPlusAfterUnwind in PREEMP, but we need to be in COOP from here on out
GCX_COOP_NO_DTOR();
tct.bIsUnwind = TRUE;
tct.pProfilerNotify = NULL;
LOG((LF_EH, LL_INFO100, "COMPlusFrameHandler: unwinding\n"));
tct.bUnwindStack = CPFH_ShouldUnwindStack(pExceptionRecord);
LOG((LF_EH, LL_INFO1000, "COMPlusAfterUnwind: going to: pFunc:%#X, pStack:%#X\n",
tct.pFunc, tct.pStack));
// TODO: UnwindFrames ends up calling into StackWalkFrames which is SO_INTOLERANT
// as is UnwindFrames, etc... Should we make COMPlusAfterUnwind SO_INTOLERANT???
ANNOTATION_VIOLATION(SOToleranceViolation);
UnwindFrames(pThread, &tct);
#ifdef DEBUGGING_SUPPORTED
ExInfo* pExInfo = pThread->GetExceptionState()->GetCurrentExceptionTracker();
if (pExInfo->m_ValidInterceptionContext)
{
// By now we should have all unknown FS:[0] handlers unwinded along with the managed Frames until
// the interception point. We can now pop nested exception handlers and resume at interception context.
EHContext context = pExInfo->m_InterceptionContext;
pExInfo->m_InterceptionContext.Init();
pExInfo->m_ValidInterceptionContext = FALSE;
UnwindExceptionTrackerAndResumeInInterceptionFrame(pExInfo, &context);
}
#endif // DEBUGGING_SUPPORTED
_ASSERTE(!"Should not get here");
return ExceptionContinueSearch;
} // EXCEPTION_DISPOSITION COMPlusAfterUnwind()
#ifdef DEBUGGING_SUPPORTED
//---------------------------------------------------------------------------------------
//
// This function is called to intercept an exception and start an unwind.
//
// Arguments:
// pCurrentEstablisherFrame - the exception registration record covering the stack range
// containing the interception point
// pExceptionRecord - EXCEPTION_RECORD of the exception being intercepted
//
// Return Value:
// ExceptionContinueSearch if the exception cannot be intercepted
//
// Notes:
// If the exception is intercepted, this function never returns.
//
EXCEPTION_DISPOSITION ClrDebuggerDoUnwindAndIntercept(EXCEPTION_REGISTRATION_RECORD *pCurrentEstablisherFrame,
EXCEPTION_RECORD *pExceptionRecord)
{
WRAPPER_NO_CONTRACT;
if (!CheckThreadExceptionStateForInterception())
{
return ExceptionContinueSearch;
}
Thread* pThread = GetThread();
ThreadExceptionState* pExState = pThread->GetExceptionState();
EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame;
ThrowCallbackType tct;
tct.Init();
pExState->GetDebuggerState()->GetDebuggerInterceptInfo(&pEstablisherFrame,
&(tct.pFunc),
&(tct.dHandler),
&(tct.pStack),
NULL,
&(tct.pBottomFrame)
);
//
// If the handler that we've selected as the handler for the target frame of the unwind is in fact above the
// handler that we're currently executing in, then use the current handler instead. Why? Our handlers for
// nested exceptions actually process managed frames that live above them, up to the COMPlusFrameHanlder that
// pushed the nested handler. If the user selectes a frame above the nested handler, then we will have selected
// the COMPlusFrameHandler above the current nested handler. But we don't want to ask RtlUnwind to unwind past
// the nested handler that we're currently executing in.
//
if (pEstablisherFrame > pCurrentEstablisherFrame)
{
// This should only happen if we're in a COMPlusNestedExceptionHandler.
_ASSERTE(IsComPlusNestedExceptionRecord(pCurrentEstablisherFrame));
pEstablisherFrame = pCurrentEstablisherFrame;
}
#ifdef _DEBUG
tct.pCurrentExceptionRecord = pEstablisherFrame;
#endif
LOG((LF_EH|LF_CORDB, LL_INFO100, "ClrDebuggerDoUnwindAndIntercept: Intercepting at %s\n", tct.pFunc->m_pszDebugMethodName));
LOG((LF_EH|LF_CORDB, LL_INFO100, "\t\t: pFunc is 0x%X\n", tct.pFunc));
LOG((LF_EH|LF_CORDB, LL_INFO100, "\t\t: pStack is 0x%X\n", tct.pStack));
CallRtlUnwindSafe(pEstablisherFrame, RtlUnwindCallback, pExceptionRecord, 0);
ExInfo* pExInfo = pThread->GetExceptionState()->GetCurrentExceptionTracker();
if (pExInfo->m_ValidInterceptionContext)
{
// By now we should have all unknown FS:[0] handlers unwinded along with the managed Frames until
// the interception point. We can now pop nested exception handlers and resume at interception context.
GCX_COOP();
EHContext context = pExInfo->m_InterceptionContext;
pExInfo->m_InterceptionContext.Init();
pExInfo->m_ValidInterceptionContext = FALSE;
UnwindExceptionTrackerAndResumeInInterceptionFrame(pExInfo, &context);
}
// on x86 at least, RtlUnwind always returns
// Note: we've completed the unwind pass up to the establisher frame, and we're headed off to finish our
// cleanup and end up back in jitted code. Any more FS0 handlers pushed from this point on out will _not_ be
// unwound.
return COMPlusAfterUnwind(pExState->GetExceptionRecord(), pEstablisherFrame, tct);
} // EXCEPTION_DISPOSITION ClrDebuggerDoUnwindAndIntercept()
#endif // DEBUGGING_SUPPORTED
// This is a wrapper around the assembly routine that invokes RtlUnwind in the OS.
// When we invoke RtlUnwind, the OS will modify the ExceptionFlags field in the
// exception record to reflect unwind. Since we call RtlUnwind in the first pass
// with a valid exception record when we find an exception handler AND because RtlUnwind
// returns on x86, the OS would have flagged the exception record for unwind.
//
// Incase the exception is rethrown from the catch/filter-handler AND it's a non-COMPLUS
// exception, the runtime will use the reference to the saved exception record to reraise
// the exception, as part of rethrow fixup. Since the OS would have modified the exception record
// to reflect unwind, this wrapper will "reset" the ExceptionFlags field when RtlUnwind returns.
// Otherwise, the rethrow will result in second pass, as opposed to first, since the ExceptionFlags
// would indicate an unwind.
//
// This rethrow issue does not affect COMPLUS exceptions since we always create a brand new exception
// record for them in RaiseTheExceptionInternalOnly.
BOOL CallRtlUnwindSafe(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame,
RtlUnwindCallbackType callback,
EXCEPTION_RECORD *pExceptionRecord,
void *retval)
{
LIMITED_METHOD_CONTRACT;
// Save the ExceptionFlags value before invoking RtlUnwind.
DWORD dwExceptionFlags = pExceptionRecord->ExceptionFlags;
BOOL fRetVal = CallRtlUnwind(pEstablisherFrame, callback, pExceptionRecord, retval);
// Reset ExceptionFlags field, if applicable
if (pExceptionRecord->ExceptionFlags != dwExceptionFlags)
{
// We would expect the 32bit OS to have set the unwind flag at this point.
_ASSERTE(pExceptionRecord->ExceptionFlags & EXCEPTION_UNWINDING);
LOG((LF_EH, LL_INFO100, "CallRtlUnwindSafe: Resetting ExceptionFlags from %lu to %lu\n", pExceptionRecord->ExceptionFlags, dwExceptionFlags));
pExceptionRecord->ExceptionFlags = dwExceptionFlags;
}
return fRetVal;
}
//******************************************************************************
// The essence of the first pass handler (after we've decided to actually do
// the first pass handling).
//******************************************************************************
inline EXCEPTION_DISPOSITION __cdecl
CPFH_RealFirstPassHandler( // ExceptionContinueSearch, etc.
EXCEPTION_RECORD *pExceptionRecord, // The exception record, with exception type.
EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame, // Exception frame on whose behalf this is called.
CONTEXT *pContext, // Context from the exception.
void *pDispatcherContext, // @todo
BOOL bAsynchronousThreadStop, // @todo
BOOL fPGCDisabledOnEntry) // @todo
{
// We don't want to use a runtime contract here since this codepath is used during
// the processing of a hard SO. Contracts use a significant amount of stack
// which we can't afford for those cases.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
STATIC_CONTRACT_SO_TOLERANT;
#ifdef _DEBUG
static int breakOnFirstPass = -1;
if (breakOnFirstPass == -1)
breakOnFirstPass = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_BreakOnFirstPass);
if (breakOnFirstPass != 0)
{
_ASSERTE(!"First pass exception handler");
}
#endif
EXCEPTION_DISPOSITION retval;
DWORD exceptionCode = pExceptionRecord->ExceptionCode;
Thread *pThread = GetThread();
#ifdef _DEBUG
static int breakOnSO = -1;
if (breakOnSO == -1)
breakOnSO = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_BreakOnSO);
if (breakOnSO != 0 && exceptionCode == STATUS_STACK_OVERFLOW)
{
DebugBreak(); // ASSERTing will overwrite the guard region
}
#endif
// We always want to be in co-operative mode when we run this function and whenever we return
// from it, want to go to pre-emptive mode because are returning to OS.
_ASSERTE(pThread->PreemptiveGCDisabled());
BOOL bPopNestedHandlerExRecord = FALSE;
LFH found = LFH_NOT_FOUND; // Result of calling LookForHandler.
BOOL bRethrownException = FALSE;
BOOL bNestedException = FALSE;
#if defined(USE_FEF)
BOOL bPopFaultingExceptionFrame = FALSE;
FrameWithCookie<FaultingExceptionFrame> faultingExceptionFrame;
#endif // USE_FEF
ExInfo* pExInfo = &(pThread->GetExceptionState()->m_currentExInfo);
ThrowCallbackType tct;
tct.Init();
tct.pTopFrame = GetCurrFrame(pEstablisherFrame); // highest frame to search to
#ifdef _DEBUG
tct.pCurrentExceptionRecord = pEstablisherFrame;
tct.pPrevExceptionRecord = GetPrevSEHRecord(pEstablisherFrame);
#endif // _DEBUG
BOOL fIsManagedCode = pContext ? ExecutionManager::IsManagedCode(GetIP(pContext)) : FALSE;
// this establishes a marker so can determine if are processing a nested exception
// don't want to use the current frame to limit search as it could have been unwound by
// the time get to nested handler (ie if find an exception, unwind to the call point and
// then resume in the catch and then get another exception) so make the nested handler
// have the same boundary as this one. If nested handler can't find a handler, we won't
// end up searching this frame list twice because the nested handler will set the search
// boundary in the thread and so if get back to this handler it will have a range that starts
// and ends at the same place.
NestedHandlerExRecord nestedHandlerExRecord;
nestedHandlerExRecord.Init((PEXCEPTION_ROUTINE)COMPlusNestedExceptionHandler, GetCurrFrame(pEstablisherFrame));
INSTALL_EXCEPTION_HANDLING_RECORD(&(nestedHandlerExRecord.m_ExReg));
bPopNestedHandlerExRecord = TRUE;
#if defined(USE_FEF)
// Note: don't attempt to push a FEF for an exception in managed code if we weren't in cooperative mode when
// the exception was received. If preemptive GC was enabled when we received the exception, then it means the
// exception was rethrown from unmangaed code (including EE impl), and we shouldn't push a FEF.
if (fIsManagedCode &&
fPGCDisabledOnEntry &&
(pThread->m_pFrame == FRAME_TOP ||
pThread->m_pFrame->GetVTablePtr() != FaultingExceptionFrame::GetMethodFrameVPtr() ||
(size_t)pThread->m_pFrame > (size_t)pEstablisherFrame))
{
// setup interrupted frame so that GC during calls to init won't collect the frames
// only need it for non COM+ exceptions in managed code when haven't already
// got one on the stack (will have one already if we have called rtlunwind because
// the instantiation that called unwind would have installed one)
faultingExceptionFrame.InitAndLink(pContext);
bPopFaultingExceptionFrame = TRUE;
}
#endif // USE_FEF
OBJECTREF e;
e = pThread->LastThrownObject();
STRESS_LOG7(LF_EH, LL_INFO10, "CPFH_RealFirstPassHandler: code:%X, LastThrownObject:%p, MT:%pT"
", IP:%p, SP:%p, pContext:%p, pEstablisherFrame:%p\n",
exceptionCode, OBJECTREFToObject(e), (e!=0)?e->GetMethodTable():0,
pContext ? GetIP(pContext) : 0, pContext ? GetSP(pContext) : 0,
pContext, pEstablisherFrame);
#ifdef LOGGING
// If it is a complus exception, and there is a thrown object, get its name, for better logging.
if (IsComPlusException(pExceptionRecord))
{
const char * eClsName = "!EXCEPTION_COMPLUS";
if (e != 0)
{
eClsName = e->GetTrueMethodTable()->GetDebugClassName();
}
LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: exception: 0x%08X, class: '%s', IP: 0x%p\n",
exceptionCode, eClsName, pContext ? GetIP(pContext) : NULL));
}
#endif
EXCEPTION_POINTERS exceptionPointers = {pExceptionRecord, pContext};
STRESS_LOG4(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: setting boundaries: Exinfo: 0x%p, BottomMostHandler:0x%p, SearchBoundary:0x%p, TopFrame:0x%p\n",
pExInfo, pExInfo->m_pBottomMostHandler, pExInfo->m_pSearchBoundary, tct.pTopFrame);
// Here we are trying to decide if we are coming in as:
// 1) first handler in a brand new exception
// 2) a subsequent handler in an exception
// 3) a nested exception
// m_pBottomMostHandler is the registration structure (establisher frame) for the most recent (ie lowest in
// memory) non-nested handler that was installed and pEstablisher frame is what the current handler
// was registered with.
// The OS calls each registered handler in the chain, passing its establisher frame to it.
if (pExInfo->m_pBottomMostHandler != NULL && pEstablisherFrame > pExInfo->m_pBottomMostHandler)
{
STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: detected subsequent handler. ExInfo:0x%p, BottomMost:0x%p SearchBoundary:0x%p\n",
pExInfo, pExInfo->m_pBottomMostHandler, pExInfo->m_pSearchBoundary);
// If the establisher frame of this handler is greater than the bottommost then it must have been
// installed earlier and therefore we are case 2
if (pThread->GetThrowable() == NULL)
{
// Bottommost didn't setup a throwable, so not exception not for us
retval = ExceptionContinueSearch;
goto exit;
}
// setup search start point
tct.pBottomFrame = pExInfo->m_pSearchBoundary;
if (tct.pTopFrame == tct.pBottomFrame)
{
// this will happen if our nested handler already searched for us so we don't want
// to search again
retval = ExceptionContinueSearch;
goto exit;
}
}
else
{ // we are either case 1 or case 3
#if defined(_DEBUG_IMPL)
//@todo: merge frames, context, handlers
if (pThread->GetFrame() != FRAME_TOP)
pThread->GetFrame()->LogFrameChain(LF_EH, LL_INFO1000);
#endif // _DEBUG_IMPL
// If the exception was rethrown, we'll create a new ExInfo, which will represent the rethrown exception.
// The original exception is not the rethrown one.
if (pExInfo->m_ExceptionFlags.IsRethrown() && pThread->LastThrownObject() != NULL)
{
pExInfo->m_ExceptionFlags.ResetIsRethrown();
bRethrownException = TRUE;
#if defined(USE_FEF)
if (bPopFaultingExceptionFrame)
{
// if we added a FEF, it will refer to the frame at the point of the original exception which is
// already unwound so don't want it.
// If we rethrew the exception we have already added a helper frame for the rethrow, so don't
// need this one. If we didn't rethrow it, (ie rethrow from native) then there the topmost frame will
// be a transition to native frame in which case we don't need it either
faultingExceptionFrame.Pop();
bPopFaultingExceptionFrame = FALSE;
}
#endif
}
// If the establisher frame is less than the bottommost handler, then this is nested because the
// establisher frame was installed after the bottommost.
if (pEstablisherFrame < pExInfo->m_pBottomMostHandler
/* || IsComPlusNestedExceptionRecord(pEstablisherFrame) */ )
{
bNestedException = TRUE;
// case 3: this is a nested exception. Need to save and restore the thread info
STRESS_LOG3(LF_EH, LL_INFO10000, "CPFH_RealFirstPassHandler: ExInfo:0x%p detected nested exception 0x%p < 0x%p\n",
pExInfo, pEstablisherFrame, pExInfo->m_pBottomMostHandler);
EXCEPTION_REGISTRATION_RECORD* pNestedER = TryFindNestedEstablisherFrame(pEstablisherFrame);
ExInfo *pNestedExInfo;
if (!pNestedER || pNestedER >= pExInfo->m_pBottomMostHandler )
{
// RARE CASE. We've re-entered the EE from an unmanaged filter.
//
// OR
//
// We can be here if we dont find a nested exception handler. This is exemplified using
// call chain of scenario 2 explained further below.
//
// Assuming __try of NativeB throws an exception E1 and it gets caught in ManagedA2, then
// bottom-most handler (BMH) is going to be CPFH_A. The catch will trigger an unwind
// and invoke __finally in NativeB. Let the __finally throw a new exception E2.
//
// Assuming ManagedB2 has a catch block to catch E2, when we enter CPFH_B looking for a
// handler for E2, our establisher frame will be that of CPFH_B, which will be lower
// in stack than current BMH (which is CPFH_A). Thus, we will come here, determining
// E2 to be nested exception correctly but not find a nested exception handler.
void *limit = (void *) GetPrevSEHRecord(pExInfo->m_pBottomMostHandler);
pNestedExInfo = new (nothrow) ExInfo(); // Very rare failure here; need robust allocator.
if (pNestedExInfo == NULL)
{ // if we can't allocate memory, we can't correctly continue.
#if defined(_DEBUG)
if (CLRConfig::GetConfigValue(CLRConfig::INTERNAL_NestedEhOom))
_ASSERTE(!"OOM in callback from unmanaged filter.");
#endif // _DEBUG
EEPOLICY_HANDLE_FATAL_ERROR(COR_E_OUTOFMEMORY);
}
pNestedExInfo->m_StackAddress = limit; // Note: this is also the flag that tells us this
// ExInfo was stack allocated.
}
else
{
pNestedExInfo = &((NestedHandlerExRecord*)pNestedER)->m_handlerInfo;
}
LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: PushExInfo() current: 0x%p previous: 0x%p\n",
pExInfo->m_StackAddress, pNestedExInfo->m_StackAddress));
_ASSERTE(pNestedExInfo);
pNestedExInfo->m_hThrowable = NULL; // pNestedExInfo may be stack allocated, and as such full of
// garbage. m_hThrowable must be sane, so set it to NULL. (We could
// zero the entire record, but this is cheaper.)
pNestedExInfo->CopyAndClearSource(pExInfo);
pExInfo->m_pPrevNestedInfo = pNestedExInfo; // Save at head of nested info chain
#if 0
/* the following code was introduced in Whidbey as part of the Faulting Exception Frame removal (12/03).
However it isn't correct. If any nested exceptions occur while processing a rethrow, we would
incorrectly consider the nested exception to be a rethrow. See VSWhidbey 349379 for an example.
Therefore I am disabling this code until we see a failure that explains why it was added in the first
place. cwb 9/04.
*/
// If we're here as a result of a rethrown exception, set the rethrown flag on the new ExInfo.
if (bRethrownException)
{
pExInfo->m_ExceptionFlags.SetIsRethrown();
}
#endif
}
else
{
// At this point, either:
//
// 1) the bottom-most handler is NULL, implying this is a new exception for which we are getting ready, OR
// 2) the bottom-most handler is not-NULL, implying that a there is already an existing exception in progress.
//
// Scenario 1 is that of a new throw and is easy to understand. Scenario 2 is the interesting one.
//
// ManagedA1 -> ManagedA2 -> ManagedA3 -> NativeCodeA -> ManagedB1 -> ManagedB2 -> ManagedB3 -> NativeCodeB
//
// On x86, each block of managed code is protected by one COMPlusFrameHandler [CPFH] (CLR's exception handler
// for managed code), unlike 64bit where each frame has a personality routine attached to it. Thus,
// for the example above, assume CPFH_A protects ManagedA* blocks and is setup just before the call to
// ManagedA1. Likewise, CPFH_B protects ManagedB* blocks and is setup just before the call to ManagedB1.
//
// When ManagedB3 throws an exception, CPFH_B is invoked to look for a handler in all of the ManagedB* blocks.
// At this point, it is setup as the "bottom-most-handler" (BMH). If no handler is found and exception reaches
// ManagedA* blocks, CPFH_A is invoked to look for a handler and thus, becomes BMH.
//
// Thus, in the first pass on x86 for a given exception, a particular CPFH will be invoked only once when looking
// for a handler and thus, registered as BMH only once. Either the exception goes unhandled and the process will
// terminate or a handler will be found and second pass will commence.
//
// However, assume NativeCodeB had a __try/__finally and raised an exception [E1] within the __try. Let's assume
// it gets caught in ManagedB1 and thus, unwind is triggered. At this point, the active exception tracker
// has context about the exception thrown out of __try and CPFH_B is registered as BMH.
//
// If the __finally throws a new exception [E2], CPFH_B will be invoked again for first pass while looking for
// a handler for the thrown exception. Since BMH is already non-NULL, we will come here since EstablisherFrame will be
// the same as BMH (because EstablisherFrame will be that of CPFH_B). We will proceed to overwrite the "required" parts
// of the existing exception tracker with the details of E2 (see setting of exception record and context below), erasing
// any artifact of E1.
//
// This is unlike Scenario 1 when exception tracker is completely initialized to default values. This is also
// unlike 64bit which will detect that E1 and E2 are different exceptions and hence, will setup a new tracker
// to track E2, effectively behaving like Scenario 1 above. X86 cannot do this since there is no nested exception
// tracker setup that gets to see the new exception.
//
// Thus, if E1 was a CSE and E2 isn't, we will come here and treat E2 as a CSE as well since corruption severity
// is initialized as part of exception tracker initialization. Thus, E2 will start to be treated as CSE, which is
// incorrect. Similar argument applies to delivery of First chance exception notification delivery.
//
// <QUIP> Another example why we should unify EH systems :) </QUIP>
//
// To address this issue, we will need to reset exception tracker here, just like the overwriting of "required"
// parts of exception tracker.
// If the current establisher frame is the same as the bottom-most-handler and we are here
// in the first pass, assert that current exception and the one tracked by active exception tracker
// are indeed different exceptions. In such a case, we must reset the exception tracker so that it can be
// setup correctly further down when CEHelper::SetupCorruptionSeverityForActiveException is invoked.
if ((pExInfo->m_pBottomMostHandler != NULL) &&
(pEstablisherFrame == pExInfo->m_pBottomMostHandler))
{
// Current exception should be different from the one exception tracker is already tracking.
_ASSERTE(pExceptionRecord != pExInfo->m_pExceptionRecord);
// This cannot be nested exceptions - they are handled earlier (see above).
_ASSERTE(!bNestedException);
LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Bottom-most handler (0x%p) is the same as EstablisherFrame.\n",
pExInfo->m_pBottomMostHandler));
LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Exception record in exception tracker is 0x%p, while that of new exception is 0x%p.\n",
pExInfo->m_pExceptionRecord, pExceptionRecord));
LOG((LF_EH, LL_INFO100, "CPFH_RealFirstPassHandler: Resetting exception tracker (0x%p).\n", pExInfo));
// This will reset the exception tracker state, including the corruption severity.
pExInfo->Init();
}
}
// If we are handling a fault from managed code, we need to set the Thread->ExInfo->pContext to
// the current fault context, which is used in the stack walk to get back into the managed
// stack with the correct registers. (Previously, this was done by linking in a FaultingExceptionFrame
// record.)
// We are about to create the managed exception object, which may trigger a GC, so set this up now.
pExInfo->m_pExceptionRecord = pExceptionRecord;
pExInfo->m_pContext = pContext;
if (pContext && ShouldHandleManagedFault(pExceptionRecord, pContext, pEstablisherFrame, pThread))
{ // If this was a fault in managed code, rather than create a Frame for stackwalking,
// we can use this exinfo (after all, it has all the register info.)
pExInfo->m_ExceptionFlags.SetUseExInfoForStackwalk();
}