Add IJW profiler regression test for transition FunctionID (#120151) - #131578
Add IJW profiler regression test for transition FunctionID (#120151)#131578lateralusX wants to merge 5 commits into
Conversation
…0151) When a C++/CLI (IJW) method invokes a managed method through a raw native function pointer, the resulting reverse (unmanaged->managed) marshaling stub emits a profiler code-transition callback. Prior to dotnet#117901 that stub loaded its secret argument (a UMEntryThunkData*) as the "MethodDesc", so the profiler received a bogus, non-null FunctionID; calling GetFunctionInfo on it crashed with an access violation. The fix routes the stub to EmitLoadNullPtr so the transition reports a NULL FunctionID, but nothing guarded the behavior. Add a self-contained Windows-only profiler test under src/tests/profiler/ijw that reproduces the scenario (managed -> native 'call' -> managed target by pointer) and validates the transition contract: - every non-null transition FunctionID must resolve via GetFunctionInfo; - the by-pointer target is reported CALL on entry and RETURN on exit; - the nested reverse-stub transitions surrounding it must report a NULL FunctionID rather than a bogus pointer. On unfixed runtimes the test fails via the GetFunctionInfo access violation.
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
Adds a Windows-only IJW (C++/CLI) profiler regression test to validate code-transition callback behavior when a managed method is invoked via a raw native function pointer, along with a new native profiler implementation used by the test.
Changes:
- Added a new
IjwProfilerimplementation under the shared test profiler DLL to validate transitionFunctionIDvalidity and expected transition shape. - Added a new IJW profilee mixed-mode DLL (
IjwProfileeDll) plus managed harness (ijw.csproj/ijw.cs) to drive the repro scenario. - Wired the new profiler into the native profiler build (
CMakeLists.txt) and COM class factory.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/profiler/native/ijw/ijwprofiler.h | Defines the new IJW-focused profiler used by the regression test. |
| src/tests/profiler/native/ijw/ijwprofiler.cpp | Implements transition validation logic and pass/fail reporting for the profiler test. |
| src/tests/profiler/native/CMakeLists.txt | Adds the new IJW profiler implementation to the shared profiler DLL build. |
| src/tests/profiler/native/classfactory.cpp | Registers the new profiler CLSID so it can be instantiated by the test runner. |
| src/tests/profiler/ijw/IjwProfileeDll.cpp | Mixed-mode IJW profilee implementing the managed→native→managed-by-pointer call shape. |
| src/tests/profiler/ijw/ijw.csproj | New Windows-only profiler test project that builds the native pieces and runs under ProfilerTestRunner. |
| src/tests/profiler/ijw/ijw.cs | Managed harness that launches the profiled process and triggers the IJW-by-pointer scenario. |
| src/tests/profiler/ijw/CMakeLists.txt | CMake project for building/installing the IJW mixed-mode profilee DLL. |
Comments suppressed due to low confidence (2)
src/tests/profiler/native/ijw/ijwprofiler.h:44
- Track whether any nested transitions were observed (and whether they were NULL FunctionID) so Shutdown() can assert the contract was exercised, not just that no non-null value was seen incidentally.
private:
std::atomic<int> _failures;
std::atomic<int> _transitions;
src/tests/profiler/native/ijw/ijwprofiler.cpp:124
- The nested-transition check only fails on non-null FunctionID, but it doesn’t record that a nested transition was actually seen (and that it was NULL). Incrementing counters here lets Shutdown() assert the contract was exercised and reduces the chance of a false pass if the runtime stops reporting these transitions.
// Any non-target transition seen while executing the managed target is a
// nested reverse (unmanaged->managed) marshaling stub. Post-fix these report
// a NULL FunctionID; a non-null value here is exactly the 120151 bug (a
// resolvable-but-wrong pointer would slip past the check above).
if (_insideTarget.load() && functionID != 0)
{
_failures++;
printf("FAIL: nested %s inside target reported non-null FunctionID=0x%p (reason=%d)\n",
which, (void*)functionID, (int)reason);
fflush(stdout);
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
src/tests/profiler/ijw/ijw.csproj:8
- is already set for all profiler tests via src/tests/profiler/Directory.Build.targets, so the project-local property (and the comment claiming it’s needed for CMakeProjectReference) is redundant and potentially misleading.
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- Needed for CMakeProjectReference -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<CrossGenTest>false</CrossGenTest>
src/tests/profiler/ijw/ijw.cs:23
- The reflection-based load path can throw or null-ref with little context (e.g., GetType/GetMethod returning null). Adding explicit error messages here makes failures actionable if the IJW profilee DLL isn’t found or is missing the expected type/method.
Assembly ijwDll = Assembly.Load("IjwProfileeDll");
Type testType = ijwDll.GetType("TestClass");
object testInstance = Activator.CreateInstance(testType);
MethodInfo method = testType.GetMethod("CallManagedFunctionByPointer");
return (int)method.Invoke(testInstance, null);
src/tests/profiler/native/ijw/ijwprofiler.cpp:23
- IjwProfiler::Initialize assumes pCorProfilerInfo is non-null after Profiler::Initialize, but Profiler::Initialize returns S_OK even if QI fails and can leave pCorProfilerInfo == nullptr. This can lead to a null dereference when calling SetEventMask2, obscuring the actual failure mode.
Profiler::Initialize(pICorProfilerInfoUnk);
HRESULT hr = S_OK;
if (FAILED(hr = pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_CODE_TRANSITIONS | COR_PRF_DISABLE_INLINING, 0)))
{
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/tests/profiler/ijw/IjwProfileeDll.cpp:17
- The profilee depends on the compiler's per-function /clr defaults to get the required repro shape (native helper calling a managed target via raw function pointer). Most existing IJW tests explicitly use
#pragma managed/#pragma unmanagedto avoid this being a compiler-version-dependent detail; without explicit pragmas this test could silently stop exercising the reverse-stub path (e.g., ifcallorManagedByPointerTargetends up compiled in the wrong mode). Consider making the managed/unmanaged boundaries explicit here and updating the comment accordingly.
// Compiled with default /clr per-function codegen (no #pragma managed/unmanaged);
// the reporter's sample used a file-static function, but a named function
// reproduces identically and lets the profiler match it by name.
__declspec(noinline) void ManagedByPointerTarget() {}
__declspec(noinline) static void call(void (*f)()) { f(); }
When a C++/CLI (IJW) method invokes a managed method through a raw native function pointer, the resulting reverse (unmanaged->managed) marshaling stub emits a profiler code-transition callback. Prior to #117901 that stub loaded its secret argument (a UMEntryThunkData*) as the "MethodDesc", so the profiler received a bogus, non-null FunctionID; calling GetFunctionInfo on it crashed with an access violation. The fix routes the stub to EmitLoadNullPtr so the transition reports a NULL FunctionID, but nothing guarded the behavior.
Add a self-contained Windows-only profiler test under src/tests/profiler/ijw that reproduces the scenario (managed -> native 'call' -> managed target by pointer) and validates the transition contract:
On unfixed runtimes the test fails via the GetFunctionInfo access violation.