summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHanjoung Lee <waterets@gmail.com>2017-02-24 16:02:21 +0900
committerJan Kotas <jkotas@microsoft.com>2017-02-23 23:02:21 -0800
commit77f2ad4c07b24fcfe1298dd7cd260876ef46e6c3 (patch)
treeca3ae57361dfc130e729df51bee8e13b8649d873 /src
parent687e0fbfe1b753653968308d1cd17c079c5a87fc (diff)
downloadcoreclr-77f2ad4c07b24fcfe1298dd7cd260876ef46e6c3.tar.gz
coreclr-77f2ad4c07b24fcfe1298dd7cd260876ef46e6c3.tar.bz2
coreclr-77f2ad4c07b24fcfe1298dd7cd260876ef46e6c3.zip
[x86/Linux] Initial patch for EH funclet (#9601)
- Generate a simple EH funclet frame and support SP-based stack unwinding for funclets. - Introduce assembly helpers : CallEHFunclet and CallEHFilterFunclet
Diffstat (limited to 'src')
-rw-r--r--src/jit/codegencommon.cpp26
-rw-r--r--src/jit/codegenxarch.cpp2
-rw-r--r--src/pal/inc/unixasmmacrosx86.inc12
-rw-r--r--src/pal/src/arch/i386/exceptionhelper.S7
-rw-r--r--src/pal/src/exception/seh.cpp4
-rw-r--r--src/vm/CMakeLists.txt1
-rw-r--r--src/vm/eetwain.cpp31
-rw-r--r--src/vm/exceptionhandling.cpp83
-rw-r--r--src/vm/exceptionhandling.h1
-rw-r--r--src/vm/i386/ehhelpers.S98
10 files changed, 225 insertions, 40 deletions
diff --git a/src/jit/codegencommon.cpp b/src/jit/codegencommon.cpp
index 0c4cba40e1..75de4206f8 100644
--- a/src/jit/codegencommon.cpp
+++ b/src/jit/codegencommon.cpp
@@ -10316,6 +10316,22 @@ void CodeGen::genCaptureFuncletPrologEpilogInfo()
/*****************************************************************************
*
* Generates code for an EH funclet prolog.
+ *
+ *
+ * Funclets have the following incoming arguments:
+ *
+ * catch/filter-handler: eax = the exception object that was caught (see GT_CATCH_ARG)
+ * filter: eax = the exception object that was caught (see GT_CATCH_ARG)
+ * finally/fault: none
+ *
+ * Funclets set the following registers on exit:
+ *
+ * catch/filter-handler: eax = the address at which execution should resume (see BBJ_EHCATCHRET)
+ * filter: eax = non-zero if the handler should handle the exception, zero otherwise (see GT_RETFILT)
+ * finally/fault: none
+ *
+ * Funclet prolog/epilog sequence and funclet frame layout are TBD.
+ *
*/
void CodeGen::genFuncletProlog(BasicBlock* block)
@@ -10331,10 +10347,13 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
compiler->unwindBegProlog();
- // TODO Save callee-saved registers
-
// This is the end of the OS-reported prolog for purposes of unwinding
compiler->unwindEndProlog();
+
+ // TODO We may need EBP restore sequence here if we introduce PSPSym
+
+ // Add a padding for 16-byte alignment
+ inst_RV_IV(INS_sub, REG_SPBASE, 12, EA_PTRSIZE);
}
/*****************************************************************************
@@ -10353,7 +10372,8 @@ void CodeGen::genFuncletEpilog()
ScopedSetVariable<bool> _setGeneratingEpilog(&compiler->compGeneratingEpilog, true);
- // TODO Restore callee-saved registers
+ // Revert a padding that was added for 16-byte alignment
+ inst_RV_IV(INS_add, REG_SPBASE, 12, EA_PTRSIZE);
instGen_Return(0);
}
diff --git a/src/jit/codegenxarch.cpp b/src/jit/codegenxarch.cpp
index 758156b165..acf7a0d266 100644
--- a/src/jit/codegenxarch.cpp
+++ b/src/jit/codegenxarch.cpp
@@ -241,7 +241,9 @@ BasicBlock* CodeGen::genCallFinally(BasicBlock* block)
if ((compiler->lvaPSPSym == BAD_VAR_NUM) ||
(!compiler->compLocallocUsed && (compiler->funCurrentFunc()->funKind == FUNC_ROOT)))
{
+#ifndef UNIX_X86_ABI
inst_RV_RV(INS_mov, REG_ARG_0, REG_SPBASE, TYP_I_IMPL);
+#endif // !UNIX_X86_ABI
}
else
{
diff --git a/src/pal/inc/unixasmmacrosx86.inc b/src/pal/inc/unixasmmacrosx86.inc
index 77b3a63484..61ad946c7f 100644
--- a/src/pal/inc/unixasmmacrosx86.inc
+++ b/src/pal/inc/unixasmmacrosx86.inc
@@ -66,6 +66,18 @@ C_FUNC(\Name\()_End):
pop ebp
.endm
+.macro ESP_PROLOG_BEG
+.endm
+
+.macro ESP_PROLOG_END
+.endm
+
+.macro ESP_EPILOG_BEG
+.endm
+
+.macro ESP_EPILOG_END
+.endm
+
.macro PREPARE_EXTERNAL_VAR Name, Reg
.att_syntax
call 0f
diff --git a/src/pal/src/arch/i386/exceptionhelper.S b/src/pal/src/arch/i386/exceptionhelper.S
index 4980b89451..f0bf8f8ef9 100644
--- a/src/pal/src/arch/i386/exceptionhelper.S
+++ b/src/pal/src/arch/i386/exceptionhelper.S
@@ -19,8 +19,8 @@
LEAF_ENTRY ThrowExceptionFromContextInternal, _TEXT
push ebp
- mov eax, [esp + 12] // ebx: PAL_SEHException *
- mov ebx, [esp + 8] // eax: CONTEXT *
+ mov ecx, [esp + 12] // ecx: PAL_SEHException * (first argument for ThrowExceptionHelper)
+ mov ebx, [esp + 8] // ebx: CONTEXT *
mov ebp, [ebx + CONTEXT_Ebp]
mov esp, [ebx + CONTEXT_ResumeEsp]
@@ -33,9 +33,6 @@ LEAF_ENTRY ThrowExceptionFromContextInternal, _TEXT
// the EBP is no longer saved in the current stack frame.
.cfi_restore ebp
- // Store PAL_SEHException as the first argument
- push eax
-
// Store return address to the stack
mov ebx, [ebx + CONTEXT_Eip]
push ebx
diff --git a/src/pal/src/exception/seh.cpp b/src/pal/src/exception/seh.cpp
index 7682e9e6e6..bc74ccc937 100644
--- a/src/pal/src/exception/seh.cpp
+++ b/src/pal/src/exception/seh.cpp
@@ -226,7 +226,11 @@ Parameters:
PAL_SEHException* ex - the exception to throw.
--*/
extern "C"
+#ifdef _TARGET_X86_
+void __fastcall ThrowExceptionHelper(PAL_SEHException* ex)
+#else // _TARGET_X86_
void ThrowExceptionHelper(PAL_SEHException* ex)
+#endif // !_TARGET_X86_
{
throw std::move(*ex);
}
diff --git a/src/vm/CMakeLists.txt b/src/vm/CMakeLists.txt
index cdf5a53d1c..01537534ed 100644
--- a/src/vm/CMakeLists.txt
+++ b/src/vm/CMakeLists.txt
@@ -368,6 +368,7 @@ else(WIN32)
)
elseif(CLR_CMAKE_TARGET_ARCH_I386)
set(VM_SOURCES_WKS_ARCH_ASM
+ ${ARCH_SOURCES_DIR}/ehhelpers.S
${ARCH_SOURCES_DIR}/asmhelpers.S
${ARCH_SOURCES_DIR}/jithelp.S
${ARCH_SOURCES_DIR}/gmsasm.S
diff --git a/src/vm/eetwain.cpp b/src/vm/eetwain.cpp
index bf6e1c7aa5..989300d468 100644
--- a/src/vm/eetwain.cpp
+++ b/src/vm/eetwain.cpp
@@ -3786,10 +3786,11 @@ void UnwindEbpDoubleAlignFrameProlog(
/*****************************************************************************/
bool UnwindEbpDoubleAlignFrame(
- PREGDISPLAY pContext,
- hdrInfo * info,
- PTR_CBYTE methodStart,
- unsigned flags,
+ PREGDISPLAY pContext,
+ EECodeInfo *pCodeInfo,
+ hdrInfo *info,
+ PTR_CBYTE methodStart,
+ unsigned flags,
StackwalkCacheUnwindInfo *pUnwindInfo) // out-only, perf improvement
{
LIMITED_METHOD_CONTRACT;
@@ -3806,6 +3807,25 @@ bool UnwindEbpDoubleAlignFrame(
{
TADDR baseSP;
+#ifdef WIN64EXCEPTIONS
+ // Funclets' frame pointers(EBP) are always restored so they can access to main function's local variables.
+ // Therefore the value of EBP is invalid for unwinder so we should use ESP instead.
+ // TODO If funclet frame layout is changed from CodeGen::genFuncletProlog() and genFuncletEpilog(),
+ // we need to change here accordingly. It is likely to have changes when introducing PSPSym.
+ // TODO Currently we assume that ESP of funclet frames is always fixed but actually it could change.
+ if (pCodeInfo->IsFunclet())
+ {
+ baseSP = curESP + 12; // padding for 16byte stack alignment allocated in genFuncletProlog()
+
+ pContext->PCTAddr = baseSP;
+ pContext->ControlPC = *PTR_PCODE(pContext->PCTAddr);
+
+ pContext->SP = (DWORD)(baseSP + sizeof(TADDR));
+
+ return true;
+ }
+#else // WIN64EXCEPTIONS
+
FrameType frameType = GetHandlerFrameInfo(info, curEBP,
curESP, (DWORD) IGNORE_VAL,
&baseSP);
@@ -3860,6 +3880,7 @@ bool UnwindEbpDoubleAlignFrame(
return true;
}
+#endif // !WIN64EXCEPTIONS
}
//
@@ -3990,7 +4011,7 @@ bool UnwindStackFrame(PREGDISPLAY pContext,
* Now we know that have an EBP frame
*/
- if (!UnwindEbpDoubleAlignFrame(pContext, info, methodStart, flags, pUnwindInfo))
+ if (!UnwindEbpDoubleAlignFrame(pContext, pCodeInfo, info, methodStart, flags, pUnwindInfo))
return false;
}
diff --git a/src/vm/exceptionhandling.cpp b/src/vm/exceptionhandling.cpp
index 1e51467789..d7f22d58f1 100644
--- a/src/vm/exceptionhandling.cpp
+++ b/src/vm/exceptionhandling.cpp
@@ -19,6 +19,7 @@
#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_) || defined(_TARGET_X86_)
#define ADJUST_PC_UNWOUND_TO_CALL
+#define USE_CALLER_SP_IN_FUNCLET
#endif // _TARGET_ARM_ || _TARGET_ARM64_ || _TARGET_X86_
#ifndef DACCESS_COMPILE
@@ -574,7 +575,7 @@ UINT_PTR ExceptionTracker::CallCatchHandler(CONTEXT* pContextRecord, bool* pfAbo
if (!fIntercepted)
{
_ASSERTE(m_uCatchToCallPC != 0 && m_pClauseForCatchToken != NULL);
- uResumePC = CallHandler(m_uCatchToCallPC, sfStackFp, &m_ClauseForCatch, pMD, Catch ARM_ARG(pContextRecord) ARM64_ARG(pContextRecord));
+ uResumePC = CallHandler(m_uCatchToCallPC, sfStackFp, &m_ClauseForCatch, pMD, Catch X86_ARG(pContextRecord) ARM_ARG(pContextRecord) ARM64_ARG(pContextRecord));
}
else
{
@@ -1289,12 +1290,12 @@ void ExceptionTracker::InitializeCurrentContextForCrawlFrame(CrawlFrame* pcfThis
pRD->SP = sfEstablisherFrame.SP;
pRD->ControlPC = pDispatcherContext->ControlPc;
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
pcfThisFrame->pRD->IsCallerSPValid = TRUE;
// Assert our first pass assumptions for the Arm/Arm64
_ASSERTE(sfEstablisherFrame.SP == GetSP(pDispatcherContext->ContextRecord));
-#endif // defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#endif // USE_CALLER_SP_IN_FUNCLET
}
@@ -2872,7 +2873,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
SetEnclosingClauseInfo(fIsFunclet,
pcfThisFrame->GetRelOffset(),
GetSP(pcfThisFrame->GetRegisterSet()->pCallerContext));
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
// On ARM & ARM64, the OS passes us the CallerSP for the frame for which personality routine has been invoked.
// Since IL filters are invoked in the first pass, we pass this CallerSP to the filter funclet which will
// then lookup the actual frame pointer value using it since we dont have a frame pointer to pass to it
@@ -2887,11 +2888,11 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
_ASSERTE(pCurRegDisplay->IsCallerContextValid && pCurRegDisplay->IsCallerSPValid);
// 3) CallerSP is intact
_ASSERTE(GetSP(pCurRegDisplay->pCallerContext) == GetRegdisplaySP(pCurRegDisplay));
-#endif // _TARGET_ARM_ || _TARGET_ARM64_
+#endif // USE_CALLER_SP_IN_FUNCLET
{
// CallHandler expects to be in COOP mode.
GCX_COOP();
- dwResult = CallHandler(dwFilterStartPC, sf, &EHClause, pMD, Filter ARM_ARG(pCurRegDisplay->pCallerContext) ARM64_ARG(pCurRegDisplay->pCallerContext));
+ dwResult = CallHandler(dwFilterStartPC, sf, &EHClause, pMD, Filter X86_ARG(pCurRegDisplay->pCallerContext) ARM_ARG(pCurRegDisplay->pCallerContext) ARM64_ARG(pCurRegDisplay->pCallerContext));
}
}
EX_CATCH
@@ -3124,7 +3125,7 @@ CLRUnwindStatus ExceptionTracker::ProcessManagedCallFrame(
// Since we also forbid GC during second pass, disable it now since
// invocation of managed code can result in a GC.
ENDFORBIDGC();
- dwStatus = CallHandler(dwHandlerStartPC, sf, &EHClause, pMD, FaultFinally ARM_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM64_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext));
+ dwStatus = CallHandler(dwHandlerStartPC, sf, &EHClause, pMD, FaultFinally X86_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext) ARM64_ARG(pcfThisFrame->GetRegisterSet()->pCurrentContext));
// Once we return from a funclet, forbid GC again (refer to comment before start of the loop for details)
BEGINFORBIDGC();
@@ -3192,20 +3193,57 @@ lExit:
#define OPTIONAL_SO_CLEANUP_UNWIND(pThread, pFrame) if (pThread->GetFrame() < pFrame) { UnwindFrameChain(pThread, pFrame); }
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
// This is an assembly helper that enables us to call into EH funclets.
EXTERN_C DWORD_PTR STDCALL CallEHFunclet(Object *pThrowable, UINT_PTR pFuncletToInvoke, UINT_PTR *pFirstNonVolReg, UINT_PTR *pFuncletCallerSP);
// This is an assembly helper that enables us to call into EH filter funclets.
EXTERN_C DWORD_PTR STDCALL CallEHFilterFunclet(Object *pThrowable, TADDR CallerSP, UINT_PTR pFuncletToInvoke, UINT_PTR *pFuncletCallerSP);
-#endif // _TARGET_ARM_ || _TARGET_ARM64_
+static inline UINT_PTR CastHandlerFn(HandlerFn *pfnHandler)
+{
+#ifdef _TARGET_ARM_
+ return DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler);
+#else
+ return (UINT_PTR)pfnHandler;
+#endif
+}
+
+static inline UINT_PTR *GetFirstNonVolatileRegisterAddress(PCONTEXT pContextRecord)
+{
+#if defined(_TARGET_ARM_)
+ return (UINT_PTR*)&(pContextRecord->R4);
+#elif defined(_TARGET_ARM64_)
+ return (UINT_PTR*)&(pContextRecord->X19);
+#elif defined(_TARGET_X86_)
+ return (UINT_PTR*)&(pContextRecord->Edi);
+#else
+ PORTABILITY_ASSERT("GetFirstNonVolatileRegisterAddress");
+ return NULL;
+#endif
+}
+
+static inline TADDR GetFrameRestoreBase(PCONTEXT pContextRecord)
+{
+#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+ return GetSP(pContextRecord);
+#elif defined(_TARGET_X86_)
+ return pContextRecord->Ebp;
+#else
+ PORTABILITY_ASSERT("GetFrameRestoreBase");
+ return NULL;
+#endif
+}
+
+#endif // USE_CALLER_SP_IN_FUNCLET
+
DWORD_PTR ExceptionTracker::CallHandler(
UINT_PTR uHandlerStartPC,
StackFrame sf,
EE_ILEXCEPTION_CLAUSE* pEHClause,
MethodDesc* pMD,
EHFuncletType funcletType
+ X86_ARG(PCONTEXT pContextRecord)
ARM_ARG(PCONTEXT pContextRecord)
ARM64_ARG(PCONTEXT pContextRecord)
)
@@ -3260,7 +3298,7 @@ DWORD_PTR ExceptionTracker::CallHandler(
break;
}
-#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#ifdef USE_CALLER_SP_IN_FUNCLET
// Invoke the funclet. We pass throwable only when invoking the catch block.
// Since the actual caller of the funclet is the assembly helper, pass the reference
// to the CallerStackFrame instance so that it can be updated.
@@ -3269,14 +3307,9 @@ DWORD_PTR ExceptionTracker::CallHandler(
if (funcletType != EHFuncletType::Filter)
{
dwResumePC = CallEHFunclet((funcletType == EHFuncletType::Catch)?OBJECTREFToObject(throwable):(Object *)NULL,
-#ifdef _TARGET_ARM_
- DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler),
- (UINT_PTR*)&(pContextRecord->R4),
-#else
- (UINT_PTR)pfnHandler,
- &(pContextRecord->X19),
-#endif // _TARGET_ARM_
- pFuncletCallerSP);
+ CastHandlerFn(pfnHandler),
+ GetFirstNonVolatileRegisterAddress(pContextRecord),
+ pFuncletCallerSP);
}
else
{
@@ -3284,20 +3317,16 @@ DWORD_PTR ExceptionTracker::CallHandler(
// it will retrieve the framepointer for accessing the locals in the parent
// method.
dwResumePC = CallEHFilterFunclet(OBJECTREFToObject(throwable),
- GetSP(pContextRecord),
-#ifdef _TARGET_ARM_
- DataPointerToThumbCode<UINT_PTR, HandlerFn *>(pfnHandler),
-#else
- (UINT_PTR)pfnHandler,
-#endif // _TARGET_ARM_
- pFuncletCallerSP);
+ GetFrameRestoreBase(pContextRecord),
+ CastHandlerFn(pfnHandler),
+ pFuncletCallerSP);
}
-#else // defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
+#else // USE_CALLER_SP_IN_FUNCLET
//
// Invoke the funclet.
//
dwResumePC = pfnHandler(sf.SP, OBJECTREFToObject(throwable));
-#endif // _TARGET_ARM_
+#endif // !USE_CALLER_SP_IN_FUNCLET
switch(funcletType)
{
diff --git a/src/vm/exceptionhandling.h b/src/vm/exceptionhandling.h
index 71bcab749f..4dba095ff5 100644
--- a/src/vm/exceptionhandling.h
+++ b/src/vm/exceptionhandling.h
@@ -404,6 +404,7 @@ private:
EE_ILEXCEPTION_CLAUSE* pEHClause,
MethodDesc* pMD,
EHFuncletType funcletType
+ X86_ARG(PT_CONTEXT pContextRecord)
ARM_ARG(PT_CONTEXT pContextRecord)
ARM64_ARG(PT_CONTEXT pContextRecord)
);
diff --git a/src/vm/i386/ehhelpers.S b/src/vm/i386/ehhelpers.S
new file mode 100644
index 0000000000..9f959b1bbd
--- /dev/null
+++ b/src/vm/i386/ehhelpers.S
@@ -0,0 +1,98 @@
+// 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.
+
+.intel_syntax noprefix
+#include "unixasmmacros.inc"
+#include "asmconstants.h"
+
+// DWORD_PTR STDCALL CallEHFunclet(Object *pThrowable, UINT_PTR pFuncletToInvoke, UINT_PTR *pFirstNonVolReg, UINT_PTR *pFuncletCallerSP);
+// ESP based frame
+NESTED_ENTRY CallEHFunclet, _TEXT, NoHandler
+
+ ESP_PROLOG_BEG
+ PROLOG_PUSH ebp
+ mov ebp, esp
+ PROLOG_PUSH ebx
+ PROLOG_PUSH esi
+ PROLOG_PUSH edi
+ ESP_PROLOG_END
+
+ // On entry:
+ //
+ // [ebp+ 8] = throwable
+ // [ebp+12] = PC to invoke
+ // [ebp+16] = address of EDI register in CONTEXT record // used to restore the non-volatile registers of CrawlFrame
+ // [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved.
+ //
+
+ // Save the SP of this function
+ mov eax, [ebp + 20]
+ mov [eax], esp
+ // Save the funclet PC for later call
+ mov edx, [ebp + 12]
+ // Pass throwable object to funclet
+ mov eax, [ebp + 8]
+ // Restore non-volatiles registers
+ mov ecx, [ebp + 16]
+ mov edi, [ecx]
+ mov esi, [ecx + 4]
+ mov ebx, [ecx + 8]
+ mov ebp, [ecx + 24]
+ // Invoke the funclet
+ call edx
+
+ ESP_EPILOG_BEG
+ EPILOG_POP edi
+ EPILOG_POP esi
+ EPILOG_POP ebx
+ EPILOG_POP ebp
+ ESP_EPILOG_END
+
+ ret 16
+
+NESTED_END CallEHFunclet, _TEXT
+
+// DWORD_PTR STDCALL CallEHFilterFunclet(Object *pThrowable, TADDR CallerSP, UINT_PTR pFuncletToInvoke, UINT_PTR *pFuncletCallerSP);
+// ESP based frame
+NESTED_ENTRY CallEHFilterFunclet, _TEXT, NoHandler
+
+ ESP_PROLOG_BEG
+ PROLOG_PUSH ebp
+ mov ebp, esp
+ PROLOG_PUSH ebx
+ PROLOG_PUSH esi
+ PROLOG_PUSH edi
+ ESP_PROLOG_END
+
+ // On entry:
+ //
+ // [ebp+ 8] = throwable
+ // [ebp+12] = FP to restore
+ // [ebp+16] = PC to invoke
+ // [ebp+20] = address of the location where the SP of funclet's caller (i.e. this helper) should be saved.
+ //
+
+ // Save the SP of this function
+ mov eax, [ebp + 20]
+ mov [eax], esp
+ // Save the funclet PC for later call
+ mov edx, [ebp + 16]
+ // Pass throwable object to funclet
+ mov eax, [ebp + 8]
+ // Restore FP
+ mov ebp, [ebp + 12]
+ // Invoke the funclet
+ call edx
+
+ ESP_EPILOG_BEG
+ EPILOG_POP edi
+ EPILOG_POP esi
+ EPILOG_POP ebx
+ EPILOG_POP ebp
+ ESP_EPILOG_END
+
+ ret 16
+
+NESTED_END CallEHFunclet, _TEXT
+