# 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
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
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]
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
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]
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
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]
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
// 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
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
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
#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
#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
#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;
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)
}
ProfileArgIterator::ProfileArgIterator(MetaSig* pSig, void* pPlatformSpecificHandle)
- : m_argIterator(pSig)
+ : m_argIterator(pSig),
+ m_bufferPos(0)
{
WRAPPER_NO_CONTRACT;
}
}
- 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];
}
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);
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
<ExcludeList Include="$(XunitTestBinBase)/profiler/gc/gc/**">
<Issue>needs triage</Issue>
</ExcludeList>
+ <ExcludeList Include="$(XunitTestBinBase)/profiler/elt/slowpatheltenter/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
+ <ExcludeList Include="$(XunitTestBinBase)/profiler/elt/slowpatheltleave/*">
+ <Issue>needs triage</Issue>
+ </ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/profiler/unittest/metadatagetdispenser/**">
<Issue>needs triage</Issue>
</ExcludeList>
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 + "}");
}
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);
--- /dev/null
+// 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;
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <OutputType>Library</OutputType>
+ <CLRTestKind>BuildOnly</CLRTestKind>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <CLRTestPriority>0</CLRTestPriority>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+ <OutputType>exe</OutputType>
+ <CLRTestKind>BuildAndRun</CLRTestKind>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <CLRTestPriority>0</CLRTestPriority>
+ <Optimize>true</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+ <ProjectReference Include="../common/profiler_common.csproj" />
+ <ProjectReference Include="slowpathcommon.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
+ </ItemGroup>
+</Project>
--- /dev/null
+// 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);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworkIdentifier>.NETCoreApp</TargetFrameworkIdentifier>
+ <OutputType>exe</OutputType>
+ <CLRTestKind>BuildAndRun</CLRTestKind>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <CLRTestPriority>0</CLRTestPriority>
+ <Optimize>true</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(MSBuildProjectName).cs" />
+ <ProjectReference Include="$(TestSourceDir)Common/CoreCLRTestLibrary/CoreCLRTestLibrary.csproj" />
+ <ProjectReference Include="../common/profiler_common.csproj" />
+ <ProjectReference Include="slowpathcommon.csproj" />
+ <ProjectReference Include="$(MSBuildThisFileDirectory)/../native/CMakeLists.txt" />
+ </ItemGroup>
+</Project>
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)
#include "eventpipeprofiler/eventpipewritingprofiler.h"
#include "metadatagetdispenser/metadatagetdispenser.h"
#include "getappdomainstaticaddress/getappdomainstaticaddress.h"
+#include "eltprofiler/slowpatheltprofiler.h"
ClassFactory::ClassFactory(REFCLSID clsid) : refCount(0), clsid(clsid)
{
new EventPipeReadingProfiler(),
new EventPipeWritingProfiler(),
new MetaDataGetDispenser(),
- new GetAppDomainStaticAddress()
+ new GetAppDomainStaticAddress(),
+ new SlowPathELTProfiler()
// add new profilers here
};
--- /dev/null
+// 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 <iostream>
+#include <cctype>
+#include <iomanip>
+#include <algorithm>
+
+using std::shared_ptr;
+using std::vector;
+using std::wcout;
+using std::endl;
+
+shared_ptr<SlowPathELTProfiler> 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<SlowPathELTProfiler>(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<BYTE> 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<COR_PRF_FUNCTION_ARGUMENT_INFO *>((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<ExpectedArgValue> 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<ExpectedArgValue> 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<ExpectedArgValue> 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<ExpectedArgValue> 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;
+}
--- /dev/null
+// 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 <memory>
+#include <vector>
+#include <functional>
+#include "../profiler.h"
+
+typedef bool (*validateFunc)(void *pMem);
+
+typedef struct
+{
+ size_t length;
+ void *value;
+ std::function<bool(UINT_PTR)> 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<SlowPathELTProfiler> 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<int> _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<ExpectedArgValue> expectedArgValues);
+};
printf("Profiler.dll!Profiler::Initialize\n");
fflush(stdout);
- HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo9), reinterpret_cast<void **>(&this->pCorProfilerInfo));
+ HRESULT queryInterfaceResult = pICorProfilerInfoUnk->QueryInterface(__uuidof(ICorProfilerInfo11), reinterpret_cast<void **>(&this->pCorProfilerInfo));
if (FAILED(queryInterfaceResult))
{
printf("Profiler.dll!Profiler::Initialize failed to QI for ICorProfilerInfo.\n");
pICorProfilerInfoUnk = NULL;
}
+
return S_OK;
}
#pragma once
+#define NOMINMAX
+
#include <atomic>
#include <cstdio>
#include "cor.h"
String GetModuleIDName(ModuleID modId);
public:
- ICorProfilerInfo9* pCorProfilerInfo;
+ ICorProfilerInfo11* pCorProfilerInfo;
Profiler();
virtual ~Profiler();
#include <assert.h>
#include <cstring>
#include <string>
+#include <algorithm>
#ifdef _WIN32
#define WCHAR(str) L##str
// 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)
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;
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;
}