From ed17f546ee019a4d38b3829404a832400f45e0b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:34:13 +0000 Subject: [PATCH 01/12] Initial plan From 65a0ad05170ad9ff67340db7b7074be63ccd038e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:39:27 +0000 Subject: [PATCH 02/12] Fix ResolveRecordTracePath for NativeAOT Co-authored-by: mdh1418 <16830051+mdh1418@users.noreply.github.com> --- .../tracing/userevents/common/UserEventsTestRunner.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index 8860a4c4d5fea6..404802b3158e99 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -198,6 +198,13 @@ private static int RunOrchestrator( private static string ResolveRecordTracePath(string userEventsScenarioDir) { + // In NativeAOT, Assembly.Location returns an empty string, so userEventsScenarioDir can be null or empty. + // Fall back to the current process's main module path. + if (string.IsNullOrEmpty(userEventsScenarioDir)) + { + userEventsScenarioDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule!.FileName)!; + } + // scenario dir: .../tracing/userevents// string usereventsRoot = Path.GetFullPath(Path.Combine(userEventsScenarioDir, "..", "..")); // common dir: .../tracing/userevents/common/userevents_common From 9e4f84edee0c9a4bb2a5b38ab5d85f8726ff3c6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:40:05 +0000 Subject: [PATCH 03/12] Document NativeAOT limitation with record-trace Co-authored-by: mdh1418 <16830051+mdh1418@users.noreply.github.com> --- src/tests/tracing/userevents/common/UserEventsTestRunner.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index 404802b3158e99..b21e1497773fd8 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -125,6 +125,10 @@ private static int RunOrchestrator( string diagnosticPortDir = "/tmp"; traceeStartInfo.Environment["TMPDIR"] = diagnosticPortDir; + // Note: record-trace uses perfmap/doublemapper to discover .NET process PIDs and their diagnostic ports. + // NativeAOT apps do not use doublemapping, so they will not be discovered by record-trace even with TMPDIR set. + // See https://github.com/microsoft/one-collect/issues/226 for details. + // TMPDIR is configured on Helix, but the diagnostic port is created outside of Helix's default temp datadisk path. // The diagnostic port should be automatically cleaned up when the tracee shuts down, but zombie sockets can be left // behind after catastrophic exits. Clean them before launching the tracee to avoid deleting sockets from a reused PID. From 187f6ed1a5d7162556eb1b5faca79d788ec1ebbb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:41:24 +0000 Subject: [PATCH 04/12] Improve null handling in ResolveRecordTracePath Co-authored-by: mdh1418 <16830051+mdh1418@users.noreply.github.com> --- .../userevents/common/UserEventsTestRunner.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index b21e1497773fd8..f08e451698b85a 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -206,7 +206,17 @@ private static string ResolveRecordTracePath(string userEventsScenarioDir) // Fall back to the current process's main module path. if (string.IsNullOrEmpty(userEventsScenarioDir)) { - userEventsScenarioDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule!.FileName)!; + string? mainModuleFileName = Process.GetCurrentProcess().MainModule?.FileName; + if (string.IsNullOrEmpty(mainModuleFileName)) + { + throw new InvalidOperationException("Unable to determine the test assembly path: Assembly.Location is empty and MainModule.FileName is unavailable."); + } + + userEventsScenarioDir = Path.GetDirectoryName(mainModuleFileName); + if (string.IsNullOrEmpty(userEventsScenarioDir)) + { + throw new InvalidOperationException($"Unable to determine the directory of the main module: {mainModuleFileName}"); + } } // scenario dir: .../tracing/userevents// From 79e4dc83eb9a326d9b2deb4f8d2d83af13e20b49 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Fri, 23 Jan 2026 19:23:08 +0000 Subject: [PATCH 05/12] [clr-interp] Fix jmp instruction to tolerate the call.tail opcode failing to actually tail call (#123513) call.tail in the interpreter isn't guaranteed to tail-call in some cases, so we need to put in a ret instruction after the tail-call to ensure that the runtime doesn't execute invalid code. The code for doing a ret is extracted into a helper routine, and now called from both the CEE_JMP and CEE_RET pathways. The only change made to the code was to unify where the ip adjustment was to not happen in the EmitRet logic and instead keep it all in the same place in CEE_RET handling case. This fixes these test cases on Windows Arm64 JIT/Directed/pinvoke/jump JIT/Directed/pinvoke/tail_pinvoke --- src/coreclr/interpreter/compiler.cpp | 213 ++++++++++++++------------- src/coreclr/interpreter/compiler.h | 1 + 2 files changed, 112 insertions(+), 102 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 3f597700ec5fd0..3d4f6d5aad9f4c 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -5283,6 +5283,110 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } } +void InterpCompiler::EmitRet(CORINFO_METHOD_INFO* methodInfo) +{ + CORINFO_SIG_INFO sig = methodInfo->args; + InterpType retType = GetInterpType(sig.retType); + + if ((retType != InterpTypeVoid) && (retType != InterpTypeByRef)) + { + CheckStackExact(1); + m_pStackPointer[-1].BashStackTypeToI_ForLocalVariableAddress(); + } + + if ((m_isSynchronized || m_isAsyncMethodWithContextSaveRestore) && m_currentILOffset < m_ILCodeSizeFromILHeader) + { + // We are in a synchronized/async method, but we need to go through the finally/fault first + int32_t ilOffset = (int32_t)(m_ip - m_pILCode); + int32_t target = m_synchronizedOrAsyncPostFinallyOffset; + + if (retType != InterpTypeVoid) + { + CheckStackExact(1); + if (m_synchronizedOrAsyncRetValVarIndex == -1) + { + PushInterpType(retType, m_pVars[m_pStackPointer[-1].var].clsHnd); + m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; + m_pStackPointer--; + INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); + } + INTERP_DUMP("Store to ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); + EmitStoreVar(m_synchronizedOrAsyncRetValVarIndex); + } + else + { + CheckStackExact(0); + } + EmitLeave(ilOffset, target); + return; + } + + if (m_methodInfo->args.isAsyncCall()) + { + // We're doing a standard return. Set the continuation return to NULL. + AddIns(INTOP_SET_CONTINUATION_NULL); + } + + if (retType == InterpTypeVoid) + { + CheckStackExact(0); + AddIns(INTOP_RET_VOID); + } + else if (retType == InterpTypeVT) + { + CheckStackExact(1); + AddIns(INTOP_RET_VT); + m_pStackPointer--; + int32_t retVar = m_pStackPointer[0].var; + m_pLastNewIns->SetSVar(retVar); + m_pLastNewIns->data[0] = m_pVars[retVar].size; + } + else + { + CheckStackExact(1); + +#ifdef TARGET_64BIT + // nint and int32 can be used interchangeably. Add implicit conversions. + if (m_pStackPointer[-1].GetStackType() == StackTypeI4 && g_stackTypeFromInterpType[retType] == StackTypeI8) + EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_I4); +#endif + if (m_pStackPointer[-1].GetStackType() == StackTypeR4 && g_stackTypeFromInterpType[retType] == StackTypeR8) + EmitConv(m_pStackPointer - 1, StackTypeR8, INTOP_CONV_R8_R4); + else if (m_pStackPointer[-1].GetStackType() == StackTypeR8 && g_stackTypeFromInterpType[retType] == StackTypeR4) + EmitConv(m_pStackPointer - 1, StackTypeR4, INTOP_CONV_R4_R8); + + if (m_pStackPointer[-1].GetStackType() != g_stackTypeFromInterpType[retType]) + { + StackType retStackType = g_stackTypeFromInterpType[retType]; + StackType stackType = m_pStackPointer[-1].GetStackType(); + + if (stackType == StackTypeI && (retStackType == StackTypeO || retStackType == StackTypeByRef)) + { + // Allow implicit conversion from nint to ref or byref + } + else if (retStackType == StackTypeI && (stackType == StackTypeO || stackType == StackTypeByRef)) + { + // Allow implicit conversion from ref or byref to nint + } +#ifdef TARGET_64BIT + else if (retStackType == StackTypeI4 && stackType == StackTypeI8) + { + // nint and int32 can be used interchangeably. Add implicit conversions. + // Allow implicit conversion from int64 to int32 which is just a truncation + } +#endif + else + { + BADCODE("return type mismatch"); + } + } + + AddIns(GetRetForType(retType)); + m_pStackPointer--; + m_pLastNewIns->SetSVar(m_pStackPointer[0].var); + } +} + void SetSlotToTrue(TArray &gcRefMap, int32_t slotOffset) { assert(slotOffset % sizeof(void*) == 0); @@ -8158,108 +8262,7 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) case CEE_RET: { - CORINFO_SIG_INFO sig = methodInfo->args; - InterpType retType = GetInterpType(sig.retType); - - if ((retType != InterpTypeVoid) && (retType != InterpTypeByRef)) - { - CheckStackExact(1); - m_pStackPointer[-1].BashStackTypeToI_ForLocalVariableAddress(); - } - - if ((m_isSynchronized || m_isAsyncMethodWithContextSaveRestore) && m_currentILOffset < m_ILCodeSizeFromILHeader) - { - // We are in a synchronized/async method, but we need to go through the finally/fault first - int32_t ilOffset = (int32_t)(m_ip - m_pILCode); - int32_t target = m_synchronizedOrAsyncPostFinallyOffset; - - if (retType != InterpTypeVoid) - { - CheckStackExact(1); - if (m_synchronizedOrAsyncRetValVarIndex == -1) - { - PushInterpType(retType, m_pVars[m_pStackPointer[-1].var].clsHnd); - m_synchronizedOrAsyncRetValVarIndex = m_pStackPointer[-1].var; - m_pStackPointer--; - INTERP_DUMP("Created ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); - } - INTERP_DUMP("Store to ret val var V%d\n", m_synchronizedOrAsyncRetValVarIndex); - EmitStoreVar(m_synchronizedOrAsyncRetValVarIndex); - } - else - { - CheckStackExact(0); - } - EmitLeave(ilOffset, target); - linkBBlocks = false; - m_ip++; - break; - } - - if (m_methodInfo->args.isAsyncCall()) - { - // We're doing a standard return. Set the continuation return to NULL. - AddIns(INTOP_SET_CONTINUATION_NULL); - } - - if (retType == InterpTypeVoid) - { - CheckStackExact(0); - AddIns(INTOP_RET_VOID); - } - else if (retType == InterpTypeVT) - { - CheckStackExact(1); - AddIns(INTOP_RET_VT); - m_pStackPointer--; - int32_t retVar = m_pStackPointer[0].var; - m_pLastNewIns->SetSVar(retVar); - m_pLastNewIns->data[0] = m_pVars[retVar].size; - } - else - { - CheckStackExact(1); - -#ifdef TARGET_64BIT - // nint and int32 can be used interchangeably. Add implicit conversions. - if (m_pStackPointer[-1].GetStackType() == StackTypeI4 && g_stackTypeFromInterpType[retType] == StackTypeI8) - EmitConv(m_pStackPointer - 1, StackTypeI8, INTOP_CONV_I8_I4); -#endif - if (m_pStackPointer[-1].GetStackType() == StackTypeR4 && g_stackTypeFromInterpType[retType] == StackTypeR8) - EmitConv(m_pStackPointer - 1, StackTypeR8, INTOP_CONV_R8_R4); - else if (m_pStackPointer[-1].GetStackType() == StackTypeR8 && g_stackTypeFromInterpType[retType] == StackTypeR4) - EmitConv(m_pStackPointer - 1, StackTypeR4, INTOP_CONV_R4_R8); - - if (m_pStackPointer[-1].GetStackType() != g_stackTypeFromInterpType[retType]) - { - StackType retStackType = g_stackTypeFromInterpType[retType]; - StackType stackType = m_pStackPointer[-1].GetStackType(); - - if (stackType == StackTypeI && (retStackType == StackTypeO || retStackType == StackTypeByRef)) - { - // Allow implicit conversion from nint to ref or byref - } - else if (retStackType == StackTypeI && (stackType == StackTypeO || stackType == StackTypeByRef)) - { - // Allow implicit conversion from ref or byref to nint - } -#ifdef TARGET_64BIT - else if (retStackType == StackTypeI4 && stackType == StackTypeI8) - { - // nint and int32 can be used interchangeably. Add implicit conversions. - // Allow implicit conversion from int64 to int32 which is just a truncation - } -#endif - else - { - BADCODE("return type mismatch"); - } - } - - AddIns(GetRetForType(retType)); - m_pStackPointer--; - m_pLastNewIns->SetSVar(m_pStackPointer[0].var); - } + EmitRet(methodInfo); m_ip++; linkBBlocks = false; break; @@ -9394,7 +9397,13 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) // CEE_JMP inside a funclet is not allowed BADCODE("CEE_JMP inside funclet"); } + if (m_isSynchronized || m_isAsyncMethodWithContextSaveRestore) + { + BADCODE("CEE_JMP in synchronized or async method"); + } EmitCall(m_pConstrainedToken, readonly, true /* tailcall */, false /*newObj*/, false /*isCalli*/); + EmitRet(methodInfo); // The tail-call infrastructure in the interpreter is not 100% guaranteed to do a + // tail-call, so inject the ret logic here to cover that case. linkBBlocks = false; break; } diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 72e71258d7a43a..19f548c746b4c0 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -940,6 +940,7 @@ class InterpCompiler void EmitShiftOp(int32_t opBase); void EmitCompareOp(int32_t opBase); void EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool readonly, bool tailcall, bool newObj, bool isCalli); + void EmitRet(CORINFO_METHOD_INFO* methodInfo); void EmitSuspend(const CORINFO_CALL_INFO &callInfo, ContinuationContextHandling ContinuationContextHandling); void EmitCalli(bool isTailCall, void* calliCookie, int callIFunctionPointerVar, CORINFO_SIG_INFO* callSiteSig); bool EmitNamedIntrinsicCall(NamedIntrinsic ni, bool nonVirtualCall, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); From 6ff6bf5f9499e97154735481aef915bdf2f43d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 23 Jan 2026 22:42:24 +0100 Subject: [PATCH 06/12] Mark new userevents tests as native AOT incompatible (#123541) These are all crashing: ``` 11:31:58.741 Running test: tracing/userevents/custommetadata/custommetadata/custommetadata.cmd Unhandled exception. System.ArgumentNullException: Value cannot be null. (Parameter 'path1') at System.ArgumentNullException.Throw(String) at System.IO.Path.Combine(String, String, String) at Tracing.UserEvents.Tests.Common.UserEventsTestRunner.ResolveRecordTracePath(String) at Tracing.UserEvents.Tests.Common.UserEventsTestRunner.RunOrchestrator(String, String, Func`2, Int32, Int32) at Tracing.UserEvents.Tests.CustomMetadata.CustomMetadata.Main(String[] args) ``` --- src/tests/tracing/userevents/Directory.Build.props | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/tests/tracing/userevents/Directory.Build.props diff --git a/src/tests/tracing/userevents/Directory.Build.props b/src/tests/tracing/userevents/Directory.Build.props new file mode 100644 index 00000000000000..98e0af61c4f9da --- /dev/null +++ b/src/tests/tracing/userevents/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + true + + + From 22d341a752dfc83a7ef041207e88390b51365d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 23 Jan 2026 22:43:22 +0100 Subject: [PATCH 07/12] Disable StringBuilder marshalling tests on native AOT (#123530) Test started running with https://github.com/dotnet/runtime/pull/123112 Cc @dotnet/ilc-contrib --- .../Interop/StringMarshalling/Common/StringBuilderTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/Interop/StringMarshalling/Common/StringBuilderTests.cs b/src/tests/Interop/StringMarshalling/Common/StringBuilderTests.cs index f09305a2a03046..84bd526f1997d8 100644 --- a/src/tests/Interop/StringMarshalling/Common/StringBuilderTests.cs +++ b/src/tests/Interop/StringMarshalling/Common/StringBuilderTests.cs @@ -15,6 +15,7 @@ public partial class StringBuilderTests private static readonly string InitialString = "Hello World"; [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/123529", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static void ByValue() { var builder = new StringBuilder(InitialString); @@ -23,6 +24,7 @@ public static void ByValue() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/123529", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static void ByRef() { var builder = new StringBuilder(InitialString); @@ -31,6 +33,7 @@ public static void ByRef() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/123529", typeof(TestLibrary.Utilities), nameof(TestLibrary.Utilities.IsNativeAot))] public static void ReversePInvoke() { var builder = new StringBuilder(InitialString); From fbcc04a6a153c7a4db080ab2d4035e0c248d2aca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:34:13 +0000 Subject: [PATCH 08/12] Initial plan From 8339d8e2d6d786c9648a4ee8be54510f7b3e9e7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:00:16 +0000 Subject: [PATCH 09/12] Revert PR #123541: Remove NativeAotIncompatible marking Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com> --- src/tests/tracing/userevents/Directory.Build.props | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/tests/tracing/userevents/Directory.Build.props diff --git a/src/tests/tracing/userevents/Directory.Build.props b/src/tests/tracing/userevents/Directory.Build.props deleted file mode 100644 index 98e0af61c4f9da..00000000000000 --- a/src/tests/tracing/userevents/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - true - - - From 15d9475e3dee3aa2f240f924ba701f15449c98fe Mon Sep 17 00:00:00 2001 From: Jan Kotas Date: Fri, 23 Jan 2026 14:02:05 -0800 Subject: [PATCH 10/12] Update src/tests/tracing/userevents/common/UserEventsTestRunner.cs Co-authored-by: Adeel Mujahid <3840695+am11@users.noreply.github.com> --- src/tests/tracing/userevents/common/UserEventsTestRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index f08e451698b85a..c1dba245c607a5 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -206,7 +206,7 @@ private static string ResolveRecordTracePath(string userEventsScenarioDir) // Fall back to the current process's main module path. if (string.IsNullOrEmpty(userEventsScenarioDir)) { - string? mainModuleFileName = Process.GetCurrentProcess().MainModule?.FileName; + string? mainModuleFileName = Environment.ProcessPath; if (string.IsNullOrEmpty(mainModuleFileName)) { throw new InvalidOperationException("Unable to determine the test assembly path: Assembly.Location is empty and MainModule.FileName is unavailable."); From 9bd07521eff3568d88a291ac504828b79eedaf73 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:27:37 +0000 Subject: [PATCH 11/12] Use AppContext.BaseDirectory instead of Assembly.Location for NativeAOT compatibility Co-authored-by: mdh1418 <16830051+mdh1418@users.noreply.github.com> --- .../tracing/userevents/activity/activity.cs | 1 - src/tests/tracing/userevents/basic/basic.cs | 1 - .../userevents/common/UserEventsTestRunner.cs | 32 +++++-------------- .../custommetadata/custommetadata.cs | 1 - .../userevents/managedevent/managedevent.cs | 1 - .../userevents/multithread/multithread.cs | 1 - 6 files changed, 8 insertions(+), 29 deletions(-) diff --git a/src/tests/tracing/userevents/activity/activity.cs b/src/tests/tracing/userevents/activity/activity.cs index 3e711af20a9ad9..71db59ef9f8c8b 100644 --- a/src/tests/tracing/userevents/activity/activity.cs +++ b/src/tests/tracing/userevents/activity/activity.cs @@ -151,7 +151,6 @@ public static int Main(string[] args) return UserEventsTestRunner.Run( args, "activity", - typeof(Activity).Assembly.Location, ActivityTracee, s_traceValidator); } diff --git a/src/tests/tracing/userevents/basic/basic.cs b/src/tests/tracing/userevents/basic/basic.cs index 180a59f6e7ab20..0e9a3891ac9c10 100644 --- a/src/tests/tracing/userevents/basic/basic.cs +++ b/src/tests/tracing/userevents/basic/basic.cs @@ -55,7 +55,6 @@ public static int Main(string[] args) return UserEventsTestRunner.Run( args, "basic", - typeof(Basic).Assembly.Location, BasicTracee, s_traceValidator); } diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index c1dba245c607a5..3a661e357cd659 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -26,7 +26,6 @@ public class UserEventsTestRunner public static int Run( string[] args, string scenarioName, - string traceeAssemblyPath, Action traceeAction, Func traceValidator, int traceeExitTimeout = DefaultTraceeExitTimeoutMs, @@ -46,7 +45,6 @@ public static int Run( return RunOrchestrator( scenarioName, - traceeAssemblyPath, traceValidator, traceeExitTimeout, recordTraceExitTimeout); @@ -54,12 +52,14 @@ public static int Run( private static int RunOrchestrator( string scenarioName, - string traceeAssemblyPath, Func traceValidator, int traceeExitTimeout, int recordTraceExitTimeout) { - string userEventsScenarioDir = Path.GetDirectoryName(traceeAssemblyPath); + // Use AppContext.BaseDirectory which works consistently for both CoreCLR and NativeAOT. + // For CoreCLR: .../tracing/userevents/// + // For NativeAOT: .../tracing/userevents/// (even though exe is in native/ subdirectory) + string userEventsScenarioDir = AppContext.BaseDirectory; string recordTracePath = ResolveRecordTracePath(userEventsScenarioDir); string scriptFilePath = Path.Combine(userEventsScenarioDir, $"{scenarioName}.script"); @@ -116,7 +116,7 @@ private static int RunOrchestrator( ProcessStartInfo traceeStartInfo = new(); traceeStartInfo.FileName = Process.GetCurrentProcess().MainModule!.FileName; - traceeStartInfo.Arguments = $"{traceeAssemblyPath} tracee"; + traceeStartInfo.Arguments = "tracee"; traceeStartInfo.WorkingDirectory = userEventsScenarioDir; traceeStartInfo.RedirectStandardOutput = true; traceeStartInfo.RedirectStandardError = true; @@ -202,26 +202,10 @@ private static int RunOrchestrator( private static string ResolveRecordTracePath(string userEventsScenarioDir) { - // In NativeAOT, Assembly.Location returns an empty string, so userEventsScenarioDir can be null or empty. - // Fall back to the current process's main module path. - if (string.IsNullOrEmpty(userEventsScenarioDir)) - { - string? mainModuleFileName = Environment.ProcessPath; - if (string.IsNullOrEmpty(mainModuleFileName)) - { - throw new InvalidOperationException("Unable to determine the test assembly path: Assembly.Location is empty and MainModule.FileName is unavailable."); - } - - userEventsScenarioDir = Path.GetDirectoryName(mainModuleFileName); - if (string.IsNullOrEmpty(userEventsScenarioDir)) - { - throw new InvalidOperationException($"Unable to determine the directory of the main module: {mainModuleFileName}"); - } - } - - // scenario dir: .../tracing/userevents// + // userEventsScenarioDir is AppContext.BaseDirectory, which points to: + // .../tracing/userevents/// + // Navigate up two directories to reach userevents root, then into common/userevents_common string usereventsRoot = Path.GetFullPath(Path.Combine(userEventsScenarioDir, "..", "..")); - // common dir: .../tracing/userevents/common/userevents_common string commonDir = Path.Combine(usereventsRoot, "common", "userevents_common"); string recordTracePath = Path.Combine(commonDir, "record-trace"); return recordTracePath; diff --git a/src/tests/tracing/userevents/custommetadata/custommetadata.cs b/src/tests/tracing/userevents/custommetadata/custommetadata.cs index 50fdc3d242d48a..1436e548ea1f58 100644 --- a/src/tests/tracing/userevents/custommetadata/custommetadata.cs +++ b/src/tests/tracing/userevents/custommetadata/custommetadata.cs @@ -70,7 +70,6 @@ public static int Main(string[] args) return UserEventsTestRunner.Run( args, "custommetadata", - typeof(CustomMetadata).Assembly.Location, CustomMetadataTracee, s_traceValidator); } diff --git a/src/tests/tracing/userevents/managedevent/managedevent.cs b/src/tests/tracing/userevents/managedevent/managedevent.cs index 798a86a723d2dd..97364d5151c7fb 100644 --- a/src/tests/tracing/userevents/managedevent/managedevent.cs +++ b/src/tests/tracing/userevents/managedevent/managedevent.cs @@ -58,7 +58,6 @@ public static int Main(string[] args) return UserEventsTestRunner.Run( args, "managedevent", - typeof(ManagedEvent).Assembly.Location, ManagedEventTracee, s_traceValidator); } diff --git a/src/tests/tracing/userevents/multithread/multithread.cs b/src/tests/tracing/userevents/multithread/multithread.cs index eb7230f6922f76..01a9768f3de0fc 100644 --- a/src/tests/tracing/userevents/multithread/multithread.cs +++ b/src/tests/tracing/userevents/multithread/multithread.cs @@ -97,7 +97,6 @@ public static int Main(string[] args) return UserEventsTestRunner.Run( args, "multithread", - typeof(MultiThread).Assembly.Location, MultiThreadTracee, s_traceValidator); } From 1e53e9c6bb723cb71904a0adc6de77e515341bd2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:04:28 +0000 Subject: [PATCH 12/12] Use Environment.ProcessPath for tracee execution in NativeAOT NativeAOT tests run executables directly (not through corerun), so use Environment.ProcessPath instead of Process.GetCurrentProcess().MainModule.FileName. Co-authored-by: mdh1418 <16830051+mdh1418@users.noreply.github.com> --- src/tests/tracing/userevents/common/UserEventsTestRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs index 3a661e357cd659..abdef82a3fed72 100644 --- a/src/tests/tracing/userevents/common/UserEventsTestRunner.cs +++ b/src/tests/tracing/userevents/common/UserEventsTestRunner.cs @@ -115,7 +115,7 @@ private static int RunOrchestrator( Console.WriteLine($"record-trace started with PID: {recordTraceProcess.Id}"); ProcessStartInfo traceeStartInfo = new(); - traceeStartInfo.FileName = Process.GetCurrentProcess().MainModule!.FileName; + traceeStartInfo.FileName = Environment.ProcessPath!; traceeStartInfo.Arguments = "tracee"; traceeStartInfo.WorkingDirectory = userEventsScenarioDir; traceeStartInfo.RedirectStandardOutput = true;