; 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.
; ==++==
;
;
; ==--==
;
; FILE: asmhelpers.asm
;
; *** NOTE: If you make changes to this file, propagate the changes to
; asmhelpers.s in this directory
;
;
; ======================================================================================
.586
.model flat
include asmconstants.inc
assume fs: nothing
option casemap:none
.code
EXTERN __imp__RtlUnwind@16:DWORD
ifdef _DEBUG
EXTERN _HelperMethodFrameConfirmState@20:PROC
endif
ifdef FEATURE_MIXEDMODE
EXTERN _IJWNOADThunkJumpTargetHelper@4:PROC
endif
EXTERN _StubRareEnableWorker@4:PROC
ifdef FEATURE_COMINTEROP
EXTERN _StubRareDisableHRWorker@4:PROC
endif ; FEATURE_COMINTEROP
EXTERN _StubRareDisableTHROWWorker@4:PROC
EXTERN __imp__TlsGetValue@4:DWORD
TlsGetValue PROTO stdcall
ifdef FEATURE_HIJACK
EXTERN _OnHijackWorker@4:PROC
endif ;FEATURE_HIJACK
EXTERN _COMPlusFrameHandler:PROC
ifdef FEATURE_COMINTEROP
EXTERN _COMPlusFrameHandlerRevCom:PROC
endif ; FEATURE_COMINTEROP
EXTERN __alloca_probe:PROC
EXTERN _NDirectImportWorker@4:PROC
EXTERN _UMThunkStubRareDisableWorker@8:PROC
ifndef FEATURE_IMPLICIT_TLS
ifdef ENABLE_GET_THREAD_GENERIC_FULL_CHECK
; This is defined in C (threads.cpp) and enforces EE_THREAD_NOT_REQUIRED contracts
GetThreadGenericFullCheck EQU ?GetThreadGenericFullCheck@@YGPAVThread@@XZ
EXTERN GetThreadGenericFullCheck:PROC
endif ; ENABLE_GET_THREAD_GENERIC_FULL_CHECK
EXTERN _gThreadTLSIndex:DWORD
EXTERN _gAppDomainTLSIndex:DWORD
endif ; FEATURE_IMPLICIT_TLS
EXTERN _VarargPInvokeStubWorker@12:PROC
EXTERN _GenericPInvokeCalliStubWorker@12:PROC
; To debug that LastThrownObjectException really is EXCEPTION_COMPLUS
ifdef TRACK_CXX_EXCEPTION_CODE_HACK
EXTERN __imp____CxxFrameHandler:PROC
endif
EXTERN _GetThread@0:PROC
EXTERN _GetAppDomain@0:PROC
ifdef MDA_SUPPORTED
EXTERN _PInvokeStackImbalanceWorker@8:PROC
endif
ifndef FEATURE_CORECLR
EXTERN _CopyCtorCallStubWorker@4:PROC
endif
EXTERN _PreStubWorker@8:PROC
ifdef FEATURE_COMINTEROP
EXTERN _CLRToCOMWorker@8:PROC
endif
ifdef FEATURE_REMOTING
EXTERN _TransparentProxyStubWorker@8:PROC
endif
ifdef FEATURE_PREJIT
EXTERN _ExternalMethodFixupWorker@16:PROC
EXTERN _VirtualMethodFixupWorker@8:PROC
EXTERN _StubDispatchFixupWorker@16:PROC
endif
ifdef FEATURE_COMINTEROP
EXTERN _ComPreStubWorker@8:PROC
endif
ifdef FEATURE_READYTORUN
EXTERN _DynamicHelperWorker@20:PROC
endif
ifdef FEATURE_REMOTING
EXTERN _InContextTPQuickDispatchAsmStub@0:PROC
endif
EXTERN @JIT_InternalThrow@4:PROC
EXTERN @ProfileEnter@8:PROC
EXTERN @ProfileLeave@8:PROC
EXTERN @ProfileTailcall@8:PROC
UNREFERENCED macro arg
local unref
unref equ size arg
endm
FASTCALL_FUNC macro FuncName,cbArgs
FuncNameReal EQU @&FuncName&@&cbArgs
FuncNameReal proc public
endm
FASTCALL_ENDFUNC macro
FuncNameReal endp
endm
ifdef FEATURE_COMINTEROP
ifdef _DEBUG
CPFH_STACK_SIZE equ SIZEOF_FrameHandlerExRecord + STACK_OVERWRITE_BARRIER_SIZE*4
else ; _DEBUG
CPFH_STACK_SIZE equ SIZEOF_FrameHandlerExRecord
endif ; _DEBUG
PUSH_CPFH_FOR_COM macro trashReg, pFrameBaseReg, pFrameOffset
;
; Setup the FrameHandlerExRecord
;
push dword ptr [pFrameBaseReg + pFrameOffset]
push _COMPlusFrameHandlerRevCom
mov trashReg, fs:[0]
push trashReg
mov fs:[0], esp
ifdef _DEBUG
mov trashReg, STACK_OVERWRITE_BARRIER_SIZE
@@:
push STACK_OVERWRITE_BARRIER_VALUE
dec trashReg
jnz @B
endif ; _DEBUG
endm ; PUSH_CPFH_FOR_COM
POP_CPFH_FOR_COM macro trashReg
;
; Unlink FrameHandlerExRecord from FS:0 chain
;
ifdef _DEBUG
add esp, STACK_OVERWRITE_BARRIER_SIZE*4
endif
mov trashReg, [esp + OFFSETOF__FrameHandlerExRecord__m_ExReg__Next]
mov fs:[0], trashReg
add esp, SIZEOF_FrameHandlerExRecord
endm ; POP_CPFH_FOR_COM
endif ; FEATURE_COMINTEROP
;
; FramedMethodFrame prolog
;
STUB_PROLOG macro
; push ebp-frame
push ebp
mov ebp,esp
; save CalleeSavedRegisters
push ebx
push esi
push edi
; push ArgumentRegisters
push ecx
push edx
endm
;
; FramedMethodFrame epilog
;
STUB_EPILOG macro
; pop ArgumentRegisters
pop edx
pop ecx
; pop CalleeSavedRegisters
pop edi
pop esi
pop ebx
pop ebp
endm
;
; FramedMethodFrame epilog
;
STUB_EPILOG_RETURN macro
; pop ArgumentRegisters
add esp, 8
; pop CalleeSavedRegisters
pop edi
pop esi
pop ebx
pop ebp
endm
STUB_PROLOG_2_HIDDEN_ARGS macro
;
; The stub arguments are where we want to setup the TransitionBlock. We will
; setup the TransitionBlock later once we can trash them
;
; push ebp-frame
; push ebp
; mov ebp,esp
; save CalleeSavedRegisters
; push ebx
push esi
push edi
; push ArgumentRegisters
push ecx
push edx
mov ecx, [esp + 4*4]
mov edx, [esp + 5*4]
; Setup up proper EBP frame now that the stub arguments can be trashed
mov [esp + 4*4],ebx
mov [esp + 5*4],ebp
lea ebp, [esp + 5*4]
endm
ResetCurrentContext PROC stdcall public
LOCAL ctrlWord:WORD
; Clear the direction flag (used for rep instructions)
cld
fnstcw ctrlWord
fninit ; reset FPU
and ctrlWord, 0f00h ; preserve precision and rounding control
or ctrlWord, 007fh ; mask all exceptions
fldcw ctrlWord ; preserve precision control
RET
ResetCurrentContext ENDP
;Incoming:
; ESP+4: Pointer to buffer to which FPU state should be saved
_CaptureFPUContext@4 PROC public
mov ecx, [esp+4]
fnstenv [ecx]
retn 4
_CaptureFPUContext@4 ENDP
; Incoming:
; ESP+4: Pointer to buffer from which FPU state should be restored
_RestoreFPUContext@4 PROC public
mov ecx, [esp+4]
fldenv [ecx]
retn 4
_RestoreFPUContext@4 ENDP
ifndef FEATURE_CORECLR
ifdef _DEBUG
; For C++ exceptions, we desperately need to know the SEH code. This allows us to properly
; distinguish managed exceptions from C++ exceptions from standard SEH like hard stack overflow.
; We do this by providing our own handler that squirrels away the exception code and then
; defers to the C++ service. Fortunately, two symbols exist for the C++ symbol.
___CxxFrameHandler3 PROC public
; We don't know what arguments are passed to us (except for the first arg on stack)
; It turns out that EAX is part of the non-standard calling convention of this
; function.
push eax
push edx
cmp dword ptr [_gThreadTLSIndex], -1
je Chain ; CLR is not initialized yet
call _GetThread@0
test eax, eax ; not a managed thread
jz Chain
mov edx, [esp + 0ch] ; grab the first argument
mov edx, [edx] ; grab the SEH exception code
mov dword ptr [eax + Thread_m_LastCxxSEHExceptionCode], edx
Chain:
pop edx
; [esp] contains the value of EAX we must restore. We would like
; [esp] to contain the address of the real imported CxxFrameHandler
; so we can chain to it.
mov eax, [__imp____CxxFrameHandler]
mov eax, [eax]
xchg [esp], eax
ret
___CxxFrameHandler3 ENDP
endif ; _DEBUG
endif ; FEATURE_CORECLR
; Register CLR exception handlers defined on the C++ side with SAFESEH.
; Note that these directives must be in a file that defines symbols that will be used during linking,
; otherwise it's possible that the resulting .obj will completly be ignored by the linker and these
; directives will have no effect.
COMPlusFrameHandler proto c
.safeseh COMPlusFrameHandler
COMPlusNestedExceptionHandler proto c
.safeseh COMPlusNestedExceptionHandler
FastNExportExceptHandler proto c
.safeseh FastNExportExceptHandler
UMThunkPrestubHandler proto c
.safeseh UMThunkPrestubHandler
ifdef FEATURE_COMINTEROP
COMPlusFrameHandlerRevCom proto c
.safeseh COMPlusFrameHandlerRevCom
endif
; Note that RtlUnwind trashes EBX, ESI and EDI, so this wrapper preserves them
CallRtlUnwind PROC stdcall public USES ebx esi edi, pEstablisherFrame :DWORD, callback :DWORD, pExceptionRecord :DWORD, retVal :DWORD
push retVal
push pExceptionRecord
push callback
push pEstablisherFrame
call dword ptr [__imp__RtlUnwind@16]
; return 1
push 1
pop eax
RET
CallRtlUnwind ENDP
_ResumeAtJitEHHelper@4 PROC public
mov edx, [esp+4] ; edx = pContext (EHContext*)
mov ebx, [edx+EHContext_Ebx]
mov esi, [edx+EHContext_Esi]
mov edi, [edx+EHContext_Edi]
mov ebp, [edx+EHContext_Ebp]
mov ecx, [edx+EHContext_Esp]
mov eax, [edx+EHContext_Eip]
mov [ecx-4], eax
mov eax, [edx+EHContext_Eax]
mov [ecx-8], eax
mov eax, [edx+EHContext_Ecx]
mov [ecx-0Ch], eax
mov eax, [edx+EHContext_Edx]
mov [ecx-10h], eax
lea esp, [ecx-10h]
pop edx
pop ecx
pop eax
ret
_ResumeAtJitEHHelper@4 ENDP
; int __stdcall CallJitEHFilterHelper(size_t *pShadowSP, EHContext *pContext);
; on entry, only the pContext->Esp, Ebx, Esi, Edi, Ebp, and Eip are initialized
_CallJitEHFilterHelper@8 PROC public
push ebp
mov ebp, esp
push ebx
push esi
push edi
pShadowSP equ [ebp+8]
pContext equ [ebp+12]
mov eax, pShadowSP ; Write esp-4 to the shadowSP slot
test eax, eax
jz DONE_SHADOWSP_FILTER
mov ebx, esp
sub ebx, 4
or ebx, SHADOW_SP_IN_FILTER_ASM
mov [eax], ebx
DONE_SHADOWSP_FILTER:
mov edx, [pContext]
mov eax, [edx+EHContext_Eax]
mov ebx, [edx+EHContext_Ebx]
mov esi, [edx+EHContext_Esi]
mov edi, [edx+EHContext_Edi]
mov ebp, [edx+EHContext_Ebp]
call dword ptr [edx+EHContext_Eip]
ifdef _DEBUG
nop ; Indicate that it is OK to call managed code directly from here
endif
pop edi
pop esi
pop ebx
pop ebp ; don't use 'leave' here, as ebp as been trashed
retn 8
_CallJitEHFilterHelper@8 ENDP
; void __stdcall CallJITEHFinallyHelper(size_t *pShadowSP, EHContext *pContext);
; on entry, only the pContext->Esp, Ebx, Esi, Edi, Ebp, and Eip are initialized
_CallJitEHFinallyHelper@8 PROC public
push ebp
mov ebp, esp
push ebx
push esi
push edi
pShadowSP equ [ebp+8]
pContext equ [ebp+12]
mov eax, pShadowSP ; Write esp-4 to the shadowSP slot
test eax, eax
jz DONE_SHADOWSP_FINALLY
mov ebx, esp
sub ebx, 4
mov [eax], ebx
DONE_SHADOWSP_FINALLY:
mov edx, [pContext]
mov eax, [edx+EHContext_Eax]
mov ebx, [edx+EHContext_Ebx]
mov esi, [edx+EHContext_Esi]
mov edi, [edx+EHContext_Edi]
mov ebp, [edx+EHContext_Ebp]
call dword ptr [edx+EHContext_Eip]
ifdef _DEBUG
nop ; Indicate that it is OK to call managed code directly from here
endif
; Reflect the changes to the context and only update non-volatile registers.
; This will be used later to update REGDISPLAY
mov edx, [esp+12+12]
mov [edx+EHContext_Ebx], ebx
mov [edx+EHContext_Esi], esi
mov [edx+EHContext_Edi], edi
mov [edx+EHContext_Ebp], ebp
pop edi
pop esi
pop ebx
pop ebp ; don't use 'leave' here, as ebp as been trashed
retn 8
_CallJitEHFinallyHelper@8 ENDP
_GetSpecificCpuTypeAsm@0 PROC public
push ebx ; ebx is trashed by the cpuid calls
; See if the chip supports CPUID
pushfd
pop ecx ; Get the EFLAGS
mov eax, ecx ; Save for later testing
xor ecx, 200000h ; Invert the ID bit.
push ecx
popfd ; Save the updated flags.
pushfd
pop ecx ; Retrieve the updated flags
xor ecx, eax ; Test if it actually changed (bit set means yes)
push eax
popfd ; Restore the flags
test ecx, 200000h
jz Assume486
xor eax, eax
cpuid
test eax, eax
jz Assume486 ; brif CPUID1 not allowed
mov eax, 1
cpuid
; filter out everything except family and model
; Note that some multi-procs have different stepping number for each proc
and eax, 0ff0h
jmp CpuTypeDone
Assume486:
mov eax, 0400h ; report 486
CpuTypeDone:
pop ebx
retn
_GetSpecificCpuTypeAsm@0 ENDP
; DWORD __stdcall GetSpecificCpuFeaturesAsm(DWORD *pInfo);
_GetSpecificCpuFeaturesAsm@4 PROC public
push ebx ; ebx is trashed by the cpuid calls
; See if the chip supports CPUID
pushfd
pop ecx ; Get the EFLAGS
mov eax, ecx ; Save for later testing
xor ecx, 200000h ; Invert the ID bit.
push ecx
popfd ; Save the updated flags.
pushfd
pop ecx ; Retrieve the updated flags
xor ecx, eax ; Test if it actually changed (bit set means yes)
push eax
popfd ; Restore the flags
test ecx, 200000h
jz CpuFeaturesFail
xor eax, eax
cpuid
test eax, eax
jz CpuFeaturesDone ; br if CPUID1 not allowed
mov eax, 1
cpuid
mov eax, edx ; return all feature flags
mov edx, [esp+8]
test edx, edx
jz CpuFeaturesDone
mov [edx],ebx ; return additional useful information
jmp CpuFeaturesDone
CpuFeaturesFail:
xor eax, eax ; Nothing to report
CpuFeaturesDone:
pop ebx
retn 4
_GetSpecificCpuFeaturesAsm@4 ENDP
;-----------------------------------------------------------------------
; The out-of-line portion of the code to enable preemptive GC.
; After the work is done, the code jumps back to the "pRejoinPoint"
; which should be emitted right after the inline part is generated.
;
; Assumptions:
; ebx = Thread
; Preserves
; all registers except ecx.
;
;-----------------------------------------------------------------------
_StubRareEnable proc public
push eax
push edx
push ebx
call _StubRareEnableWorker@4
pop edx
pop eax
retn
_StubRareEnable ENDP
ifdef FEATURE_COMINTEROP
_StubRareDisableHR proc public
push edx
push ebx ; Thread
call _StubRareDisableHRWorker@4
pop edx
retn
_StubRareDisableHR ENDP
endif ; FEATURE_COMINTEROP
_StubRareDisableTHROW proc public
push eax
push edx
push ebx ; Thread
call _StubRareDisableTHROWWorker@4
pop edx
pop eax
retn
_StubRareDisableTHROW endp
ifdef FEATURE_MIXEDMODE
; VOID __stdcall IJWNOADThunkJumpTarget(void);
; This routine is used by the IJWNOADThunk to determine the callsite of the domain-specific stub to call.
_IJWNOADThunkJumpTarget@0 proc public
push ebp
mov ebp, esp
; EAX contains IJWNOADThunk*
; Must retain ebx, ecx, edx, esi, edi.
; save ebx - holds the IJWNOADThunk*
; save ecx - holds the current AppDomain ID.
; save edx - holds the cached AppDomain ID.
push ebx
push ecx
; put the IJWNOADThunk into ebx for safe keeping
mov ebx, eax
; get thread - assumes registers are preserved
call _GetThread@0
; if thread is null, go down un-optimized path
test eax,eax
jz cachemiss
; get current domain - assumes registers are preserved
call _GetAppDomain@0
; if domain is null, go down un-optimized path
test eax,eax
jz cachemiss
; get the current appdomain id
mov ecx, [eax + AppDomain__m_dwId]
; test it against each cache location
mov eax, ebx
add eax, IJWNOADThunk__m_cache
cmp ecx, [eax]
je cachehit
add eax, IJWNOADThunk__NextCacheOffset
cmp ecx, [eax]
je cachehit
add eax, IJWNOADThunk__NextCacheOffset
cmp ecx, [eax]
je cachehit
add eax, IJWNOADThunk__NextCacheOffset
cmp ecx, [eax]
je cachehit
cachemiss:
; save extra registers
push edx
push esi
push edi
; call unoptimized path
push ebx ; only arg is IJWNOADThunk*
call _IJWNOADThunkJumpTargetHelper@4
; restore extra registers
pop edi
pop esi
pop edx
; jump back up to the epilog
jmp complete
cachehit:
; found a matching ADID, get the code addr.
mov eax, [eax + IJWNOADThunk__CodeAddrOffsetFromADID]
; if the callsite is null, go down the un-optimized path
test eax, eax
jz cachemiss
complete:
; restore regs
pop ecx
pop ebx
mov esp, ebp
pop ebp
; Jump to callsite
jmp eax
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_IJWNOADThunkJumpTarget@0 endp
endif
InternalExceptionWorker proc public
pop edx ; recover RETADDR
add esp, eax ; release caller's args
push edx ; restore RETADDR
jmp @JIT_InternalThrow@4
InternalExceptionWorker endp
; EAX -> number of caller arg bytes on the stack that we must remove before going
; to the throw helper, which assumes the stack is clean.
_ArrayOpStubNullException proc public
; kFactorReg and kTotalReg could not have been modified, but let's pop
; them anyway for consistency and to avoid future bugs.
pop esi
pop edi
mov ecx, CORINFO_NullReferenceException_ASM
jmp InternalExceptionWorker
_ArrayOpStubNullException endp
; EAX -> number of caller arg bytes on the stack that we must remove before going
; to the throw helper, which assumes the stack is clean.
_ArrayOpStubRangeException proc public
; kFactorReg and kTotalReg could not have been modified, but let's pop
; them anyway for consistency and to avoid future bugs.
pop esi
pop edi
mov ecx, CORINFO_IndexOutOfRangeException_ASM
jmp InternalExceptionWorker
_ArrayOpStubRangeException endp
; EAX -> number of caller arg bytes on the stack that we must remove before going
; to the throw helper, which assumes the stack is clean.
_ArrayOpStubTypeMismatchException proc public
; kFactorReg and kTotalReg could not have been modified, but let's pop
; them anyway for consistency and to avoid future bugs.
pop esi
pop edi
mov ecx, CORINFO_ArrayTypeMismatchException_ASM
jmp InternalExceptionWorker
_ArrayOpStubTypeMismatchException endp
;------------------------------------------------------------------------------
; This helper routine enregisters the appropriate arguments and makes the
; actual call.
;------------------------------------------------------------------------------
; void STDCALL CallDescrWorkerInternal(CallDescrWorkerParams * pParams)
CallDescrWorkerInternal PROC stdcall public USES EBX,
pParams: DWORD
mov ebx, pParams
mov ecx, [ebx+CallDescrData__numStackSlots]
mov eax, [ebx+CallDescrData__pSrc] ; copy the stack
test ecx, ecx
jz donestack
lea eax, [eax+4*ecx-4] ; last argument
push dword ptr [eax]
dec ecx
jz donestack
sub eax, 4
push dword ptr [eax]
dec ecx
jz donestack
stackloop:
sub eax, 4
push dword ptr [eax]
dec ecx
jnz stackloop
donestack:
; now we must push each field of the ArgumentRegister structure
mov eax, [ebx+CallDescrData__pArgumentRegisters]
mov edx, dword ptr [eax]
mov ecx, dword ptr [eax+4]
call [ebx+CallDescrData__pTarget]
ifdef _DEBUG
nop ; This is a tag that we use in an assert. Fcalls expect to
; be called from Jitted code or from certain blessed call sites like
; this one. (See HelperMethodFrame::InsureInit)
endif
; Save FP return value if necessary
mov ecx, [ebx+CallDescrData__fpReturnSize]
cmp ecx, 0
je ReturnsInt
cmp ecx, 4
je ReturnsFloat
cmp ecx, 8
je ReturnsDouble
; unexpected
jmp Epilog
ReturnsInt:
mov [ebx+CallDescrData__returnValue], eax
mov [ebx+CallDescrData__returnValue+4], edx
Epilog:
RET
ReturnsFloat:
fstp dword ptr [ebx+CallDescrData__returnValue] ; Spill the Float return value
jmp Epilog
ReturnsDouble:
fstp qword ptr [ebx+CallDescrData__returnValue] ; Spill the Double return value
jmp Epilog
CallDescrWorkerInternal endp
ifdef _DEBUG
; int __fastcall HelperMethodFrameRestoreState(HelperMethodFrame*, struct MachState *)
FASTCALL_FUNC HelperMethodFrameRestoreState,8
mov eax, edx ; eax = MachState*
else
; int __fastcall HelperMethodFrameRestoreState(struct MachState *)
FASTCALL_FUNC HelperMethodFrameRestoreState,4
mov eax, ecx ; eax = MachState*
endif
; restore the registers from the m_MachState stucture. Note that
; we only do this for register that where not saved on the stack
; at the time the machine state snapshot was taken.
cmp [eax+MachState__pRetAddr], 0
ifdef _DEBUG
jnz noConfirm
push ebp
push ebx
push edi
push esi
push ecx ; HelperFrame*
call _HelperMethodFrameConfirmState@20
; on return, eax = MachState*
cmp [eax+MachState__pRetAddr], 0
noConfirm:
endif
jz doRet
lea edx, [eax+MachState__esi] ; Did we have to spill ESI
cmp [eax+MachState__pEsi], edx
jnz SkipESI
mov esi, [edx] ; Then restore it
SkipESI:
lea edx, [eax+MachState__edi] ; Did we have to spill EDI
cmp [eax+MachState__pEdi], edx
jnz SkipEDI
mov edi, [edx] ; Then restore it
SkipEDI:
lea edx, [eax+MachState__ebx] ; Did we have to spill EBX
cmp [eax+MachState__pEbx], edx
jnz SkipEBX
mov ebx, [edx] ; Then restore it
SkipEBX:
lea edx, [eax+MachState__ebp] ; Did we have to spill EBP
cmp [eax+MachState__pEbp], edx
jnz SkipEBP
mov ebp, [edx] ; Then restore it
SkipEBP:
doRet:
xor eax, eax
retn
FASTCALL_ENDFUNC HelperMethodFrameRestoreState
ifndef FEATURE_IMPLICIT_TLS
;---------------------------------------------------------------------------
; Portable GetThread() function: used if no platform-specific optimizations apply.
; This is in assembly code because we count on edx not getting trashed on calls
; to this function.
;---------------------------------------------------------------------------
; Thread* __stdcall GetThreadGeneric(void);
GetThreadGeneric PROC stdcall public USES ecx edx
ifdef _DEBUG
cmp dword ptr [_gThreadTLSIndex], -1
jnz @F
int 3
@@:
endif
ifdef ENABLE_GET_THREAD_GENERIC_FULL_CHECK
; non-PAL, debug-only GetThreadGeneric should defer to GetThreadGenericFullCheck
; to do extra contract enforcement. (See GetThreadGenericFullCheck for details.)
; This code is intentionally not added to asmhelper.s, as this enforcement is only
; implemented for non-PAL builds.
call GetThreadGenericFullCheck
else
push dword ptr [_gThreadTLSIndex]
call dword ptr [__imp__TlsGetValue@4]
endif
ret
GetThreadGeneric ENDP
;---------------------------------------------------------------------------
; Portable GetAppdomain() function: used if no platform-specific optimizations apply.
; This is in assembly code because we count on edx not getting trashed on calls
; to this function.
;---------------------------------------------------------------------------
; Appdomain* __stdcall GetAppDomainGeneric(void);
GetAppDomainGeneric PROC stdcall public USES ecx edx
ifdef _DEBUG
cmp dword ptr [_gAppDomainTLSIndex], -1
jnz @F
int 3
@@:
endif
push dword ptr [_gAppDomainTLSIndex]
call dword ptr [__imp__TlsGetValue@4]
ret
GetAppDomainGeneric ENDP
endif
ifdef FEATURE_HIJACK
; A JITted method's return address was hijacked to return to us here.
; VOID OnHijackTripThread()
OnHijackTripThread PROC stdcall public
; Don't fiddle with this unless you change HijackFrame::UpdateRegDisplay
; and HijackArgs
push eax ; make room for the real return address (Eip)
push ebp
push eax
push ecx
push edx
push ebx
push esi
push edi
; unused space for floating point state
sub esp,12
push esp
call _OnHijackWorker@4
; unused space for floating point state
add esp,12
pop edi
pop esi
pop ebx
pop edx
pop ecx
pop eax
pop ebp
retn ; return to the correct place, adjusted by our caller
OnHijackTripThread ENDP
; VOID OnHijackFPTripThread()
OnHijackFPTripThread PROC stdcall public
; Don't fiddle with this unless you change HijackFrame::UpdateRegDisplay
; and HijackArgs
push eax ; make room for the real return address (Eip)
push ebp
push eax
push ecx
push edx
push ebx
push esi
push edi
sub esp,12
; save top of the floating point stack (there is return value passed in it)
; save full 10 bytes to avoid precision loss
fstp tbyte ptr [esp]
push esp
call _OnHijackWorker@4
; restore top of the floating point stack
fld tbyte ptr [esp]
add esp,12
pop edi
pop esi
pop ebx
pop edx
pop ecx
pop eax
pop ebp
retn ; return to the correct place, adjusted by our caller
OnHijackFPTripThread ENDP
endif ; FEATURE_HIJACK
;==========================================================================
; This function is reached only via the embedded ImportThunkGlue code inside
; an NDirectMethodDesc. It's purpose is to load the DLL associated with an
; N/Direct method, then backpatch the DLL target into the methoddesc.
;
; Initial state:
;
; Preemptive GC is *enabled*: we are actually in an unmanaged state.
;
;
; [esp+...] - The *unmanaged* parameters to the DLL target.
; [esp+4] - Return address back into the JIT'ted code that made
; the DLL call.
; [esp] - Contains the "return address." Because we got here
; thru a call embedded inside a MD, this "return address"
; gives us an easy to way to find the MD (which was the
; whole purpose of the embedded call manuever.)
;
;
;
;==========================================================================
_NDirectImportThunk@0 proc public
; Preserve argument registers
push ecx
push edx
; Invoke the function that does the real work.
push eax
call _NDirectImportWorker@4
; Restore argument registers
pop edx
pop ecx
; If we got back from NDirectImportWorker, the MD has been successfully
; linked and "eax" contains the DLL target. Proceed to execute the
; original DLL call.
jmp eax ; Jump to DLL target
_NDirectImportThunk@0 endp
;==========================================================================
; The call in fixup precode initally points to this function.
; The pupose of this function is to load the MethodDesc and forward the call the prestub.
_PrecodeFixupThunk@0 proc public
pop eax ; Pop the return address. It points right after the call instruction in the precode.
push esi
push edi
; Inline computation done by FixupPrecode::GetMethodDesc()
movzx esi,byte ptr [eax+2] ; m_PrecodeChunkIndex
movzx edi,byte ptr [eax+1] ; m_MethodDescChunkIndex
mov eax,dword ptr [eax+esi*8+3]
lea eax,[eax+edi*4]
pop edi
pop esi
jmp _ThePreStub@0
_PrecodeFixupThunk@0 endp
; LPVOID __stdcall CTPMethodTable__CallTargetHelper2(
; const void *pTarget,
; LPVOID pvFirst,
; LPVOID pvSecond)
CTPMethodTable__CallTargetHelper2 proc stdcall public,
pTarget : DWORD,
pvFirst : DWORD,
pvSecond : DWORD
mov ecx, pvFirst
mov edx, pvSecond
call pTarget
ifdef _DEBUG
nop ; Mark this as a special call site that can
; directly call unmanaged code
endif
ret
CTPMethodTable__CallTargetHelper2 endp
; LPVOID __stdcall CTPMethodTable__CallTargetHelper3(
; const void *pTarget,
; LPVOID pvFirst,
; LPVOID pvSecond,
; LPVOID pvThird)
CTPMethodTable__CallTargetHelper3 proc stdcall public,
pTarget : DWORD,
pvFirst : DWORD,
pvSecond : DWORD,
pvThird : DWORD
push pvThird
mov ecx, pvFirst
mov edx, pvSecond
call pTarget
ifdef _DEBUG
nop ; Mark this as a special call site that can
; directly call unmanaged code
endif
ret
CTPMethodTable__CallTargetHelper3 endp
; void __stdcall setFPReturn(int fpSize, INT64 retVal)
_setFPReturn@12 proc public
mov ecx, [esp+4]
; leave the return value in eax:edx if it is not the floating point case
mov eax, [esp+8]
mov edx, [esp+12]
cmp ecx, 4
jz setFPReturn4
cmp ecx, 8
jnz setFPReturnNot8
fld qword ptr [esp+8]
setFPReturnNot8:
retn 12
setFPReturn4:
fld dword ptr [esp+8]
retn 12
_setFPReturn@12 endp
; void __stdcall getFPReturn(int fpSize, INT64 *pretVal)
_getFPReturn@8 proc public
mov ecx, [esp+4]
mov eax, [esp+8]
cmp ecx, 4
jz getFPReturn4
cmp ecx, 8
jnz getFPReturnNot8
fstp qword ptr [eax]
getFPReturnNot8:
retn 8
getFPReturn4:
fstp dword ptr [eax]
retn 8
_getFPReturn@8 endp
; void __stdcall UM2MThunk_WrapperHelper(void *pThunkArgs,
; int argLen,
; void *pAddr,
; UMEntryThunk *pEntryThunk,
; Thread *pThread)
UM2MThunk_WrapperHelper proc stdcall public,
pThunkArgs : DWORD,
argLen : DWORD,
pAddr : DWORD,
pEntryThunk : DWORD,
pThread : DWORD
UNREFERENCED argLen
push ebx
mov eax, pEntryThunk
mov ecx, pThread
mov ebx, pThunkArgs
call pAddr
pop ebx
ret
UM2MThunk_WrapperHelper endp
; VOID __cdecl UMThunkStubRareDisable()
;
; @todo: this is very similar to StubRareDisable
;
_UMThunkStubRareDisable proc public
push eax
push ecx
push eax ; Push the UMEntryThunk
push ecx ; Push thread
call _UMThunkStubRareDisableWorker@8
pop ecx
pop eax
retn
_UMThunkStubRareDisable endp
;+----------------------------------------------------------------------------
;
; Method: CRemotingServices::CheckForContextMatch public
;
; Synopsis: This code generates a check to see if the current context and
; the context of the proxy match.
;
;+----------------------------------------------------------------------------
;
; returns zero if contexts match
; returns non-zero if contexts do not match
;
; UINT_PTR __stdcall CRemotingServices__CheckForContextMatch(Object* pStubData)
ifdef FEATURE_REMOTING
_CRemotingServices__CheckForContextMatch@4 proc public
push ebx ; spill ebx
mov ebx, [eax+4] ; Get the internal context id by unboxing
; the stub data
call _GetThread@0 ; Get the current thread, assumes that the
; registers are preserved
mov eax, [eax+Thread_m_Context] ; Get the current context from the
; thread
sub eax, ebx ; Get the pointer to the context from the
; proxy and compare with the current context
pop ebx ; restore the value of ebx
retn
_CRemotingServices__CheckForContextMatch@4 endp
endif ; FEATURE_REMOTING
;+----------------------------------------------------------------------------
;
; Method: CRemotingServices::DispatchInterfaceCall public
;
; Synopsis:
; Push that method desc on the stack and jump to the
; transparent proxy stub to execute the call.
; WARNING!! This MethodDesc is not the methoddesc in the vtable
; of the object instead it is the methoddesc in the vtable of
; the interface class. Since we use the MethodDesc only to probe
; the stack via the signature of the method call we are safe.
; If we want to get any object vtable/class specific
; information this is not safe.
;
;
;+----------------------------------------------------------------------------
; void __stdcall CRemotingServices__DispatchInterfaceCall()
ifdef FEATURE_REMOTING
_CRemotingServices__DispatchInterfaceCall@0 proc public
; push MethodDesc* passed in eax by precode and forward to the worker
push eax
; NOTE: At this point the stack looks like
;
; esp---> saved MethodDesc of Interface method
; return addr of calling function
;
mov eax, [ecx + TransparentProxyObject___stubData]
call [ecx + TransparentProxyObject___stub]
ifdef _DEBUG
nop ; Mark this as a special call site that can directly
; call managed code
endif
test eax, eax
jnz CtxMismatch
jmp _InContextTPQuickDispatchAsmStub@0
CtxMismatch:
pop eax ; restore MethodDesc *
jmp _TransparentProxyStub_CrossContext@0 ; jump to slow TP stub
_CRemotingServices__DispatchInterfaceCall@0 endp
endif ; FEATURE_REMOTING
;+----------------------------------------------------------------------------
;
; Method: CRemotingServices::CallFieldGetter private
;
; Synopsis: Calls the field getter function (Object::__FieldGetter) in
; managed code by setting up the stack and calling the target
;
;
;+----------------------------------------------------------------------------
; void __stdcall CRemotingServices__CallFieldGetter(
; MethodDesc *pMD,
; LPVOID pThis,
; LPVOID pFirst,
; LPVOID pSecond,
; LPVOID pThird)
ifdef FEATURE_REMOTING
CRemotingServices__CallFieldGetter proc stdcall public,
pMD : DWORD,
pThis : DWORD,
pFirst : DWORD,
pSecond : DWORD,
pThird : DWORD
push [pSecond] ; push the second argument on the stack
push [pThird] ; push the third argument on the stack
mov ecx, [pThis] ; enregister pThis, the 'this' pointer
mov edx, [pFirst] ; enregister pFirst, the first argument
mov eax, [pMD] ; load MethodDesc of object::__FieldGetter
call _TransparentProxyStub_CrossContext@0 ; call the TP stub
ret
CRemotingServices__CallFieldGetter endp
endif ; FEATURE_REMOTING
;+----------------------------------------------------------------------------
;
; Method: CRemotingServices::CallFieldSetter private
;
; Synopsis: Calls the field setter function (Object::__FieldSetter) in
; managed code by setting up the stack and calling the target
;
;
;+----------------------------------------------------------------------------
; void __stdcall CRemotingServices__CallFieldSetter(
; MethodDesc *pMD,
; LPVOID pThis,
; LPVOID pFirst,
; LPVOID pSecond,
; LPVOID pThird)
ifdef FEATURE_REMOTING
CRemotingServices__CallFieldSetter proc stdcall public,
pMD : DWORD,
pThis : DWORD,
pFirst : DWORD,
pSecond : DWORD,
pThird : DWORD
push [pSecond] ; push the field name (second arg)
push [pThird] ; push the object (third arg) on the stack
mov ecx, [pThis] ; enregister pThis, the 'this' pointer
mov edx, [pFirst] ; enregister the first argument
mov eax, [pMD] ; load MethodDesc of object::__FieldGetter
call _TransparentProxyStub_CrossContext@0 ; call the TP stub
ret
CRemotingServices__CallFieldSetter endp
endif ; FEATURE_REMOTING
;+----------------------------------------------------------------------------
;
; Method: CTPMethodTable::GenericCheckForContextMatch private
;
; Synopsis: Calls the stub in the TP & returns TRUE if the contexts
; match, FALSE otherwise.
;
; Note: 1. Called during FieldSet/Get, used for proxy extensibility
;
;+----------------------------------------------------------------------------
; BOOL __stdcall CTPMethodTable__GenericCheckForContextMatch(Object* orTP)
ifdef FEATURE_REMOTING
CTPMethodTable__GenericCheckForContextMatch proc stdcall public uses ecx, tp : DWORD
mov ecx, [tp]
mov eax, [ecx + TransparentProxyObject___stubData]
call [ecx + TransparentProxyObject___stub]
ifdef _DEBUG
nop ; Mark this as a special call site that can directly
; call managed code
endif
test eax, eax
mov eax, 0
setz al
; NOTE: In the CheckForXXXMatch stubs (for URT ctx/ Ole32 ctx) eax is
; non-zero if contexts *do not* match & zero if they do.
ret
CTPMethodTable__GenericCheckForContextMatch endp
endif ; FEATURE_REMOTING
; void __stdcall JIT_ProfilerEnterLeaveTailcallStub(UINT_PTR ProfilerHandle)
_JIT_ProfilerEnterLeaveTailcallStub@4 proc public
; this function must preserve all registers, including scratch
retn 4
_JIT_ProfilerEnterLeaveTailcallStub@4 endp
;
; Used to get the current instruction pointer value
;
; UINT_PTR __stdcall GetCurrentIP(void);
_GetCurrentIP@0 proc public
mov eax, [esp]
retn
_GetCurrentIP@0 endp
; LPVOID __stdcall GetCurrentSP(void);
_GetCurrentSP@0 proc public
mov eax, esp
retn
_GetCurrentSP@0 endp
; void __stdcall ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID);
_ProfileEnterNaked@4 proc public
push esi
push edi
;
; Push in reverse order the fields of ProfilePlatformSpecificData
;
push dword ptr [esp+8] ; EIP of the managed code that we return to. -- struct ip field
push ebp ; Methods are always EBP framed
add [esp], 8 ; Skip past the return IP, straight to the stack args that were passed to our caller
; Skip past saved EBP value: 4 bytes
; - plus return address from caller's caller: 4 bytes
;
; Assuming Foo() calls Bar(), and Bar() calls ProfileEnterNake() as illustrated (stack
; grows up). We want to get what Foo() passed on the stack to Bar(), so we need to pass
; the return address from caller's caller which is Foo() in this example.
;
; ProfileEnterNaked()
; Bar()
; Foo()
;
; [ESP] is now the ESP of caller's caller pointing to the arguments to the caller.
push ecx ; -- struct ecx field
push edx ; -- struct edx field
push eax ; -- struct eax field
push 0 ; Create buffer space in the structure -- struct floatingPointValuePresent field
push 0 ; Create buffer space in the structure -- struct floatBuffer field
push 0 ; Create buffer space in the structure -- struct doubleBuffer2 field
push 0 ; Create buffer space in the structure -- struct doubleBuffer1 field
push 0 ; Create buffer space in the structure -- struct functionId field
mov edx, esp ; the address of the Platform structure
mov ecx, [esp+52]; The functionIDOrClientID parameter that was pushed to FunctionEnter
; Skip past ProfilePlatformSpecificData we pushed: 40 bytes
; - plus saved edi, esi : 8 bytes
; - plus return address from caller: 4 bytes
call @ProfileEnter@8
add esp, 20 ; Remove buffer space
pop eax
pop edx
pop ecx
add esp, 8 ; Remove buffer space
pop edi
pop esi
retn 4
_ProfileEnterNaked@4 endp
; void __stdcall ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID);
_ProfileLeaveNaked@4 proc public
push ecx ; We do not strictly need to save ECX, however
; emitNoGChelper(CORINFO_HELP_PROF_FCN_LEAVE) returns true in the JITcompiler
push edx ; Return value may be in EAX:EDX
;
; Push in reverse order the fields of ProfilePlatformSpecificData
;
push dword ptr [esp+8] ; EIP of the managed code that we return to. -- struct ip field
push ebp ; Methods are always EBP framed
add [esp], 8 ; Skip past the return IP, straight to the stack args that were passed to our caller
; Skip past saved EBP value: 4 bytes
; - plus return address from caller's caller: 4 bytes
;
; Assuming Foo() calls Bar(), and Bar() calls ProfileEnterNake() as illustrated (stack
; grows up). We want to get what Foo() passed on the stack to Bar(), so we need to pass
; the return address from caller's caller which is Foo() in this example.
;
; ProfileEnterNaked()
; Bar()
; Foo()
;
; [ESP] is now the ESP of caller's caller pointing to the arguments to the caller.
push ecx ; -- struct ecx field
push edx ; -- struct edx field
push eax ; -- struct eax field
; Check if we need to save off any floating point registers
fstsw ax
and ax, 3800h ; Check the top-of-fp-stack bits
cmp ax, 0 ; If non-zero, we have something to save
jnz SaveFPReg
push 0 ; Create buffer space in the structure -- struct floatingPointValuePresent field
push 0 ; Create buffer space in the structure -- struct floatBuffer field
push 0 ; Create buffer space in the structure -- struct doubleBuffer2 field
push 0 ; Create buffer space in the structure -- struct doubleBuffer1 field
jmp Continue
SaveFPReg:
push 1 ; mark that a float value is present -- struct floatingPointValuePresent field
sub esp, 4 ; Make room for the FP value
fst dword ptr [esp] ; Copy the FP value to the buffer as a float -- struct floatBuffer field
sub esp, 8 ; Make room for the FP value
fstp qword ptr [esp] ; Copy FP values to the buffer as a double -- struct doubleBuffer1 and doubleBuffer2 fields
Continue:
push 0 ; Create buffer space in the structure -- struct functionId field
mov edx, esp ; the address of the Platform structure
mov ecx, [esp+52]; The clientData that was pushed to FunctionEnter
; Skip past ProfilePlatformSpecificData we pushed: 40 bytes
; - plus saved edx, ecx : 8 bytes
; - plus return address from caller: 4 bytes
call @ProfileLeave@8
;
; Now see if we have to restore and floating point registers
;
cmp [esp + 16], 0
jz NoRestore
fld qword ptr [esp + 4]
NoRestore:
add esp, 20 ; Remove buffer space
pop eax
add esp, 16 ; Remove buffer space
pop edx
pop ecx
retn 4
_ProfileLeaveNaked@4 endp
; void __stdcall ProfileTailcallNaked(FunctionIDOrClientID functionIDOrClientID);
_ProfileTailcallNaked@4 proc public
push ecx
push edx
;
; Push in reverse order the fields of ProfilePlatformSpecificData
;
push dword ptr [esp+8] ; EIP of the managed code that we return to. -- struct ip field
push ebp ; Methods are always EBP framed
add [esp], 8 ; Skip past the return IP, straight to the stack args that were passed to our caller
; Skip past saved EBP value: 4 bytes
; - plus return address from caller's caller: 4 bytes
;
; Assuming Foo() calls Bar(), and Bar() calls ProfileEnterNake() as illustrated (stack
; grows up). We want to get what Foo() passed on the stack to Bar(), so we need to pass
; the return address from caller's caller which is Foo() in this example.
;
; ProfileEnterNaked()
; Bar()
; Foo()
;
; [ESP] is now the ESP of caller's caller pointing to the arguments to the caller.
push ecx ; -- struct ecx field
push edx ; -- struct edx field
push eax ; -- struct eax field
push 0 ; Create buffer space in the structure -- struct floatingPointValuePresent field
push 0 ; Create buffer space in the structure -- struct floatBuffer field
push 0 ; Create buffer space in the structure -- struct doubleBuffer2 field
push 0 ; Create buffer space in the structure -- struct doubleBuffer1 field
push 0 ; Create buffer space in the structure -- struct functionId field
mov edx, esp ; the address of the Platform structure
mov ecx, [esp+52]; The clientData that was pushed to FunctionEnter
; Skip past ProfilePlatformSpecificData we pushed: 40 bytes
; - plus saved edx, ecx : 8 bytes
; - plus return address from caller: 4 bytes
call @ProfileTailcall@8
add esp, 40 ; Remove buffer space
pop edx
pop ecx
retn 4
_ProfileTailcallNaked@4 endp
;==========================================================================
; Invoked for vararg forward P/Invoke calls as a stub.
; Except for secret return buffer, arguments come on the stack so EDX is available as scratch.
; EAX - the NDirectMethodDesc
; ECX - may be return buffer address
; [ESP + 4] - the VASigCookie
;
_VarargPInvokeStub@0 proc public
; EDX <- VASigCookie
mov edx, [esp + 4] ; skip retaddr
mov edx, [edx + VASigCookie__StubOffset]
test edx, edx
jz GoCallVarargWorker
; ---------------------------------------
; EAX contains MD ptr for the IL stub
jmp edx
GoCallVarargWorker:
;
; MD ptr in EAX, VASigCookie ptr at [esp+4]
;
STUB_PROLOG
mov esi, esp
; save pMD
push eax
push eax ; pMD
push dword ptr [esi + 4*7] ; pVaSigCookie
push esi ; pTransitionBlock
call _VarargPInvokeStubWorker@12
; restore pMD
pop eax
STUB_EPILOG
; jump back to the helper - this time it won't come back here as the stub already exists
jmp _VarargPInvokeStub@0
_VarargPInvokeStub@0 endp
;==========================================================================
; Invoked for marshaling-required unmanaged CALLI calls as a stub.
; EAX - the unmanaged target
; ECX, EDX - arguments
; [ESP + 4] - the VASigCookie
;
_GenericPInvokeCalliHelper@0 proc public
; save the target
push eax
; EAX <- VASigCookie
mov eax, [esp + 8] ; skip target and retaddr
mov eax, [eax + VASigCookie__StubOffset]
test eax, eax
jz GoCallCalliWorker
; ---------------------------------------
push eax
; stack layout at this point:
;
; | ... |
; | stack arguments | ESP + 16
; +----------------------+
; | VASigCookie* | ESP + 12
; +----------------------+
; | return address | ESP + 8
; +----------------------+
; | CALLI target address | ESP + 4
; +----------------------+
; | stub entry point | ESP + 0
; ------------------------
; remove VASigCookie from the stack
mov eax, [esp + 8]
mov [esp + 12], eax
; move stub entry point below the RA
mov eax, [esp]
mov [esp + 8], eax
; load EAX with the target address
pop eax
pop eax
; stack layout at this point:
;
; | ... |
; | stack arguments | ESP + 8
; +----------------------+
; | return address | ESP + 4
; +----------------------+
; | stub entry point | ESP + 0
; ------------------------
; CALLI target address is in EAX
ret
GoCallCalliWorker:
; the target is on the stack and will become m_Datum of PInvokeCalliFrame
; call the stub generating worker
pop eax
;
; target ptr in EAX, VASigCookie ptr in EDX
;
STUB_PROLOG
mov esi, esp
; save target
push eax
push eax ; unmanaged target
push dword ptr [esi + 4*7] ; pVaSigCookie (first stack argument)
push esi ; pTransitionBlock
call _GenericPInvokeCalliStubWorker@12
; restore target
pop eax
STUB_EPILOG
; jump back to the helper - this time it won't come back here as the stub already exists
jmp _GenericPInvokeCalliHelper@0
_GenericPInvokeCalliHelper@0 endp
ifdef MDA_SUPPORTED
;==========================================================================
; Invoked from on-the-fly generated stubs when the stack imbalance MDA is
; enabled. The common low-level work for both direct P/Invoke and unmanaged
; delegate P/Invoke happens here. PInvokeStackImbalanceWorker is where the
; actual imbalance check is implemented.
; [ESP + 4] - the StackImbalanceCookie
; [EBP + 8] - stack arguments (EBP frame pushed by the calling stub)
;
_PInvokeStackImbalanceHelper@0 proc public
; StackImbalanceCookie to EBX
push ebx
lea ebx, [esp + 8]
push esi
push edi
; copy stack args
mov edx, ecx
mov ecx, [ebx + StackImbalanceCookie__m_dwStackArgSize]
sub esp, ecx
shr ecx, 2
lea edi, [esp]
lea esi, [ebp + 8]
cld
rep movsd
; record pre-call ESP
mov [ebx + StackImbalanceCookie__m_dwSavedEsp], esp
; call the target (restore ECX in case it's a thiscall)
mov ecx, edx
call [ebx + StackImbalanceCookie__m_pTarget]
; record post-call ESP and restore ESP to pre-pushed state
mov ecx, esp
lea esp, [ebp - SIZEOF_StackImbalanceCookie - 16] ; 4 DWORDs and the cookie have been pushed
; save return value
push eax
push edx
sub esp, 12
.errnz (StackImbalanceCookie__HAS_FP_RETURN_VALUE AND 00ffffffh), HAS_FP_RETURN_VALUE has changed - update asm code
; save top of the floating point stack if the target has FP retval
test byte ptr [ebx + StackImbalanceCookie__m_callConv + 3], (StackImbalanceCookie__HAS_FP_RETURN_VALUE SHR 24)
jz noFPURetVal
fstp tbyte ptr [esp] ; save full 10 bytes to avoid precision loss
noFPURetVal:
; call PInvokeStackImbalanceWorker(StackImbalanceCookie *pSICookie, DWORD dwPostESP)
push ecx
push ebx
call _PInvokeStackImbalanceWorker@8
; restore return value
test byte ptr [ebx + StackImbalanceCookie__m_callConv + 3], (StackImbalanceCookie__HAS_FP_RETURN_VALUE SHR 24)
jz noFPURetValToRestore
fld tbyte ptr [esp]
noFPURetValToRestore:
add esp, 12
pop edx
pop eax
; restore registers
pop edi
pop esi
pop ebx
; EBP frame and original stack arguments will be removed by the caller
ret
_PInvokeStackImbalanceHelper@0 endp
endif ; MDA_SUPPORTED
ifdef FEATURE_COMINTEROP
;==========================================================================
; This is a fast alternative to CallDescr* tailored specifically for
; COM to CLR calls. Stack arguments don't come in a continuous buffer
; and secret argument can be passed in EAX.
;
; extern "C" ARG_SLOT __fastcall COMToCLRDispatchHelper(
; INT_PTR dwArgECX, ; ecx
; INT_PTR dwArgEDX, ; edx
; PCODE pTarget, ; [esp + 4]
; PCODE pSecretArg, ; [esp + 8]
; INT_PTR *pInputStack, ; [esp + c]
; WORD wOutputStackSlots, ; [esp +10]
; UINT16 *pOutputStackOffsets, ; [esp +14]
; Frame *pCurFrame); ; [esp +18]
FASTCALL_FUNC COMToCLRDispatchHelper, 32
; ecx: dwArgECX
; edx: dwArgEDX
offset_pTarget equ 4
offset_pSecretArg equ 8
offset_pInputStack equ 0Ch
offset_wOutputStackSlots equ 10h
offset_pOutputStackOffsets equ 14h
offset_pCurFrame equ 18h
movzx eax, word ptr [esp + offset_wOutputStackSlots]
test eax, eax
jnz CopyStackArgs
; There are no stack args to copy and ECX and EDX are already setup
; with the correct arguments for the callee, so we just have to
; push the CPFH and make the call.
PUSH_CPFH_FOR_COM eax, esp, offset_pCurFrame ; trashes eax
mov eax, [esp + offset_pSecretArg + CPFH_STACK_SIZE]
call [esp + offset_pTarget + CPFH_STACK_SIZE]
ifdef _DEBUG
nop ; This is a tag that we use in an assert.
endif
POP_CPFH_FOR_COM ecx ; trashes ecx
ret 18h
CopyStackArgs:
; eax: num stack slots
; ecx: dwArgECX
; edx: dwArgEDX
push ebp
mov ebp, esp
push ebx
push esi
push edi
ebpFrame_adjust equ 4h
ebp_offset_pCurFrame equ ebpFrame_adjust + offset_pCurFrame
PUSH_CPFH_FOR_COM ebx, ebp, ebp_offset_pCurFrame ; trashes ebx
mov edi, [ebp + ebpFrame_adjust + offset_pOutputStackOffsets]
mov esi, [ebp + ebpFrame_adjust + offset_pInputStack]
; eax: num stack slots
; ecx: dwArgECX
; edx: dwArgEDX
; edi: pOutputStackOffsets
; esi: pInputStack
CopyStackLoop:
dec eax
movzx ebx, word ptr [edi + 2 * eax] ; ebx <- input stack offset
push [esi + ebx] ; stack <- value on the input stack
jnz CopyStackLoop
; ECX and EDX are setup with the correct arguments for the callee,
; and we've copied the stack arguments over as well, so now it's
; time to make the call.
mov eax, [ebp + ebpFrame_adjust + offset_pSecretArg]
call [ebp + ebpFrame_adjust + offset_pTarget]
ifdef _DEBUG
nop ; This is a tag that we use in an assert.
endif
POP_CPFH_FOR_COM ecx ; trashes ecx
pop edi
pop esi
pop ebx
pop ebp
ret 18h
FASTCALL_ENDFUNC
endif ; FEATURE_COMINTEROP
ifndef FEATURE_CORECLR
;==========================================================================
; This is small stub whose purpose is to record current stack pointer and
; call CopyCtorCallStubWorker to invoke copy constructors and destructors
; as appropriate. This stub operates on arguments already pushed to the
; stack by JITted IL stub and must not create a new frame, i.e. it must tail
; call to the target for it to see the arguments that copy ctors have been
; called on.
;
_CopyCtorCallStub@0 proc public
; there may be an argument in ecx - save it
push ecx
; push pointer to arguments
lea edx, [esp + 8]
push edx
call _CopyCtorCallStubWorker@4
; restore ecx and tail call to the target
pop ecx
jmp eax
_CopyCtorCallStub@0 endp
endif ; !FEATURE_CORECLR
ifdef FEATURE_PREJIT
;==========================================================================
_StubDispatchFixupStub@0 proc public
STUB_PROLOG
mov esi, esp
push 0
push 0
push eax ; siteAddrForRegisterIndirect (for tailcalls)
push esi ; pTransitionBlock
call _StubDispatchFixupWorker@16
STUB_EPILOG
_StubDispatchFixupPatchLabel@0:
public _StubDispatchFixupPatchLabel@0
; Tailcall target
jmp eax
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_StubDispatchFixupStub@0 endp
;==========================================================================
_ExternalMethodFixupStub@0 proc public
pop eax ; pop off the return address to the stub
; leaving the actual caller's return address on top of the stack
STUB_PROLOG
mov esi, esp
; EAX is return address into CORCOMPILE_EXTERNAL_METHOD_THUNK. Subtract 5 to get start address.
sub eax, 5
push 0
push 0
push eax
; pTransitionBlock
push esi
call _ExternalMethodFixupWorker@16
; eax now contains replacement stub. PreStubWorker will never return
; NULL (it throws an exception if stub creation fails.)
; From here on, mustn't trash eax
STUB_EPILOG
_ExternalMethodFixupPatchLabel@0:
public _ExternalMethodFixupPatchLabel@0
; Tailcall target
jmp eax
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_ExternalMethodFixupStub@0 endp
ifdef FEATURE_READYTORUN
;==========================================================================
_DelayLoad_MethodCall@0 proc public
STUB_PROLOG_2_HIDDEN_ARGS
mov esi, esp
push ecx
push edx
push eax
; pTransitionBlock
push esi
call _ExternalMethodFixupWorker@16
; eax now contains replacement stub. PreStubWorker will never return
; NULL (it throws an exception if stub creation fails.)
; From here on, mustn't trash eax
STUB_EPILOG
; Share the patch label
jmp _ExternalMethodFixupPatchLabel@0
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_DelayLoad_MethodCall@0 endp
endif
;=======================================================================================
; The call in softbound vtable slots initially points to this function.
; The pupose of this function is to transfer the control to right target and
; to optionally patch the target of the jump so that we do not take this slow path again.
;
_VirtualMethodFixupStub@0 proc public
pop eax ; Pop the return address. It points right after the call instruction in the thunk.
sub eax,5 ; Calculate the address of the thunk
; Push ebp frame to get good callstack under debugger
push ebp
mov ebp, esp
; Preserve argument registers
push ecx
push edx
push eax ; address of the thunk
push ecx ; this ptr
call _VirtualMethodFixupWorker@8
; Restore argument registers
pop edx
pop ecx
; Pop ebp frame
pop ebp
_VirtualMethodFixupPatchLabel@0:
public _VirtualMethodFixupPatchLabel@0
; Proceed to execute the actual method.
jmp eax
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_VirtualMethodFixupStub@0 endp
endif ; FEATURE_PREJIT
;==========================================================================
; The prestub
_ThePreStub@0 proc public
STUB_PROLOG
mov esi, esp
; EAX contains MethodDesc* from the precode. Push it here as argument
; for PreStubWorker
push eax
push esi
call _PreStubWorker@8
; eax now contains replacement stub. PreStubWorker will never return
; NULL (it throws an exception if stub creation fails.)
; From here on, mustn't trash eax
STUB_EPILOG
; Tailcall target
jmp eax
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_ThePreStub@0 endp
; This method does nothing. It's just a fixed function for the debugger to put a breakpoint
; on so that it can trace a call target.
_ThePreStubPatch@0 proc public
; make sure that the basic block is unique
test eax,34
_ThePreStubPatchLabel@0:
public _ThePreStubPatchLabel@0
ret
_ThePreStubPatch@0 endp
ifdef FEATURE_COMINTEROP
;==========================================================================
; CLR -> COM generic or late-bound call
_GenericComPlusCallStub@0 proc public
STUB_PROLOG
; pTransitionBlock
mov esi, esp
; return value
sub esp, 8
; save pMD
mov ebx, eax
push eax ; pMD
push esi ; pTransitionBlock
call _CLRToCOMWorker@8
push eax
call _setFPReturn@12 ; pop & set the return value
; From here on, mustn't trash eax:edx
; Get pComPlusCallInfo for return thunk
mov ecx, [ebx + ComPlusCallMethodDesc__m_pComPlusCallInfo]
STUB_EPILOG_RETURN
; Tailcall return thunk
jmp [ecx + ComPlusCallInfo__m_pRetThunk]
; This will never be executed. It is just to help out stack-walking logic
; which disassembles the epilog to unwind the stack.
ret
_GenericComPlusCallStub@0 endp
endif ; FEATURE_COMINTEROP
ifdef FEATURE_REMOTING
_TransparentProxyStub@0 proc public
; push slot passed in eax
push eax
; Move into eax the stub data and call the stub
mov eax, [ecx + TransparentProxyObject___stubData]
call [ecx + TransparentProxyObject___stub]
ifdef _DEBUG
nop ; Mark this as a special call site that can directly
; call managed code
endif
test eax, eax
jnz CtxMismatch2
mov eax, [ecx + TransparentProxyObject___pMT]
push ebx ; spill EBX
; Convert the slot number into the code address
; See MethodTable.h for details on vtable layout
mov ebx, [esp + 4] ; Reload the slot
shr ebx, ASM__VTABLE_SLOTS_PER_CHUNK_LOG2 ; indirectionSlotNumber
mov eax,[eax + ebx*4 + SIZEOF_MethodTable]
mov ebx, [esp + 4] ; use unchanged slot from above
and ebx, ASM__VTABLE_SLOTS_PER_CHUNK-1 ; offsetInChunk
mov eax, [eax + ebx*4]
; At this point, eax contains the code address
; Restore EBX
pop ebx
; Remove the slot number from the stack
lea esp, [esp+4]
jmp eax
; CONTEXT MISMATCH CASE, call out to the real proxy to dispatch
CtxMismatch2:
pop eax ; restore MethodDesc *
jmp _TransparentProxyStub_CrossContext@0 ; jump to slow TP stub
_TransparentProxyStub@0 endp
_TransparentProxyStub_CrossContext@0 proc public
STUB_PROLOG
; pTransitionBlock
mov esi, esp
; return value
sub esp, 3*4 ; 64-bit return value + cb stack pop
push eax ; pMD
push esi ; pTransitionBlock
call _TransparentProxyStubWorker@8
pop ebx ; cbStackPop
push eax
call _setFPReturn@12 ; pop & set the return value
; From here on, mustn't trash eax:edx
mov ecx, ebx ; cbStackPop
mov ebx, [esp+6*4] ; get retaddr
mov [esp+6*4+ecx], ebx ; put it where it belongs
STUB_EPILOG_RETURN
add esp, ecx ; pop all the args
ret
_TransparentProxyStub_CrossContext@0 endp
; This method does nothing. It's just a fixed function for the debugger to put a breakpoint
; on so that it can trace a call target.
_TransparentProxyStubPatch@0 proc public
; make sure that the basic block is unique
test eax,12
_TransparentProxyStubPatchLabel@0:
public _TransparentProxyStubPatchLabel@0
ret
_TransparentProxyStubPatch@0 endp
endif ; FEATURE_REMOTING
ifdef FEATURE_COMINTEROP
;--------------------------------------------------------------------------
; This is the code that all com call method stubs run initially.
; Most of the real work occurs in ComStubWorker(), a C++ routine.
; The template only does the part that absolutely has to be in assembly
; language.
;--------------------------------------------------------------------------
_ComCallPreStub@0 proc public
pop eax ;ComCallMethodDesc*
; push ebp-frame
push ebp
mov ebp,esp
; save CalleeSavedRegisters
push ebx
push esi
push edi
push eax ; ComCallMethodDesc*
sub esp, 5*4 ; next, vtable, gscookie, 64-bit error return
lea edi, [esp]
lea esi, [esp+3*4]
push edi ; pErrorReturn
push esi ; pFrame
call _ComPreStubWorker@8
; eax now contains replacement stub. ComStubWorker will return NULL if stub creation fails
cmp eax, 0
je nostub ; oops we could not create a stub
add esp, 6*4
; pop CalleeSavedRegisters
pop edi
pop esi
pop ebx
pop ebp
jmp eax ; Reexecute with replacement stub.
; We will never get here. This "ret" is just so that code-disassembling
; profilers know to stop disassembling any further
ret
nostub:
; Even though the ComPreStubWorker sets a 64 bit value as the error return code.
; Only the lower 32 bits contain usefula data. The reason for this is that the
; possible error return types are: failure HRESULT, 0 and floating point 0.
; In each case, the data fits in 32 bits. Instead, we use the upper half of
; the return value to store number of bytes to pop
mov eax, [edi]
mov edx, [edi+4]
add esp, 6*4
; pop CalleeSavedRegisters
pop edi
pop esi
pop ebx
pop ebp
pop ecx ; return address
add esp, edx ; pop bytes of the stack
push ecx ; return address
; We need to deal with the case where the method is PreserveSig=true and has an 8
; byte return type. There are 2 types of 8 byte return types: integer and floating point.
; For integer 8 byte return types, we always return 0 in case of failure. For floating
; point return types, we return the value in the floating point register. In both cases
; edx should be 0.
xor edx, edx ; edx <-- 0
ret
_ComCallPreStub@0 endp
endif ; FEATURE_COMINTEROP
ifdef FEATURE_READYTORUN
;==========================================================================
; Define helpers for delay loading of readytorun helpers
DYNAMICHELPER macro frameFlags, suffix
_DelayLoad_Helper&suffix&@0 proc public
STUB_PROLOG_2_HIDDEN_ARGS
mov esi, esp
push frameFlags
push ecx ; module
push edx ; section index
push eax ; indirection cell address.
push esi ; pTransitionBlock
call _DynamicHelperWorker@20
test eax,eax
jnz @F
mov eax, [esi] ; The result is stored in the argument area of the transition block
STUB_EPILOG_RETURN
ret
@@:
STUB_EPILOG
jmp eax
_DelayLoad_Helper&suffix&@0 endp
endm
DYNAMICHELPER DynamicHelperFrameFlags_Default
DYNAMICHELPER DynamicHelperFrameFlags_ObjectArg, _Obj
DYNAMICHELPER , _ObjObj
endif ; FEATURE_READYTORUN
end