diff --git a/src/coreclr/src/vm/amd64/asmhelpers.S b/src/coreclr/src/vm/amd64/asmhelpers.S index 82d6984ab545df..7a70334d6383b3 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 afe9685cda845b..025bfab6f56408 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 dbc7baa9a175c7..dcdfda4df350d6 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 8bcd075fe986c4..670dedb8ca8ca1 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 9ff8203f22f4bf..a8b0a7c07873a4 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 1250bd32652004..2f9227b1d80df6 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 64bd9603c877b3..ba35eb103eec30 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 2fbfb8e80376c7..de5b75a3434d66 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 c6c0b012046658..e15159d5a7801d 100644 --- a/src/coreclr/tests/issues.targets +++ b/src/coreclr/tests/issues.targets @@ -1661,6 +1661,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 4600f1f1f2d722..bb5bae9d90924d 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 00000000000000..eab383d0ff0092 --- /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 00000000000000..869ebc2c1fab5f --- /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 00000000000000..83d6bb56e6e628 --- /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 00000000000000..8d2ac3058e83d3 --- /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 00000000000000..da8fd03ec3867e --- /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 00000000000000..8d2ac3058e83d3 --- /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 3af1b28a4fbf77..a742c928bfc306 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 dc5cc2feea0251..301e91dde442c4 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 00000000000000..8f858cdb0899b0 --- /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 00000000000000..254ba12d6e6629 --- /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 3600788ccdf85e..2dae0d1e7d4f9b 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 490e9c9ced09a0..bb7651d018d7b2 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 a45edbd05626f1..d7b63758369f5b 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; }