Skip to content

Commit 3c3cc44

Browse files
authored
Implement Startup hooks support in Mono; refactor StartupHookProvider (#80391)
Fixes #47462 **CoreCLR** This also makes some changes to CoreCLR to decouple EventPipe and startup hooks. Presently if startup hooks are disabled, `RuntimeEventSource.Initialize` is never called. The PR makes the two features independent by moving runtime event source initialization out of the startup hook feature check. * Implement startup hooks support in Mono * Keep StartupHookProvider.ProcessStartupHooks under feature flag * Don't catch/cleanup the exceptions from startup hooks. * Add an ios simulator startup hook functional test * Implement Android functional test * Add WASM functional test * Make a single managed startup method for CoreCLR A common configuration for coreclr is event source enabled, startup hooks disabled, so at least one managed call is inevitable. Since we have to call into managed no matter what, let the trimmer determine what happens once we get there. This is different from mono where published trimmed apps may have both startup hooks and event source disabled. In that case we would rather avoid a managed call to an empty method early in startup. * fix build and line damage
1 parent 47f171e commit 3c3cc44

27 files changed

+308
-22
lines changed

src/coreclr/System.Private.CoreLib/CreateRuntimeRootILLinkDescriptorFile.targets

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
<_ILLinkDescriptorsFilePaths Include="$(ILLinkDirectory)ILLink.Descriptors.Debug.xml"
2020
Condition="'$(Configuration)' == 'Debug' or '$(Configuration)' == 'Checked'" />
2121
<_ILLinkDescriptorsFilePaths Include="$(CoreLibSharedDir)ILLink\ILLink.Descriptors.Shared.xml" />
22-
<_ILLinkDescriptorsFilePaths Include="$(CoreLibSharedDir)ILLink\ILLink.Descriptors.EventSource.xml" />
2322
</ItemGroup>
2423

2524
<!--

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@
228228
<Compile Include="$(BclSourcesRoot)\System\RuntimeType.ActivatorCache.cs" />
229229
<Compile Include="$(BclSourcesRoot)\System\RuntimeType.CoreCLR.cs" />
230230
<Compile Include="$(BclSourcesRoot)\System\Security\DynamicSecurityMethodAttribute.cs" />
231-
<Compile Include="$(BclSourcesRoot)\System\StartupHookProvider.cs" />
231+
<Compile Include="$(BclSourcesRoot)\System\StartupHookProvider.CoreCLR.cs" />
232232
<Compile Include="$(BclSourcesRoot)\System\String.CoreCLR.cs" />
233233
<Compile Include="$(BclSourcesRoot)\System\StubHelpers.cs" />
234234
<Compile Include="$(BclSourcesRoot)\System\Text\StringBuilder.CoreCLR.cs" />

src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Suppressions.LibraryBuild.xml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<linker>
33
<assembly fullname="System.Private.CoreLib">
4-
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
5-
<argument>ILLink</argument>
6-
<argument>IL2026</argument>
7-
<property name="Scope">member</property>
8-
<property name="Target">M:System.StartupHookProvider.ProcessStartupHooks()</property>
9-
<property name="Justification">This warning is left in the product so developers get an ILLink warning when trimming an app with System.StartupHookProvider.IsSupported=true.</property>
10-
</attribute>
114
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
125
<argument>ILLink</argument>
136
<argument>IL2026</argument>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
using System;
4+
using System.Diagnostics;
5+
using System.Diagnostics.Tracing;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.IO;
8+
using System.Reflection;
9+
using System.Runtime.Loader;
10+
11+
namespace System
12+
{
13+
internal static partial class StartupHookProvider
14+
{
15+
private static void ManagedStartup()
16+
{
17+
#if FEATURE_PERFTRACING
18+
if (EventSource.IsSupported)
19+
RuntimeEventSource.Initialize();
20+
#endif
21+
22+
if (IsSupported)
23+
ProcessStartupHooks();
24+
}
25+
}
26+
}

src/coreclr/vm/assembly.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1421,7 +1421,7 @@ static void RunMainPost()
14211421
}
14221422
}
14231423

1424-
static void RunStartupHooks()
1424+
static void RunManagedStartup()
14251425
{
14261426
CONTRACTL
14271427
{
@@ -1432,8 +1432,8 @@ static void RunStartupHooks()
14321432
}
14331433
CONTRACTL_END;
14341434

1435-
MethodDescCallSite processStartupHooks(METHOD__STARTUP_HOOK_PROVIDER__PROCESS_STARTUP_HOOKS);
1436-
processStartupHooks.Call(NULL);
1435+
MethodDescCallSite managedStartup(METHOD__STARTUP_HOOK_PROVIDER__MANAGED_STARTUP);
1436+
managedStartup.Call(NULL);
14371437
}
14381438

14391439
INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThreads)
@@ -1499,7 +1499,7 @@ INT32 Assembly::ExecuteMainMethod(PTRARRAYREF *stringArgs, BOOL waitForOtherThre
14991499
// Main thread wasn't started by the runtime.
15001500
Thread::InitializationForManagedThreadInNative(pThread);
15011501

1502-
RunStartupHooks();
1502+
RunManagedStartup();
15031503

15041504
hr = RunMain(pMeth, 1, &iRetVal, stringArgs);
15051505

src/coreclr/vm/corelib.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,7 @@ DEFINE_FIELD_U(rgiLastFrameFromForeignExceptionStackTrace, StackFrame
814814
DEFINE_FIELD_U(iFrameCount, StackFrameHelper, iFrameCount)
815815

816816
DEFINE_CLASS(STARTUP_HOOK_PROVIDER, System, StartupHookProvider)
817-
DEFINE_METHOD(STARTUP_HOOK_PROVIDER, PROCESS_STARTUP_HOOKS, ProcessStartupHooks, SM_RetVoid)
817+
DEFINE_METHOD(STARTUP_HOOK_PROVIDER, MANAGED_STARTUP, ManagedStartup, SM_RetVoid)
818818

819819
DEFINE_CLASS(STREAM, IO, Stream)
820820
DEFINE_METHOD(STREAM, BEGIN_READ, BeginRead, IM_ArrByte_Int_Int_AsyncCallback_Object_RetIAsyncResult)

src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Suppressions.LibraryBuild.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,12 @@
1515
<property name="Target">M:Internal.Runtime.InteropServices.ComponentActivator.GetFunctionPointer(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)</property>
1616
<property name="Justification">This warning is left in the product so developers get an ILLink warning when trimming an app with Internal.Runtime.InteropServices.ComponentActivator.IsSupported=true.</property>
1717
</attribute>
18+
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
19+
<argument>ILLink</argument>
20+
<argument>IL2026</argument>
21+
<property name="Scope">member</property>
22+
<property name="Target">M:System.StartupHookProvider.ProcessStartupHooks()</property>
23+
<property name="Justification">This warning is left in the product so developers get an ILLink warning when trimming an app with System.StartupHookProvider.IsSupported=true.</property>
24+
</attribute>
1825
</assembly>
1926
</linker>

src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,7 @@
10431043
<Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.T.cs" />
10441044
<Compile Include="$(MSBuildThisFileDirectory)System\SR.cs" />
10451045
<Compile Include="$(MSBuildThisFileDirectory)System\StackOverflowException.cs" />
1046+
<Compile Include="$(MSBuildThisFileDirectory)System\StartupHookProvider.cs" />
10461047
<Compile Include="$(MSBuildThisFileDirectory)System\String.Comparison.cs" />
10471048
<Compile Include="$(MSBuildThisFileDirectory)System\String.cs" />
10481049
<Compile Include="$(MSBuildThisFileDirectory)System\String.Manipulation.cs" />

src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/RuntimeEventSource.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ public static class Keywords
5050

5151
public static void Initialize()
5252
{
53-
s_RuntimeEventSource = new RuntimeEventSource();
53+
// initializing more than once may lead to missing events
54+
Debug.Assert(s_RuntimeEventSource == null);
55+
if (EventSource.IsSupported)
56+
s_RuntimeEventSource = new RuntimeEventSource();
5457
}
5558

5659
// Parameterized constructor to block initialization and ensure the EventSourceGenerator is creating the default constructor

src/coreclr/System.Private.CoreLib/src/System/StartupHookProvider.cs renamed to src/libraries/System.Private.CoreLib/src/System/StartupHookProvider.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace System
1313
{
14-
internal static class StartupHookProvider
14+
internal static partial class StartupHookProvider
1515
{
1616
private const string StartupHookTypeName = "StartupHook";
1717
private const string InitializeMethodName = "Initialize";
@@ -32,12 +32,6 @@ private static void ProcessStartupHooks()
3232
if (!IsSupported)
3333
return;
3434

35-
// Initialize tracing before any user code can be called if EventSource is enabled.
36-
if (EventSource.IsSupported)
37-
{
38-
System.Diagnostics.Tracing.RuntimeEventSource.Initialize();
39-
}
40-
4135
string? startupHooksVariable = AppContext.GetData("STARTUP_HOOKS") as string;
4236
if (startupHooksVariable == null)
4337
{

src/mono/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,4 +644,10 @@
644644
<method name="GetInstanceFieldFieldStore" />
645645
</type>
646646
</assembly>
647+
<assembly fullname="System.Private.CoreLib" feature="System.StartupHookProvider.IsSupported" featurevalue="true" featuredefault="true">
648+
<type fullname="System.StartupHookProvider">
649+
<!-- object.c: mono_runtime_run_startup_hooks -->
650+
<method name="ProcessStartupHooks" />
651+
</type>
652+
</assembly>
647653
</linker>

src/mono/mono/metadata/object-internals.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,4 +2148,7 @@ mono_string_instance_is_interned (MonoString *str);
21482148
gpointer
21492149
mono_method_get_unmanaged_wrapper_ftnptr_internal (MonoMethod *method, gboolean only_unmanaged_callers_only, MonoError *error);
21502150

2151+
void
2152+
mono_runtime_run_startup_hooks (void);
2153+
21512154
#endif /* __MONO_OBJECT_INTERNALS_H__ */

src/mono/mono/metadata/object.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8124,6 +8124,25 @@ mono_runtime_get_managed_cmd_line (void)
81248124
return cmd_line ? g_string_free (cmd_line, FALSE) : NULL;
81258125
}
81268126

8127+
void
8128+
mono_runtime_run_startup_hooks (void)
8129+
{
8130+
if (mono_runtime_get_no_exec ())
8131+
return;
8132+
8133+
MonoClass *klass = mono_class_try_load_from_name (mono_defaults.corlib, "System", "StartupHookProvider");
8134+
if (!klass)
8135+
return; // Linked away
8136+
ERROR_DECL (error);
8137+
MonoMethod *method = mono_class_get_method_from_name_checked (klass, "ProcessStartupHooks", -1, 0, error);
8138+
mono_error_cleanup (error);
8139+
if (!method)
8140+
return;
8141+
mono_runtime_invoke_checked (method, NULL, NULL, error);
8142+
// runtime hooks design doc says not to catch exceptions from the hooks
8143+
mono_error_raise_exception_deprecated (error);
8144+
}
8145+
81278146
#if NEVER_DEFINED
81288147
/*
81298148
* The following section is purely to declare prototypes and

src/mono/mono/mini/mini-runtime.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4729,6 +4729,8 @@ mini_init (const char *filename)
47294729

47304730
MONO_PROFILER_RAISE (runtime_initialized, ());
47314731

4732+
mono_runtime_run_startup_hooks ();
4733+
47324734
MONO_VES_INIT_END ();
47334735

47344736
return domain;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Exe</OutputType>
4+
<TestRuntime>true</TestRuntime>
5+
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
6+
<MainLibraryFileName>Android.Device_Emulator.StartupHook.Test.dll</MainLibraryFileName>
7+
<IncludesTestRunner>false</IncludesTestRunner>
8+
<ExpectedExitCode>42</ExpectedExitCode>
9+
<StartupHookSupport>true</StartupHookSupport>
10+
</PropertyGroup>
11+
12+
<ItemGroup>
13+
<Compile Include="Program.cs" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\..\TestAssets\StartupHookForFunctionalTest\StartupHookForFunctionalTest.csproj" />
18+
</ItemGroup>
19+
</Project>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Runtime.InteropServices;
8+
9+
public static class Program
10+
{
11+
public static int Main()
12+
{
13+
string appContextKey = "Test.StartupHookForFunctionalTest.DidRun";
14+
var data = (string) AppContext.GetData (appContextKey);
15+
16+
if (data != "Yes") {
17+
string msg = $"Expected startup hook to set {appContextKey} to 'Yes', got '{data}'";
18+
Console.Error.WriteLine(msg);
19+
return 104;
20+
}
21+
return 42;
22+
}
23+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"configProperties": {
3+
"STARTUP_HOOKS": "StartupHookForFunctionalTest"
4+
}
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
internal class StartupHook
4+
{
5+
public static void Initialize()
6+
{
7+
AppContext.SetData("Test.StartupHookForFunctionalTest.DidRun", "Yes");
8+
}
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<OutputType>Library</OutputType>
4+
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
5+
<IsTestProject>false</IsTestProject>
6+
<IsFunctionalTest>false</IsFunctionalTest>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<Compile Include="StartupHookForFunctionalTest.cs" />
11+
</ItemGroup>
12+
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices.JavaScript;
7+
8+
namespace Sample
9+
{
10+
public partial class Test
11+
{
12+
public static void Main()
13+
{
14+
}
15+
16+
[JSExport]
17+
public static int TestMeaning()
18+
{
19+
string appContextKey = "Test.StartupHookForFunctionalTest.DidRun";
20+
var data = (string) AppContext.GetData (appContextKey);
21+
22+
if (data != "Yes") {
23+
string msg = $"Expected startup hook to set {appContextKey} to 'Yes', got '{data}'";
24+
Console.Error.WriteLine(msg);
25+
return 104;
26+
}
27+
return 42;
28+
}
29+
}
30+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TestRuntime>true</TestRuntime>
4+
<Scenario>WasmTestOnBrowser</Scenario>
5+
<TestArchiveTestsRoot>$(TestArchiveRoot)browseronly/</TestArchiveTestsRoot>
6+
<TestArchiveTestsDir>$(TestArchiveTestsRoot)$(OSPlatformConfig)/</TestArchiveTestsDir>
7+
<DefineConstants>$(DefineConstants);TARGET_BROWSER</DefineConstants>
8+
<ExpectedExitCode>42</ExpectedExitCode>
9+
<WasmMainJSPath>main.js</WasmMainJSPath>
10+
<StartupHookSupport>true</StartupHookSupport>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<Compile Include="Program.cs" />
15+
<WasmExtraFilesToDeploy Include="index.html" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
20+
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
21+
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="..\..\..\TestAssets\StartupHookForFunctionalTest\StartupHookForFunctionalTest.csproj" />
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!DOCTYPE html>
2+
<!-- Licensed to the .NET Foundation under one or more agreements. -->
3+
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
4+
<html>
5+
6+
<head>
7+
<title>Runtime config test</title>
8+
<meta charset="UTF-8">
9+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
10+
<script type='module' src="./main.js"></script>
11+
</head>
12+
13+
<body>
14+
<h3 id="header">Runtime config test</h3>
15+
Answer to the Ultimate Question of Life, the Universe, and Everything is : <span id="out"></span>
16+
</body>
17+
18+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { dotnet } from './dotnet.js'
2+
3+
function wasm_exit(exit_code) {
4+
var tests_done_elem = document.createElement("label");
5+
tests_done_elem.id = "tests_done";
6+
tests_done_elem.innerHTML = exit_code.toString();
7+
document.body.appendChild(tests_done_elem);
8+
9+
console.log(`WASM EXIT ${exit_code}`);
10+
}
11+
12+
try {
13+
const { getAssemblyExports } = await dotnet.create();
14+
const exports = await getAssemblyExports("WebAssembly.Browser.StartupHook.Test.dll");
15+
const testMeaning = exports.Sample.Test.TestMeaning;
16+
const ret = testMeaning();
17+
document.getElementById("out").innerHTML = `${ret}`;
18+
console.debug(`ret: ${ret}`);
19+
20+
let exit_code = ret;
21+
wasm_exit(exit_code);
22+
} catch (err) {
23+
console.log(`WASM ERROR ${err}`);
24+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"configProperties": {
3+
"STARTUP_HOOKS": "StartupHookForFunctionalTest"
4+
}
5+
}

0 commit comments

Comments
 (0)