From f0ede2b86b0f1b86744aa1e020ba1df3ac3d2744 Mon Sep 17 00:00:00 2001 From: David Mason Date: Wed, 29 Jul 2020 14:53:07 -0700 Subject: [PATCH] Add profiler ELT test (#39550) Fix the following issues: On amd64 linux we didn't save and restore the xmm registers, and didn't handle enregistered 16 bytes structs as return values On arm we didn't save and restore the floating point registers (I made the linux arm helpers match the windows arm helpers) On arm64 we didn't handle 16 byte enregistered structs as return values And add tests --- src/coreclr/src/vm/amd64/asmhelpers.S | 70 +-- src/coreclr/src/vm/amd64/profiler.cpp | 44 +- src/coreclr/src/vm/arm/asmhelpers.S | 201 +++---- src/coreclr/src/vm/arm/profiler.cpp | 2 +- src/coreclr/src/vm/arm64/asmhelpers.S | 2 +- src/coreclr/src/vm/arm64/asmhelpers.asm | 2 +- src/coreclr/src/vm/arm64/profiler.cpp | 47 +- src/coreclr/src/vm/proftoeeinterfaceimpl.h | 11 +- src/coreclr/tests/issues.targets | 6 + .../profiler/common/ProfilerTestRunner.cs | 6 +- src/tests/profiler/elt/slowpathcommon.cs | 169 ++++++ src/tests/profiler/elt/slowpathcommon.csproj | 11 + src/tests/profiler/elt/slowpatheltenter.cs | 34 ++ .../profiler/elt/slowpatheltenter.csproj | 17 + src/tests/profiler/elt/slowpatheltleave.cs | 34 ++ .../profiler/elt/slowpatheltleave.csproj | 17 + src/tests/profiler/native/CMakeLists.txt | 12 +- src/tests/profiler/native/classfactory.cpp | 4 +- .../eltprofiler/slowpatheltprofiler.cpp | 499 ++++++++++++++++++ .../native/eltprofiler/slowpatheltprofiler.h | 115 ++++ src/tests/profiler/native/profiler.cpp | 3 +- src/tests/profiler/native/profiler.h | 4 +- src/tests/profiler/native/profilerstring.h | 20 +- 23 files changed, 1128 insertions(+), 202 deletions(-) create mode 100644 src/tests/profiler/elt/slowpathcommon.cs create mode 100644 src/tests/profiler/elt/slowpathcommon.csproj create mode 100644 src/tests/profiler/elt/slowpatheltenter.cs create mode 100644 src/tests/profiler/elt/slowpatheltenter.csproj create mode 100644 src/tests/profiler/elt/slowpatheltleave.cs create mode 100644 src/tests/profiler/elt/slowpatheltleave.csproj create mode 100644 src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp create mode 100644 src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h diff --git a/src/coreclr/src/vm/amd64/asmhelpers.S b/src/coreclr/src/vm/amd64/asmhelpers.S index 82d6984ab545d..7a70334d6383b 100644 --- a/src/coreclr/src/vm/amd64/asmhelpers.S +++ b/src/coreclr/src/vm/amd64/asmhelpers.S @@ -52,17 +52,12 @@ # we can align to 16 and be guaranteed to not exceed the frame size .equ STACK_FUDGE_FACTOR, 0x8 -# Space to keep xmm0 and xmm1 -.equ SIZEOF_FP_ARG_SPILL, 0x10*2 - -.equ OFFSETOF_FP_ARG_SPILL, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + STACK_FUDGE_FACTOR - # SIZEOF_STACK_FRAME is how many bytes we reserve in our ELT helpers below # There are three components, the first is space for profiler platform specific # data struct that we spill the general purpose registers to, then space to # spill xmm0 and xmm1, then finally 8 bytes of padding to ensure that the xmm # register reads/writes are aligned on 16 bytes. -.equ SIZEOF_STACK_FRAME, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + SIZEOF_FP_ARG_SPILL + STACK_FUDGE_FACTOR +.equ SIZEOF_STACK_FRAME, SIZEOF_PROFILE_PLATFORM_SPECIFIC_DATA + STACK_FUDGE_FACTOR .equ PROFILE_ENTER, 0x1 .equ PROFILE_LEAVE, 0x2 @@ -131,15 +126,6 @@ NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler mov r10, 0x1 # PROFILE_ENTER mov [rsp + 0xa8], r10d # -- struct flags field - # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16) - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - - # we need to be able to restore the fp return register - # save fp return registers - movdqa [rax + 0x00], xmm0 - movdqa [rax + 0x10], xmm1 - END_PROLOGUE # rdi already contains the clientInfo @@ -148,10 +134,14 @@ NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler call C_FUNC(ProfileEnter) # restore fp return registers - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - movdqa xmm0, [rax + 0x00] - movdqa xmm1, [rax + 0x10] + movsd xmm0, real8 ptr [rsp + 0x38] # -- struct flt0 field + movsd xmm1, real8 ptr [rsp + 0x40] # -- struct flt1 field + movsd xmm2, real8 ptr [rsp + 0x48] # -- struct flt2 field + movsd xmm3, real8 ptr [rsp + 0x50] # -- struct flt3 field + movsd xmm4, real8 ptr [rsp + 0x58] # -- struct flt4 field + movsd xmm5, real8 ptr [rsp + 0x60] # -- struct flt5 field + movsd xmm6, real8 ptr [rsp + 0x68] # -- struct flt6 field + movsd xmm7, real8 ptr [rsp + 0x70] # -- struct flt7 field # restore arg registers mov rdi, [rsp + 0x78] @@ -216,15 +206,6 @@ NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler mov r10, 0x2 # PROFILE_LEAVE mov [rsp + 0xa8], r10d # flags -- struct flags field - # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16) - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - - # we need to be able to restore the fp return register - # save fp return registers - movdqa [rax + 0x00], xmm0 - movdqa [rax + 0x10], xmm1 - END_PROLOGUE # rdi already contains the clientInfo @@ -232,10 +213,14 @@ NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler call C_FUNC(ProfileLeave) # restore fp return registers - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - movdqa xmm0, [rax + 0x00] - movdqa xmm1, [rax + 0x10] + movsd xmm0, real8 ptr [rsp + 0x38] # -- struct flt0 field + movsd xmm1, real8 ptr [rsp + 0x40] # -- struct flt1 field + movsd xmm2, real8 ptr [rsp + 0x48] # -- struct flt2 field + movsd xmm3, real8 ptr [rsp + 0x50] # -- struct flt3 field + movsd xmm4, real8 ptr [rsp + 0x58] # -- struct flt4 field + movsd xmm5, real8 ptr [rsp + 0x60] # -- struct flt5 field + movsd xmm6, real8 ptr [rsp + 0x68] # -- struct flt6 field + movsd xmm7, real8 ptr [rsp + 0x70] # -- struct flt7 field # restore int return register mov rax, [rsp + 0x28] @@ -295,15 +280,6 @@ NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler mov r10, 0x2 # PROFILE_LEAVE mov [rsp + 0xa8], r10d # flags -- struct flags field - # get aligned stack ptr (rsp + OFFSETOF_FP_ARG_SPILL) & (-16) - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - - # we need to be able to restore the fp return register - # save fp return registers - movdqa [rax + 0x00], xmm0 - movdqa [rax + 0x10], xmm1 - END_PROLOGUE # rdi already contains the clientInfo @@ -311,10 +287,14 @@ NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler call C_FUNC(ProfileTailcall) # restore fp return registers - lea rax, [rsp + OFFSETOF_FP_ARG_SPILL] - and rax, -16 - movdqa xmm0, [rax + 0x00] - movdqa xmm1, [rax + 0x10] + movsd xmm0, real8 ptr [rsp + 0x38] # -- struct flt0 field + movsd xmm1, real8 ptr [rsp + 0x40] # -- struct flt1 field + movsd xmm2, real8 ptr [rsp + 0x48] # -- struct flt2 field + movsd xmm3, real8 ptr [rsp + 0x50] # -- struct flt3 field + movsd xmm4, real8 ptr [rsp + 0x58] # -- struct flt4 field + movsd xmm5, real8 ptr [rsp + 0x60] # -- struct flt5 field + movsd xmm6, real8 ptr [rsp + 0x68] # -- struct flt6 field + movsd xmm7, real8 ptr [rsp + 0x70] # -- struct flt7 field # restore int return register mov rax, [rsp + 0x28] diff --git a/src/coreclr/src/vm/amd64/profiler.cpp b/src/coreclr/src/vm/amd64/profiler.cpp index afe9685cda845..025bfab6f5640 100644 --- a/src/coreclr/src/vm/amd64/profiler.cpp +++ b/src/coreclr/src/vm/amd64/profiler.cpp @@ -126,6 +126,7 @@ ProfileArgIterator::ProfileArgIterator(MetaSig * pSig, void * platformSpecificHa PROFILE_PLATFORM_SPECIFIC_DATA* pData = (PROFILE_PLATFORM_SPECIFIC_DATA*)m_handle; #ifdef UNIX_AMD64_ABI m_bufferPos = 0; + ZeroMemory(pData->buffer, PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE * sizeof(UINT64)); #endif // UNIX_AMD64_ABI // unwind a frame and get the Rsp for the profiled method to make sure it matches @@ -483,17 +484,46 @@ LPVOID ProfileArgIterator::GetReturnBufferAddr(void) // by our calling convention, but is required by our profiler spec. return (LPVOID)pData->rax; } - + CorElementType t = m_argIterator.GetSig()->GetReturnType(); - if (ELEMENT_TYPE_VOID != t) + if (ELEMENT_TYPE_VOID == t) + { + return NULL; + } + +#ifdef UNIX_AMD64_ABI + if (m_argIterator.GetSig()->GetReturnTypeSize() == 16) { - if (ELEMENT_TYPE_R4 == t || ELEMENT_TYPE_R8 == t) - pData->rax = pData->flt0; + _ASSERTE(m_bufferPos == 0 && "Nothing else should be using the scratch space during a return"); + + // The unix x64 ABI has a special case where a 16 byte struct will be passed in registers + // and if there are integer and float args it will be passed in rax/etc and xmm/etc, respectively + // which means the values are noncontiguous. Just like the argument passing above + // we copy it in to the buffer to fake it being contiguous. + UINT flags = m_argIterator.GetFPReturnSize(); - return &(pData->rax); + // The lower two bits are used to indicate whether struct args are floating point or integer + if (flags & 1) + { + pData->buffer[0] = pData->flt0; + pData->buffer[1] = (flags & 2) ? pData->flt1 : pData->rax; + } + else + { + pData->buffer[0] = pData->rax; + pData->buffer[1] = (flags & 2) ? pData->flt0 : pData->rdx; + } + + return pData->buffer; } - else - return NULL; +#endif // UNIX_AMD64_ABI + + if (ELEMENT_TYPE_R4 == t || ELEMENT_TYPE_R8 == t) + { + pData->rax = pData->flt0; + } + + return &(pData->rax); } #undef PROFILE_ENTER diff --git a/src/coreclr/src/vm/arm/asmhelpers.S b/src/coreclr/src/vm/arm/asmhelpers.S index dbc7baa9a175c..dcdfda4df350d 100644 --- a/src/coreclr/src/vm/arm/asmhelpers.S +++ b/src/coreclr/src/vm/arm/asmhelpers.S @@ -366,134 +366,87 @@ LEAF_ENTRY JIT_ProfilerEnterLeaveTailcallStub, _TEXT bx lr LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT -// -// EXTERN_C void ProfileEnterNaked(FunctionIDOrClientID functionIDOrClientID); -// -NESTED_ENTRY ProfileEnterNaked, _TEXT, NoHandler - PROLOG_PUSH "{r4, r5, r7, r11, lr}" - PROLOG_STACK_SAVE_OFFSET r7, #8 - - // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order - - // UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier - // UINT32 r1; - // void *r11; - // void *Pc; - // union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) - // { - // UINT32 s[16]; - // UINT64 d[8]; - // }; - // FunctionID functionId; - // void *probeSp; // stack pointer of managed function - // void *profiledSp; // location of arguments on stack - // LPVOID hiddenArg; - // UINT32 flags; - movw r4, #1 - push { /* flags */ r4 } - movw r4, #0 - push { /* hiddenArg */ r4 } - add r5, r11, #8 - push { /* profiledSp */ r5 } - add r5, sp, #32 - push { /* probeSp */ r5 } - push { /* functionId */ r0 } +#define PROFILE_ENTER 1 +#define PROFILE_LEAVE 2 +#define PROFILE_TAILCALL 4 +// size of profiler data structure plus alignment padding +#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 104+4 + +// typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA +// { +// UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier +// UINT32 r1; +// void *R11; +// void *Pc; +// union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) +// { +// UINT32 s[16]; +// UINT64 d[8]; +// }; +// FunctionID functionId; +// void *probeSp; // stack pointer of managed function +// void *profiledSp; // location of arguments on stack +// LPVOID hiddenArg; +// UINT32 flags; +// } PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; + +.macro GenerateProfileHelper helper, flags +NESTED_ENTRY \helper\()Naked, _TEXT, NoHandler + PROLOG_PUSH "{r0,r3,r9,r12}" + + // for the 5 arguments that do not need popped plus 4 bytes of alignment + alloc_stack 6*4 + + // push fp regs vpush.64 { d0 - d7 } - push { lr } - push { r11 } - push { /* return value, r4 is NULL */ r4 } - push { /* return value, r4 is NULL */ r4 } - mov r1, sp - bl C_FUNC(ProfileEnter) - EPILOG_STACK_RESTORE_OFFSET r7, #8 - EPILOG_POP "{r4, r5, r7, r11, pc}" -NESTED_END ProfileEnterNaked, _TEXT -// -// EXTERN_C void ProfileLeaveNaked(FunctionIDOrClientID functionIDOrClientID); -// -NESTED_ENTRY ProfileLeaveNaked, _TEXT, NoHandler - PROLOG_PUSH "{r1, r2, r4, r5, r7, r11, lr}" - PROLOG_STACK_SAVE_OFFSET r7, #16 - - // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order - - // UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier - // UINT32 r1; - // void *r11; - // void *Pc; - // union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) - // { - // UINT32 s[16]; - // UINT64 d[8]; - // }; - // FunctionID functionId; - // void *probeSp; // stack pointer of managed function - // void *profiledSp; // location of arguments on stack - // LPVOID hiddenArg; - // UINT32 flags; - movw r4, #2 - push { /* flags */ r4 } - movw r4, #0 - push { /* hiddenArg */ r4 } - add r5, r11, #8 - push { /* profiledSp */ r5 } - add r5, sp, #40 - push { /* probeSp */ r5 } - push { /* functionId */ r0 } - vpush.64 { d0 - d7 } - push { lr } - push { r11 } - push { r1 } - push { r0 } - mov r1, sp - bl C_FUNC(ProfileLeave) - EPILOG_STACK_RESTORE_OFFSET r7, #16 - EPILOG_POP "{r1, r2, r4, r5, r7, r11, pc}" -NESTED_END ProfileLeaveNaked, _TEXT + // next three fields pc, r11, r1 + push { r1, r11, lr} -// -// EXTERN_C void ProfileTailcallNaked(FunctionIDOrClientID functionIDOrClientID); -// -NESTED_ENTRY ProfileTailcallNaked, _TEXT, NoHandler - PROLOG_PUSH "{r1, r2, r4, r5, r7, r11, lr}" - PROLOG_STACK_SAVE_OFFSET r7, #16 - - // fields of PROFILE_PLATFORM_SPECIFIC_DATA, in reverse order - - // UINT32 r0; // Keep r0 & r1 contiguous to make returning 64-bit results easier - // UINT32 r1; - // void *r11; - // void *Pc; - // union // Float arg registers as 32-bit (s0-s15) and 64-bit (d0-d7) - // { - // UINT32 s[16]; - // UINT64 d[8]; - // }; - // FunctionID functionId; - // void *probeSp; // stack pointer of managed function - // void *profiledSp; // location of arguments on stack - // LPVOID hiddenArg; - // UINT32 flags; - movw r4, #2 - push { /* flags */ r4 } - movw r4, #0 - push { /* hiddenArg */ r4 } - add r5, r11, #8 - push { /* profiledSp */ r5 } - add r5, sp, #40 - push { /* probeSp */ r5 } - push { /* functionId */ r0 } - vpush.64 { d0 - d7 } - push { lr } - push { r11 } - push { r1 } - push { r0 } + // return value is in r2 instead of r0 because functionID is passed in r0 + push { r2 } + + CHECK_STACK_ALIGNMENT + + // set the other args, starting with functionID + str r0, [sp, #80] + + // probeSp is the original sp when this stub was called + add r2, sp, SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA+20 + str r2, [sp, #84] + + // get the address of the arguments from the frame pointer, store in profiledSp + add r2, r11, #8 + str r2, [sp, #88] + + // clear hiddenArg + movw r2, #0 + str r2, [sp, #92] + + // set the flag to indicate what hook this is + movw r2, \flags + str r2, [sp, #96] + + // sp is the address of PROFILE_PLATFORM_SPECIFIC_DATA, then call to C++ mov r1, sp - bl C_FUNC(ProfileTailcall) - EPILOG_STACK_RESTORE_OFFSET r7, #16 - EPILOG_POP "{r1, r2, r4, r5, r7, r11, pc}" -NESTED_END ProfileTailcallNaked, _TEXT + bl C_FUNC(\helper) + + // restore all our regs + pop { r2 } + pop { r1, r11, lr} + vpop.64 { d0 - d7 } + + free_stack 6*4 + + EPILOG_POP "{r0,r3,r9,r12}" + + bx lr +NESTED_END \helper\()Naked, _TEXT +.endm + +GenerateProfileHelper ProfileEnter, PROFILE_ENTER +GenerateProfileHelper ProfileLeave, PROFILE_LEAVE +GenerateProfileHelper ProfileTailcall, PROFILE_TAILCALL #endif diff --git a/src/coreclr/src/vm/arm/profiler.cpp b/src/coreclr/src/vm/arm/profiler.cpp index 8bcd075fe986c..670dedb8ca8ca 100644 --- a/src/coreclr/src/vm/arm/profiler.cpp +++ b/src/coreclr/src/vm/arm/profiler.cpp @@ -144,7 +144,7 @@ Stack for the above call will look as follows (stack growing downwards): Thread::VirtualUnwindCallFrame(&ctx); // add the prespill register(r0-r3) size to get the stack pointer of previous function - _ASSERTE(pData->profiledSp == (void*)(ctx.Sp - 4*4)); + _ASSERTE(pData->profiledSp == (void*)(ctx.Sp - 4*4) || pData->profiledSp == (void*)(ctx.Sp - 6*4)); } #endif // _DEBUG diff --git a/src/coreclr/src/vm/arm64/asmhelpers.S b/src/coreclr/src/vm/arm64/asmhelpers.S index 9ff8203f22f4b..a8b0a7c07873a 100644 --- a/src/coreclr/src/vm/arm64/asmhelpers.S +++ b/src/coreclr/src/vm/arm64/asmhelpers.S @@ -1200,7 +1200,7 @@ LEAF_END JIT_ProfilerEnterLeaveTailcallStub, _TEXT #define PROFILE_ENTER 1 #define PROFILE_LEAVE 2 #define PROFILE_TAILCALL 4 -#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 256 +#define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 272 // ------------------------------------------------------------------ .macro GenerateProfileHelper helper, flags diff --git a/src/coreclr/src/vm/arm64/asmhelpers.asm b/src/coreclr/src/vm/arm64/asmhelpers.asm index 1250bd3265200..2f9227b1d80df 100644 --- a/src/coreclr/src/vm/arm64/asmhelpers.asm +++ b/src/coreclr/src/vm/arm64/asmhelpers.asm @@ -1427,7 +1427,7 @@ CallHelper2 #define PROFILE_ENTER 1 #define PROFILE_LEAVE 2 #define PROFILE_TAILCALL 4 - #define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 256 + #define SIZEOF__PROFILE_PLATFORM_SPECIFIC_DATA 272 ; ------------------------------------------------------------------ MACRO diff --git a/src/coreclr/src/vm/arm64/profiler.cpp b/src/coreclr/src/vm/arm64/profiler.cpp index 64bd9603c877b..ba35eb103eec3 100644 --- a/src/coreclr/src/vm/arm64/profiler.cpp +++ b/src/coreclr/src/vm/arm64/profiler.cpp @@ -10,6 +10,9 @@ #define PROFILE_LEAVE 2 #define PROFILE_TAILCALL 4 +// Scratch space to store HFA return values (max 16 bytes) +#define PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE 16 + typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA { void* Fp; @@ -23,6 +26,7 @@ typedef struct _PROFILE_PLATFORM_SPECIFIC_DATA void* hiddenArg; UINT32 flags; UINT32 unused; + BYTE buffer[PROFILE_PLATFORM_SPECIFIC_DATA_BUFFER_SIZE]; } PROFILE_PLATFORM_SPECIFIC_DATA, *PPROFILE_PLATFORM_SPECIFIC_DATA; UINT_PTR ProfileGetIPFromPlatformSpecificHandle(void* pPlatformSpecificHandle) @@ -45,7 +49,8 @@ void ProfileSetFunctionIDInPlatformSpecificHandle(void* pPlatformSpecificHandle, } ProfileArgIterator::ProfileArgIterator(MetaSig* pSig, void* pPlatformSpecificHandle) - : m_argIterator(pSig) + : m_argIterator(pSig), + m_bufferPos(0) { WRAPPER_NO_CONTRACT; @@ -235,8 +240,44 @@ LPVOID ProfileArgIterator::GetReturnBufferAddr(void) } } - if (m_argIterator.GetFPReturnSize() != 0) - { + UINT fpReturnSize = m_argIterator.GetFPReturnSize(); + if (fpReturnSize != 0) + { + TypeHandle thReturnValueType; + m_argIterator.GetSig()->GetReturnTypeNormalized(&thReturnValueType); + if (!thReturnValueType.IsNull() && thReturnValueType.IsHFA()) + { + UINT hfaFieldSize = fpReturnSize / 4; + UINT totalSize = m_argIterator.GetSig()->GetReturnTypeSize(); + _ASSERTE(totalSize % hfaFieldSize == 0); + _ASSERTE(totalSize <= 16); + + BYTE *dest = pData->buffer; + for (UINT floatRegIdx = 0; floatRegIdx < totalSize / hfaFieldSize; ++floatRegIdx) + { + if (hfaFieldSize == 4) + { + *(UINT32*)dest = *(UINT32*)&pData->floatArgumentRegisters.q[floatRegIdx]; + dest += 4; + } + else + { + _ASSERTE(hfaFieldSize == 8); + *(UINT64*)dest = *(UINT64*)&pData->floatArgumentRegisters.q[floatRegIdx]; + dest += 8; + } + + if (floatRegIdx > 8) + { + // There's only space for 8 arguments in buffer + _ASSERTE(FALSE); + break; + } + } + + return pData->buffer; + } + return &pData->floatArgumentRegisters.q[0]; } diff --git a/src/coreclr/src/vm/proftoeeinterfaceimpl.h b/src/coreclr/src/vm/proftoeeinterfaceimpl.h index 2fbfb8e80376c..de5b75a3434d6 100644 --- a/src/coreclr/src/vm/proftoeeinterfaceimpl.h +++ b/src/coreclr/src/vm/proftoeeinterfaceimpl.h @@ -56,9 +56,9 @@ class ProfileArgIterator private: void *m_handle; ArgIterator m_argIterator; -#ifdef UNIX_AMD64_ABI +#if defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64) UINT64 m_bufferPos; -#endif // UNIX_AMD64_ABI +#endif // defined(UNIX_AMD64_ABI) || defined(TARGET_ARM64) public: ProfileArgIterator(MetaSig * pMetaSig, void* platformSpecificHandle); @@ -74,9 +74,12 @@ class ProfileArgIterator return m_argIterator.NumFixedArgs(); } -#ifdef UNIX_AMD64_ABI +#if defined(UNIX_AMD64_ABI) + // On certain architectures we can pass args in non-sequential registers, + // this function will copy the struct so it is laid out as it would be in memory + // so it can be passed to the profiler LPVOID CopyStructFromRegisters(); -#endif // UNIX_AMD64_ABI +#endif // defined(UNIX_AMD64_ABI) // // After initialization, this method is called repeatedly until it diff --git a/src/coreclr/tests/issues.targets b/src/coreclr/tests/issues.targets index dc65eef1590d6..e21987966398f 100644 --- a/src/coreclr/tests/issues.targets +++ b/src/coreclr/tests/issues.targets @@ -1640,6 +1640,12 @@ needs triage + + needs triage + + + needs triage + needs triage diff --git a/src/tests/profiler/common/ProfilerTestRunner.cs b/src/tests/profiler/common/ProfilerTestRunner.cs index 4600f1f1f2d72..bb5bae9d90924 100644 --- a/src/tests/profiler/common/ProfilerTestRunner.cs +++ b/src/tests/profiler/common/ProfilerTestRunner.cs @@ -33,10 +33,11 @@ public static int Run(string profileePath, arguments = profileePath + " RunTest " + profileeArguments; program = GetCorerunPath(); + string profilerPath = GetProfilerPath(); if (!profileeOptions.HasFlag(ProfileeOptions.NoStartupAttach)) { envVars.Add("CORECLR_ENABLE_PROFILING", "1"); - envVars.Add("CORECLR_PROFILER_PATH", GetProfilerPath()); + envVars.Add("CORECLR_PROFILER_PATH", profilerPath); envVars.Add("CORECLR_PROFILER", "{" + profilerClsid + "}"); } @@ -48,7 +49,8 @@ public static int Run(string profileePath, envVars.Add("COMPlus_JITMinOpts", "0"); } - string profilerPath = GetProfilerPath(); + envVars.Add("Profiler_Test_Name", testName); + if(!File.Exists(profilerPath)) { LogTestFailure("Profiler library not found at expected path: " + profilerPath); diff --git a/src/tests/profiler/elt/slowpathcommon.cs b/src/tests/profiler/elt/slowpathcommon.cs new file mode 100644 index 0000000000000..eab383d0ff009 --- /dev/null +++ b/src/tests/profiler/elt/slowpathcommon.cs @@ -0,0 +1,169 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SlowPathELTTests +{ + [StructLayout(LayoutKind.Sequential)] + public struct IntegerStruct + { + public int x; + public int y; + + public IntegerStruct(int x, int y) + { + this.x = x; + this.y = y; + } + + public override String ToString() + { + return $"x={x} y={y}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct FloatingPointStruct + { + public double d1; + public double d2; + + public FloatingPointStruct(double d1, double d2) + { + this.d1 = d1; + this.d2 = d2; + } + + public override String ToString() + { + return $"d1={d1} d2={d2}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct MixedStruct + { + public int x; + public double d; + + public MixedStruct(int x, double d) + { + this.x = x; + this.d = d; + } + + public override String ToString() + { + return $"x={x} d={d}"; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct LargeStruct + { + public int x0; + public double d0; + public int x1; + public double d1; + public int x2; + public double d2; + public int x3; + public double d3; + + public LargeStruct(int x0, + double d0, + int x1, + double d1, + int x2, + double d2, + int x3, + double d3) + { + this. x0 = x0; + this.d0 = d0; + this.x1 = x1; + this.d1 = d1; + this.x2 = x2; + this.d2 = d2; + this.x3 = x3; + this.d3 = d3; + } + + public override String ToString() + { + return $"x0={x0} d0={d0} x1={x1} d1={d1} x2={x2} d2={d2} x3={x3} d3={d3}"; + } + } + + public class SlowPathELTHelpers + { + public static int RunTest() + { + Console.WriteLine($"SimpleArgsFunc returned {SimpleArgsFunc(-123, -4.3f, "Hello, test!")}"); + + Console.WriteLine($"MixedStructFunc returned {MixedStructFunc(new MixedStruct(1, 1))}"); + + Console.WriteLine($"LargeStructFunc returned {LargeStructFunc(new LargeStruct(0, 0, 1, 1, 2, 2, 3, 3))}"); + + Console.WriteLine($"IntegerStructFunc returned {IntegerStructFunc(new IntegerStruct(14, 256))}"); + + Console.WriteLine($"FloatingPointStructFunc returned {FloatingPointStructFunc(new FloatingPointStruct(13.0, 145.2))}"); + + Console.WriteLine($"DoubleRetFunc returned {DoubleRetFunc()}"); + + return 100; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static string SimpleArgsFunc(int x, float y, String str) + { + Console.WriteLine($"x={x} y={y} str={str}"); + return "Hello from SimpleArgsFunc!"; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static MixedStruct MixedStructFunc(MixedStruct ss) + { + Console.WriteLine($"ss={ss}"); + ss.x = 4; + return ss; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static int LargeStructFunc(LargeStruct ls) + { + Console.WriteLine($"ls={ls}"); + return 3; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static IntegerStruct IntegerStructFunc(IntegerStruct its) + { + its.x = 21; + return its; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static FloatingPointStruct FloatingPointStructFunc(FloatingPointStruct fps) + { + fps.d2 = 256.8; + return fps; + } + + [MethodImplAttribute(MethodImplOptions.NoInlining)] + public static double DoubleRetFunc() + { + return 13.0; + } + } +} diff --git a/src/tests/profiler/elt/slowpathcommon.csproj b/src/tests/profiler/elt/slowpathcommon.csproj new file mode 100644 index 0000000000000..869ebc2c1fab5 --- /dev/null +++ b/src/tests/profiler/elt/slowpathcommon.csproj @@ -0,0 +1,11 @@ + + + Library + BuildOnly + true + 0 + + + + + diff --git a/src/tests/profiler/elt/slowpatheltenter.cs b/src/tests/profiler/elt/slowpatheltenter.cs new file mode 100644 index 0000000000000..83d6bb56e6e62 --- /dev/null +++ b/src/tests/profiler/elt/slowpatheltenter.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Profiler.Tests; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SlowPathELTTests +{ + class SlowPathELTEnter + { + static readonly Guid EventPipeWritingProfilerGuid = new Guid("0B36296B-EC47-44DA-8320-DC5E3071DD06"); + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return SlowPathELTHelpers.RunTest(); + } + + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "ELTSlowPathEnter", + profilerClsid: EventPipeWritingProfilerGuid); + } + } +} diff --git a/src/tests/profiler/elt/slowpatheltenter.csproj b/src/tests/profiler/elt/slowpatheltenter.csproj new file mode 100644 index 0000000000000..8d2ac3058e83d --- /dev/null +++ b/src/tests/profiler/elt/slowpatheltenter.csproj @@ -0,0 +1,17 @@ + + + .NETCoreApp + exe + BuildAndRun + true + 0 + true + + + + + + + + + diff --git a/src/tests/profiler/elt/slowpatheltleave.cs b/src/tests/profiler/elt/slowpatheltleave.cs new file mode 100644 index 0000000000000..da8fd03ec3867 --- /dev/null +++ b/src/tests/profiler/elt/slowpatheltleave.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Profiler.Tests; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.Tracing; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SlowPathELTTests +{ + class SlowPathELTLeave + { + static readonly Guid EventPipeWritingProfilerGuid = new Guid("0B36296B-EC47-44DA-8320-DC5E3071DD06"); + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return SlowPathELTHelpers.RunTest(); + } + + return ProfilerTestRunner.Run(profileePath: System.Reflection.Assembly.GetExecutingAssembly().Location, + testName: "ELTSlowPathLeave", + profilerClsid: EventPipeWritingProfilerGuid); + } + } +} diff --git a/src/tests/profiler/elt/slowpatheltleave.csproj b/src/tests/profiler/elt/slowpatheltleave.csproj new file mode 100644 index 0000000000000..8d2ac3058e83d --- /dev/null +++ b/src/tests/profiler/elt/slowpatheltleave.csproj @@ -0,0 +1,17 @@ + + + .NETCoreApp + exe + BuildAndRun + true + 0 + true + + + + + + + + + diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index 3af1b28a4fbf7..a742c928bfc30 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -10,8 +10,16 @@ set(EVENTPIPE_SOURCES eventpipeprofiler/eventpipemetadatareader.cpp) set(METADATAGETDISPENSER_SOURCES metadatagetdispenser/metadatagetdispenser.cpp) set(GETAPPDOMAINSTATICADDRESS_SOURCES getappdomainstaticaddress/getappdomainstaticaddress.cpp) - -set(SOURCES ${GCBASIC_SOURCES} ${REJIT_SOURCES} ${EVENTPIPE_SOURCES} ${METADATAGETDISPENSER_SOURCES} ${GETAPPDOMAINSTATICADDRESS_SOURCES} profiler.def profiler.cpp classfactory.cpp dllmain.cpp guids.cpp) +set(ELT_SOURCES eltprofiler/slowpatheltprofiler.cpp) + +set(SOURCES + ${GCBASIC_SOURCES} + ${REJIT_SOURCES} + ${EVENTPIPE_SOURCES} + ${METADATAGETDISPENSER_SOURCES} + ${GETAPPDOMAINSTATICADDRESS_SOURCES} + ${ELT_SOURCES} + profiler.def profiler.cpp classfactory.cpp dllmain.cpp guids.cpp) include_directories(../../../coreclr/src/pal/prebuilt/inc) diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index dc5cc2feea025..301e91dde442c 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -8,6 +8,7 @@ #include "eventpipeprofiler/eventpipewritingprofiler.h" #include "metadatagetdispenser/metadatagetdispenser.h" #include "getappdomainstaticaddress/getappdomainstaticaddress.h" +#include "eltprofiler/slowpatheltprofiler.h" ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid) { @@ -61,7 +62,8 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI new EventPipeReadingProfiler(), new EventPipeWritingProfiler(), new MetaDataGetDispenser(), - new GetAppDomainStaticAddress() + new GetAppDomainStaticAddress(), + new SlowPathELTProfiler() // add new profilers here }; diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp new file mode 100644 index 0000000000000..8f858cdb0899b --- /dev/null +++ b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.cpp @@ -0,0 +1,499 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#define NOMINMAX + +#include "slowpatheltprofiler.h" +#include +#include +#include +#include + +using std::shared_ptr; +using std::vector; +using std::wcout; +using std::endl; + +shared_ptr SlowPathELTProfiler::s_profiler; + +#ifndef WIN32 +#define UINT_PTR_FORMAT "lx" +#define PROFILER_STUB EXTERN_C __attribute__((visibility("hidden"))) void STDMETHODCALLTYPE +#else // WIN32 +#define UINT_PTR_FORMAT "llx" +#define PROFILER_STUB EXTERN_C void STDMETHODCALLTYPE +#endif // WIN32 + +PROFILER_STUB EnterStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo) +{ + SlowPathELTProfiler::s_profiler->EnterCallback(functionId, eltInfo); +} + +PROFILER_STUB LeaveStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo) +{ + SlowPathELTProfiler::s_profiler->LeaveCallback(functionId, eltInfo); +} + +PROFILER_STUB TailcallStub(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo) +{ + SlowPathELTProfiler::s_profiler->TailcallCallback(functionId, eltInfo); +} + +GUID SlowPathELTProfiler::GetClsid() +{ + // {0B36296B-EC47-44DA-8320-DC5E3071DD06} + GUID clsid = { 0x0B36296B, 0xEC47, 0x44DA, { 0x83, 0x20, 0xDC, 0x5E, 0x30, 0x71, 0xDD, 0x06 } }; + return clsid; +} + +HRESULT SlowPathELTProfiler::Initialize(IUnknown* pICorProfilerInfoUnk) +{ + Profiler::Initialize(pICorProfilerInfoUnk); + + HRESULT hr = S_OK; + constexpr ULONG bufferSize = 1024; + ULONG envVarLen = 0; + WCHAR envVar[bufferSize]; + if (FAILED(hr = pCorProfilerInfo->GetEnvironmentVariable(WCHAR("Profiler_Test_Name"), + bufferSize, + &envVarLen, + envVar))) + { + wcout << L"Failed to get test name hr=" << std::hex << hr << endl; + _failures++; + return hr; + } + + size_t nullCharPos = std::min(bufferSize - 1, envVarLen); + envVar[nullCharPos] = 0; + if (wcscmp(envVar, WCHAR("ELTSlowPathEnter")) == 0) + { + wcout << L"Testing enter hooks" << endl; + _testType = TestType::EnterHooks; + } + else if (wcscmp(envVar, WCHAR("ELTSlowPathLeave")) == 0) + { + wcout << L"Testing leave hooks" << endl; + _testType = TestType::LeaveHooks; + } + else + { + wcout << L"Unknown test type" << endl; + _failures++; + return E_FAIL; + } + + SlowPathELTProfiler::s_profiler = shared_ptr(this); + + if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_ENTERLEAVE + | COR_PRF_ENABLE_FUNCTION_ARGS + | COR_PRF_ENABLE_FUNCTION_RETVAL + | COR_PRF_ENABLE_FRAME_INFO, + 0))) + { + wcout << L"FAIL: IpCorProfilerInfo::SetEventMask2() failed hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + + hr = this->pCorProfilerInfo->SetEnterLeaveFunctionHooks3WithInfo(EnterStub, LeaveStub, TailcallStub); + if (hr != S_OK) + { + wcout << L"SetEnterLeaveFunctionHooks3WithInfo failed with hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + + return S_OK; +} + +HRESULT SlowPathELTProfiler::Shutdown() +{ + Profiler::Shutdown(); + + if (_testType == TestType::EnterHooks) + { + if (_failures == 0 + && _testType == TestType::EnterHooks + && _sawSimpleFuncEnter + && _sawMixedStructFuncEnter + && _sawLargeStructFuncEnter) + { + wcout << L"PROFILER TEST PASSES" << endl; + } + else + { + wcout << L"TEST FAILED _failures=" << _failures.load() << L", _sawSimpleFuncEnter=" << _sawSimpleFuncEnter + << L", _sawMixedStructFuncEnter=" << _sawMixedStructFuncEnter << L", _sawLargeStructFuncEnter=" + << _sawLargeStructFuncEnter << endl; + } + } + else if (_testType == TestType::LeaveHooks) + { + if (_failures == 0 + && _testType == TestType::LeaveHooks + && _sawSimpleFuncLeave + && _sawMixedStructFuncLeave + && _sawLargeStructFuncLeave + && _sawIntegerStructFuncLeave + && _sawFloatingPointStructFuncLeave + && _sawDoubleRetFuncLeave) + { + wcout << L"PROFILER TEST PASSES" << endl; + } + else + { + wcout << L"TEST FAILED _failures=" << _failures.load() << L", _sawSimpleFuncLeave=" << _sawSimpleFuncLeave + << L", _sawMixedStructFuncLeave=" << _sawMixedStructFuncLeave << L", _sawLargeStructFuncLeave=" + << _sawLargeStructFuncLeave << L"_sawIntegerStructFuncLeave=" << _sawIntegerStructFuncLeave + << L"_sawFloatingPointStructFuncLeave=" << _sawFloatingPointStructFuncLeave + << L"_sawDoubleRetFuncLeave=" << _sawDoubleRetFuncLeave << endl; + } + } + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::EnterCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo) +{ + if (_testType != TestType::EnterHooks) + { + return S_OK; + } + + COR_PRF_FRAME_INFO frameInfo; + ULONG pcbArgumentInfo = 0; + NewArrayHolder pArgumentInfoBytes; + COR_PRF_FUNCTION_ARGUMENT_INFO *pArgumentInfo = NULL; + + HRESULT hr = pCorProfilerInfo->GetFunctionEnter3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, &pcbArgumentInfo, NULL); + if (FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + wcout << L"GetFunctionEnter3Info 1 failed with hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + pArgumentInfoBytes = new BYTE[pcbArgumentInfo]; + pArgumentInfo = reinterpret_cast((BYTE *)pArgumentInfoBytes); + hr = pCorProfilerInfo->GetFunctionEnter3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, &pcbArgumentInfo, pArgumentInfo); + if(FAILED(hr)) + { + wcout << L"GetFunctionEnter3Info 2 failed with hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + } + + String functionName = GetFunctionIDName(functionIdOrClientID.functionID); + if (functionName == WCHAR("SimpleArgsFunc")) + { + _sawSimpleFuncEnter = true; + + int x = -123; + float f = -4.3f; + const WCHAR *str = WCHAR("Hello, test!"); + + vector expectedValues = { { sizeof(int), (void *)&x, [&](UINT_PTR ptr){ return ValidateInt(ptr, x); } }, + { sizeof(float), (void *)&f, [&](UINT_PTR ptr){ return ValidateFloat(ptr, f); } }, + { sizeof(UINT_PTR), (void *)str, [&](UINT_PTR ptr){ return ValidateString(ptr, str); } } }; + + hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues); + } + else if (functionName == WCHAR("MixedStructFunc")) + { + _sawMixedStructFuncEnter = true; + + // On linux structs can be split with some in int registers and some in float registers + // so a struct with interleaved ints/doubles is interesting. + MixedStruct ss = { 1, 1.0 }; + vector expectedValues = { { sizeof(MixedStruct), (void *)&ss, [&](UINT_PTR ptr){ return ValidateMixedStruct(ptr, ss); } } }; + + hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues); + } + else if (functionName == WCHAR("LargeStructFunc")) + { + _sawLargeStructFuncEnter = true; + + LargeStruct ls = { 0, 0.0, 1, 1.0, 2, 2.0, 3, 3.0 }; + vector expectedValues = { { sizeof(LargeStruct), (void *)&ls, [&](UINT_PTR ptr){ return ValidateLargeStruct(ptr, ls); } } };; + + hr = ValidateFunctionArgs(pArgumentInfo, functionName, expectedValues); + } + + return hr; +} + +HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::LeaveCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo) +{ + if (_testType != TestType::LeaveHooks) + { + return S_OK; + } + + COR_PRF_FRAME_INFO frameInfo; + COR_PRF_FUNCTION_ARGUMENT_RANGE * pRetvalRange = new COR_PRF_FUNCTION_ARGUMENT_RANGE; + HRESULT hr = pCorProfilerInfo->GetFunctionLeave3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo, pRetvalRange); + if (FAILED(hr)) + { + wcout << L"GetFunctionLeave3Info failed hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + + String functionName = GetFunctionIDName(functionIdOrClientID.functionID); + if (functionName == WCHAR("SimpleArgsFunc")) + { + _sawSimpleFuncLeave = true; + + const WCHAR *str = WCHAR("Hello from SimpleArgsFunc!"); + + ExpectedArgValue simpleRetValue = { sizeof(UINT_PTR), (void *)str, [&](UINT_PTR ptr){ return ValidateString(ptr, str); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, simpleRetValue); + } + else if (functionName == WCHAR("MixedStructFunc")) + { + _sawMixedStructFuncLeave = true; + + MixedStruct ss = { 4, 1.0 }; + ExpectedArgValue MixedStructRetValue = { sizeof(MixedStruct), (void *)&ss, [&](UINT_PTR ptr){ return ValidateMixedStruct(ptr, ss); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, MixedStructRetValue); + } + else if (functionName == WCHAR("LargeStructFunc")) + { + _sawLargeStructFuncLeave = true; + + int32_t val = 3; + ExpectedArgValue largeStructRetValue = { sizeof(int32_t), (void *)&val, [&](UINT_PTR ptr){ return ValidateInt(ptr, val); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, largeStructRetValue); + } + else if (functionName == WCHAR("IntegerStructFunc")) + { + _sawIntegerStructFuncLeave = true; + + IntegerStruct is = { 21, 256 }; + ExpectedArgValue integerStructRetValue = { sizeof(IntegerStruct), (void *)&is, [&](UINT_PTR ptr){ return ValidateIntegerStruct(ptr, is); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, integerStructRetValue); + } + else if (functionName == WCHAR("FloatingPointStructFunc")) + { + _sawFloatingPointStructFuncLeave = true; + + FloatingPointStruct fps = { 13.0, 256.8 }; + ExpectedArgValue floatingPointStructRetValue = { sizeof(FloatingPointStruct), (void *)&fps, [&](UINT_PTR ptr){ return ValidateFloatingPointStruct(ptr, fps); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, floatingPointStructRetValue); + } + else if (functionName == WCHAR("DoubleRetFunc")) + { + _sawDoubleRetFuncLeave = true; + + double d = 13.0; + ExpectedArgValue doubleRetValue = { sizeof(double), (void *)&d, [&](UINT_PTR ptr){ return ValidateDouble(ptr, d); } }; + hr = ValidateOneArgument(pRetvalRange, functionName, 0, doubleRetValue); + } + + return hr; +} + +HRESULT STDMETHODCALLTYPE SlowPathELTProfiler::TailcallCallback(FunctionIDOrClientID functionIdOrClientID, COR_PRF_ELT_INFO eltInfo) +{ + COR_PRF_FRAME_INFO frameInfo; + HRESULT hr = pCorProfilerInfo->GetFunctionTailcall3Info(functionIdOrClientID.functionID, eltInfo, &frameInfo); + if (FAILED(hr)) + { + wcout << L"GetFunctionTailcall3Info failed hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + + // Tailcalls don't happen on debug builds, and there's no arguments to verify from GetFunctionTailcallinfo3 + + return hr; +} + +void SlowPathELTProfiler::PrintBytes(const BYTE *bytes, size_t length) +{ + for (size_t i = 0; i < length; ++i) + { + wcout << std::setfill(L'0') << std::setw(2) << std::uppercase << std::hex << bytes[i]; + + if (i > 1 && (i + 1) % 4 == 0) + { + wcout << " "; + } + } + + wcout << endl; +} + +bool SlowPathELTProfiler::ValidateInt(UINT_PTR ptr, int expected) +{ + if (ptr == NULL) + { + return false; + } + + return *(int *)ptr == expected; +} + +bool SlowPathELTProfiler::ValidateFloat(UINT_PTR ptr, float expected) +{ + if (ptr == NULL) + { + return false; + } + + return *(float *)ptr == expected; +} + +bool SlowPathELTProfiler::ValidateDouble(UINT_PTR ptr, double expected) +{ + if (ptr == NULL) + { + return false; + } + + return *(double *)ptr == expected; +} + +bool SlowPathELTProfiler::ValidateString(UINT_PTR ptr, const WCHAR *expected) +{ + if (ptr == NULL || *(void **)ptr == NULL) + { + return false; + } + + ULONG lengthOffset = 0; + ULONG bufferOffset = 0; + HRESULT hr = pCorProfilerInfo->GetStringLayout2(&lengthOffset, &bufferOffset); + if (FAILED(hr)) + { + wcout << L"GetStringLayout2 failed hr=0x" << std::hex << hr << endl; + _failures++; + return hr; + } + + UINT_PTR strReference = *((UINT_PTR *)ptr) + bufferOffset; + WCHAR *strPtr = (WCHAR *)strReference; + if (wcscmp(strPtr, expected) != 0) + { + _failures++; + return false; + } + + return true; +} + +bool SlowPathELTProfiler::ValidateMixedStruct(UINT_PTR ptr, MixedStruct expected) +{ + if (ptr == NULL) + { + return false; + } + + MixedStruct lhs = *(MixedStruct *)ptr; + return lhs.x == expected.x && lhs.d == expected.d; +} + +bool SlowPathELTProfiler::ValidateLargeStruct(UINT_PTR ptr, LargeStruct expected) +{ + if (ptr == NULL) + { + return false; + } + + LargeStruct lhs = *(LargeStruct *)ptr; + return lhs.x0 == expected.x0 + && lhs.x1 == expected.x1 + && lhs.x2 == expected.x2 + && lhs.x3 == expected.x3 + && lhs.d0 == expected.d0 + && lhs.d1 == expected.d1 + && lhs.d2 == expected.d2 + && lhs.d3 == expected.d3; +} + +bool SlowPathELTProfiler::ValidateFloatingPointStruct(UINT_PTR ptr, FloatingPointStruct expected) +{ + if (ptr == NULL) + { + return false; + } + + FloatingPointStruct lhs = *(FloatingPointStruct *)ptr; + return lhs.d1 == expected.d1 && lhs.d2 == expected.d2; +} + +bool SlowPathELTProfiler::ValidateIntegerStruct(UINT_PTR ptr, IntegerStruct expected) +{ + if (ptr == NULL) + { + return false; + } + + IntegerStruct lhs = *(IntegerStruct *)ptr; + return lhs.x == expected.x && lhs.y == expected.y; +} + + +HRESULT SlowPathELTProfiler::ValidateOneArgument(COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange, + String functionName, + size_t argPos, + ExpectedArgValue expectedValue) +{ + if (pArgRange->length != expectedValue.length) + { + wcout << L"Argument " << argPos << L" for function " << functionName << " expected length " << expectedValue.length + << L" but got length " << pArgRange->length << endl; + _failures++; + return E_FAIL; + } + + if (!expectedValue.func(pArgRange->startAddress)) + { + wcout << L"Argument " << argPos << L" for function " << functionName << L" did not match." << endl; + _failures++; + + // Print out the bytes so you don't have to debug if something mismatches + BYTE *expectedBytes = (BYTE *)expectedValue.value; + wcout << L"Expected bytes: "; + PrintBytes(expectedBytes, expectedValue.length); + + BYTE *actualBytes = (BYTE *)pArgRange->startAddress; + wcout << L"Actual bytes : "; + PrintBytes(actualBytes, pArgRange->length); + + return E_FAIL; + } + + return S_OK; +} + +HRESULT SlowPathELTProfiler::ValidateFunctionArgs(COR_PRF_FUNCTION_ARGUMENT_INFO *pArgInfo, + String functionName, + vector expectedArgValues) +{ + size_t expectedArgCount = expectedArgValues.size(); + + if (pArgInfo->numRanges != expectedArgCount) + { + wcout << L"Expected " << expectedArgCount << L" args for " << functionName << L" but got " << pArgInfo->numRanges << endl; + _failures++; + return E_FAIL; + } + + for (size_t i = 0; i < expectedArgCount; ++i) + { + ExpectedArgValue expectedValue = expectedArgValues[i]; + COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange = &(pArgInfo->ranges[i]); + + HRESULT hr = ValidateOneArgument(pArgRange, functionName, i, expectedValue); + if (FAILED(hr)) + { + return hr; + } + } + + return S_OK; +} diff --git a/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h new file mode 100644 index 0000000000000..254ba12d6e662 --- /dev/null +++ b/src/tests/profiler/native/eltprofiler/slowpatheltprofiler.h @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include +#include +#include +#include "../profiler.h" + +typedef bool (*validateFunc)(void *pMem); + +typedef struct +{ + size_t length; + void *value; + std::function func; +} ExpectedArgValue; + +typedef struct +{ + int x; + double d; +} MixedStruct; + +typedef struct +{ + int x0; + double d0; + int x1; + double d1; + int x2; + double d2; + int x3; + double d3; +} LargeStruct; + +typedef struct +{ + int x; + int y; +} IntegerStruct; + +typedef struct +{ + double d1; + double d2; +} FloatingPointStruct; + +class SlowPathELTProfiler : public Profiler +{ +public: + static std::shared_ptr s_profiler; + + SlowPathELTProfiler() : Profiler(), + _failures(0), + _sawSimpleFuncEnter(false), + _sawMixedStructFuncEnter(false), + _sawLargeStructFuncEnter(false), + _sawSimpleFuncLeave(false), + _sawMixedStructFuncLeave(false), + _sawLargeStructFuncLeave(false), + _testType(TestType::Unknown) + {} + + virtual GUID GetClsid(); + virtual HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pICorProfilerInfoUnk); + virtual HRESULT STDMETHODCALLTYPE Shutdown(); + + + HRESULT STDMETHODCALLTYPE EnterCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo); + HRESULT STDMETHODCALLTYPE LeaveCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo); + HRESULT STDMETHODCALLTYPE TailcallCallback(FunctionIDOrClientID functionId, COR_PRF_ELT_INFO eltInfo); + +private: + enum class TestType + { + EnterHooks, + LeaveHooks, + Unknown + }; + + std::atomic _failures; + bool _sawSimpleFuncEnter; + bool _sawMixedStructFuncEnter; + bool _sawLargeStructFuncEnter; + bool _sawSimpleFuncLeave; + bool _sawMixedStructFuncLeave; + bool _sawLargeStructFuncLeave; + bool _sawIntegerStructFuncLeave; + bool _sawFloatingPointStructFuncLeave; + bool _sawDoubleRetFuncLeave; + + TestType _testType; + + void PrintBytes(const BYTE *bytes, size_t length); + + bool ValidateInt(UINT_PTR ptr, int expected); + bool ValidateFloat(UINT_PTR ptr, float expected); + bool ValidateDouble(UINT_PTR ptr, double expected); + bool ValidateString(UINT_PTR ptr, const WCHAR *expected); + bool ValidateMixedStruct(UINT_PTR ptr, MixedStruct expected); + bool ValidateLargeStruct(UINT_PTR ptr, LargeStruct expected); + bool ValidateFloatingPointStruct(UINT_PTR ptr, FloatingPointStruct expected); + bool ValidateIntegerStruct(UINT_PTR ptr, IntegerStruct expected); + + HRESULT ValidateOneArgument(COR_PRF_FUNCTION_ARGUMENT_RANGE *pArgRange, + String functionName, + size_t argPos, + ExpectedArgValue expectedValue); + + HRESULT ValidateFunctionArgs(COR_PRF_FUNCTION_ARGUMENT_INFO *pArgInfo, + String name, + std::vector expectedArgValues); +}; diff --git a/src/tests/profiler/native/profiler.cpp b/src/tests/profiler/native/profiler.cpp index 3600788ccdf85..2dae0d1e7d4f9 100644 --- a/src/tests/profiler/native/profiler.cpp +++ b/src/tests/profiler/native/profiler.cpp @@ -21,12 +21,13 @@ HRESULT STDMETHODCALLTYPE Profiler::Initialize(IUnknown *pICorProfilerInfoUnk) printf("Profiler.dll!Profiler::Initialize\n"); fflush(stdout); - HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo9), reinterpret_cast(&this->pCorProfilerInfo)); + HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo11), reinterpret_cast(&this->pCorProfilerInfo)); if (FAILED(queryInterfaceResult)) { printf("Profiler.dll!Profiler::Initialize failed to QI for ICorProfilerInfo.\n"); pICorProfilerInfoUnk = NULL; } + return S_OK; } diff --git a/src/tests/profiler/native/profiler.h b/src/tests/profiler/native/profiler.h index 490e9c9ced09a..bb7651d018d7b 100644 --- a/src/tests/profiler/native/profiler.h +++ b/src/tests/profiler/native/profiler.h @@ -3,6 +3,8 @@ #pragma once +#define NOMINMAX + #include #include #include "cor.h" @@ -108,7 +110,7 @@ class Profiler : public ICorProfilerCallback10 String GetModuleIDName(ModuleID modId); public: - ICorProfilerInfo9* pCorProfilerInfo; + ICorProfilerInfo11* pCorProfilerInfo; Profiler(); virtual ~Profiler(); diff --git a/src/tests/profiler/native/profilerstring.h b/src/tests/profiler/native/profilerstring.h index a45edbd05626f..d7b63758369f5 100644 --- a/src/tests/profiler/native/profilerstring.h +++ b/src/tests/profiler/native/profilerstring.h @@ -7,6 +7,7 @@ #include #include #include +#include #ifdef _WIN32 #define WCHAR(str) L##str @@ -23,7 +24,6 @@ // here is to provide the easy ones to avoid all the copying and transforming. If more complex // string operations become necessary we should either write them in C++ or convert the string to // 32 bit and call the c runtime ones. -using std::max; #define WCHAR(str) u##str inline size_t wcslen(const char16_t *str) @@ -80,7 +80,7 @@ class String size_t otherLen = wcslen(other) + 1; if (buffer == nullptr || otherLen > bufferLen) { - bufferLen = max(DefaultStringLength, otherLen); + bufferLen = std::max(DefaultStringLength, otherLen); if (buffer != nullptr) { delete[] buffer; @@ -225,21 +225,23 @@ class String if (bufferLen > printBufferLen) { - delete[] printBuffer; - printBuffer = nullptr; - printBufferLen = 0; - } + if (printBuffer != nullptr) + { + delete[] printBuffer; + } - if (printBuffer == nullptr) - { printBuffer = new wchar_t[bufferLen]; + printBufferLen = bufferLen; } for (size_t i = 0; i < bufferLen; ++i) { - printBuffer[i] = (wchar_t)buffer[i]; + printBuffer[i] = CAST_CHAR(buffer[i]); } + // Make sure it's null terminated + printBuffer[bufferLen - 1] = '\0'; + return printBuffer; }