Skip to content

Commit

Permalink
Implement Startup hooks support in Mono; refactor StartupHookProvider (
Browse files Browse the repository at this point in the history
…#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
  • Loading branch information
lambdageek authored Jan 18, 2023
1 parent 47f171e commit 3c3cc44
Show file tree
Hide file tree
Showing 27 changed files with 308 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
<_ILLinkDescriptorsFilePaths Include="$(ILLinkDirectory)ILLink.Descriptors.Debug.xml"
Condition="'$(Configuration)' == 'Debug' or '$(Configuration)' == 'Checked'" />
<_ILLinkDescriptorsFilePaths Include="$(CoreLibSharedDir)ILLink\ILLink.Descriptors.Shared.xml" />
<_ILLinkDescriptorsFilePaths Include="$(CoreLibSharedDir)ILLink\ILLink.Descriptors.EventSource.xml" />
</ItemGroup>

<!--
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@
<Compile Include="$(BclSourcesRoot)\System\RuntimeType.ActivatorCache.cs" />
<Compile Include="$(BclSourcesRoot)\System\RuntimeType.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Security\DynamicSecurityMethodAttribute.cs" />
<Compile Include="$(BclSourcesRoot)\System\StartupHookProvider.cs" />
<Compile Include="$(BclSourcesRoot)\System\StartupHookProvider.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\String.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\StubHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Text\StringBuilder.CoreCLR.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<linker>
<assembly fullname="System.Private.CoreLib">
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2026</argument>
<property name="Scope">member</property>
<property name="Target">M:System.StartupHookProvider.ProcessStartupHooks()</property>
<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>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2026</argument>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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.Diagnostics;
using System.Diagnostics.Tracing;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;

namespace System
{
internal static partial class StartupHookProvider
{
private static void ManagedStartup()
{
#if FEATURE_PERFTRACING
if (EventSource.IsSupported)
RuntimeEventSource.Initialize();
#endif

if (IsSupported)
ProcessStartupHooks();
}
}
}
8 changes: 4 additions & 4 deletions src/coreclr/vm/assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1421,7 +1421,7 @@ static void RunMainPost()
}
}

static void RunStartupHooks()
static void RunManagedStartup()
{
CONTRACTL
{
Expand All @@ -1432,8 +1432,8 @@ static void RunStartupHooks()
}
CONTRACTL_END;

MethodDescCallSite processStartupHooks(METHOD__STARTUP_HOOK_PROVIDER__PROCESS_STARTUP_HOOKS);
processStartupHooks.Call(NULL);
MethodDescCallSite managedStartup(METHOD__STARTUP_HOOK_PROVIDER__MANAGED_STARTUP);
managedStartup.Call(NULL);
}

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

RunStartupHooks();
RunManagedStartup();

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

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/corelib.h
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ DEFINE_FIELD_U(rgiLastFrameFromForeignExceptionStackTrace, StackFrame
DEFINE_FIELD_U(iFrameCount, StackFrameHelper, iFrameCount)

DEFINE_CLASS(STARTUP_HOOK_PROVIDER, System, StartupHookProvider)
DEFINE_METHOD(STARTUP_HOOK_PROVIDER, PROCESS_STARTUP_HOOKS, ProcessStartupHooks, SM_RetVoid)
DEFINE_METHOD(STARTUP_HOOK_PROVIDER, MANAGED_STARTUP, ManagedStartup, SM_RetVoid)

DEFINE_CLASS(STREAM, IO, Stream)
DEFINE_METHOD(STREAM, BEGIN_READ, BeginRead, IM_ArrByte_Int_Int_AsyncCallback_Object_RetIAsyncResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,12 @@
<property name="Target">M:Internal.Runtime.InteropServices.ComponentActivator.GetFunctionPointer(System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr,System.IntPtr)</property>
<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>
</attribute>
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
<argument>ILLink</argument>
<argument>IL2026</argument>
<property name="Scope">member</property>
<property name="Target">M:System.StartupHookProvider.ProcessStartupHooks()</property>
<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>
</attribute>
</assembly>
</linker>
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.T.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SR.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\StackOverflowException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\StartupHookProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.Comparison.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.Manipulation.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ public static class Keywords

public static void Initialize()
{
s_RuntimeEventSource = new RuntimeEventSource();
// initializing more than once may lead to missing events
Debug.Assert(s_RuntimeEventSource == null);
if (EventSource.IsSupported)
s_RuntimeEventSource = new RuntimeEventSource();
}

// Parameterized constructor to block initialization and ensure the EventSourceGenerator is creating the default constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace System
{
internal static class StartupHookProvider
internal static partial class StartupHookProvider
{
private const string StartupHookTypeName = "StartupHook";
private const string InitializeMethodName = "Initialize";
Expand All @@ -32,12 +32,6 @@ private static void ProcessStartupHooks()
if (!IsSupported)
return;

// Initialize tracing before any user code can be called if EventSource is enabled.
if (EventSource.IsSupported)
{
System.Diagnostics.Tracing.RuntimeEventSource.Initialize();
}

string? startupHooksVariable = AppContext.GetData("STARTUP_HOOKS") as string;
if (startupHooksVariable == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,4 +644,10 @@
<method name="GetInstanceFieldFieldStore" />
</type>
</assembly>
<assembly fullname="System.Private.CoreLib" feature="System.StartupHookProvider.IsSupported" featurevalue="true" featuredefault="true">
<type fullname="System.StartupHookProvider">
<!-- object.c: mono_runtime_run_startup_hooks -->
<method name="ProcessStartupHooks" />
</type>
</assembly>
</linker>
3 changes: 3 additions & 0 deletions src/mono/mono/metadata/object-internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -2148,4 +2148,7 @@ mono_string_instance_is_interned (MonoString *str);
gpointer
mono_method_get_unmanaged_wrapper_ftnptr_internal (MonoMethod *method, gboolean only_unmanaged_callers_only, MonoError *error);

void
mono_runtime_run_startup_hooks (void);

#endif /* __MONO_OBJECT_INTERNALS_H__ */
19 changes: 19 additions & 0 deletions src/mono/mono/metadata/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -8124,6 +8124,25 @@ mono_runtime_get_managed_cmd_line (void)
return cmd_line ? g_string_free (cmd_line, FALSE) : NULL;
}

void
mono_runtime_run_startup_hooks (void)
{
if (mono_runtime_get_no_exec ())
return;

MonoClass *klass = mono_class_try_load_from_name (mono_defaults.corlib, "System", "StartupHookProvider");
if (!klass)
return; // Linked away
ERROR_DECL (error);
MonoMethod *method = mono_class_get_method_from_name_checked (klass, "ProcessStartupHooks", -1, 0, error);
mono_error_cleanup (error);
if (!method)
return;
mono_runtime_invoke_checked (method, NULL, NULL, error);
// runtime hooks design doc says not to catch exceptions from the hooks
mono_error_raise_exception_deprecated (error);
}

#if NEVER_DEFINED
/*
* The following section is purely to declare prototypes and
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/mini/mini-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -4729,6 +4729,8 @@ mini_init (const char *filename)

MONO_PROFILER_RAISE (runtime_initialized, ());

mono_runtime_run_startup_hooks ();

MONO_VES_INIT_END ();

return domain;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TestRuntime>true</TestRuntime>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<MainLibraryFileName>Android.Device_Emulator.StartupHook.Test.dll</MainLibraryFileName>
<IncludesTestRunner>false</IncludesTestRunner>
<ExpectedExitCode>42</ExpectedExitCode>
<StartupHookSupport>true</StartupHookSupport>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.cs" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\TestAssets\StartupHookForFunctionalTest\StartupHookForFunctionalTest.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

public static class Program
{
public static int Main()
{
string appContextKey = "Test.StartupHookForFunctionalTest.DidRun";
var data = (string) AppContext.GetData (appContextKey);

if (data != "Yes") {
string msg = $"Expected startup hook to set {appContextKey} to 'Yes', got '{data}'";
Console.Error.WriteLine(msg);
return 104;
}
return 42;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"configProperties": {
"STARTUP_HOOKS": "StartupHookForFunctionalTest"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

internal class StartupHook
{
public static void Initialize()
{
AppContext.SetData("Test.StartupHookForFunctionalTest.DidRun", "Yes");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<IsTestProject>false</IsTestProject>
<IsFunctionalTest>false</IsFunctionalTest>
</PropertyGroup>

<ItemGroup>
<Compile Include="StartupHookForFunctionalTest.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.Runtime.CompilerServices;
using System.Runtime.InteropServices.JavaScript;

namespace Sample
{
public partial class Test
{
public static void Main()
{
}

[JSExport]
public static int TestMeaning()
{
string appContextKey = "Test.StartupHookForFunctionalTest.DidRun";
var data = (string) AppContext.GetData (appContextKey);

if (data != "Yes") {
string msg = $"Expected startup hook to set {appContextKey} to 'Yes', got '{data}'";
Console.Error.WriteLine(msg);
return 104;
}
return 42;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TestRuntime>true</TestRuntime>
<Scenario>WasmTestOnBrowser</Scenario>
<TestArchiveTestsRoot>$(TestArchiveRoot)browseronly/</TestArchiveTestsRoot>
<TestArchiveTestsDir>$(TestArchiveTestsRoot)$(OSPlatformConfig)/</TestArchiveTestsDir>
<DefineConstants>$(DefineConstants);TARGET_BROWSER</DefineConstants>
<ExpectedExitCode>42</ExpectedExitCode>
<WasmMainJSPath>main.js</WasmMainJSPath>
<StartupHookSupport>true</StartupHookSupport>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.cs" />
<WasmExtraFilesToDeploy Include="index.html" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\gen\JSImportGenerator\JSImportGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\TestAssets\StartupHookForFunctionalTest\StartupHookForFunctionalTest.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>

<head>
<title>Runtime config test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type='module' src="./main.js"></script>
</head>

<body>
<h3 id="header">Runtime config test</h3>
Answer to the Ultimate Question of Life, the Universe, and Everything is : <span id="out"></span>
</body>

</html>
24 changes: 24 additions & 0 deletions src/tests/FunctionalTests/WebAssembly/Browser/StartupHook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { dotnet } from './dotnet.js'

function wasm_exit(exit_code) {
var tests_done_elem = document.createElement("label");
tests_done_elem.id = "tests_done";
tests_done_elem.innerHTML = exit_code.toString();
document.body.appendChild(tests_done_elem);

console.log(`WASM EXIT ${exit_code}`);
}

try {
const { getAssemblyExports } = await dotnet.create();
const exports = await getAssemblyExports("WebAssembly.Browser.StartupHook.Test.dll");
const testMeaning = exports.Sample.Test.TestMeaning;
const ret = testMeaning();
document.getElementById("out").innerHTML = `${ret}`;
console.debug(`ret: ${ret}`);

let exit_code = ret;
wasm_exit(exit_code);
} catch (err) {
console.log(`WASM ERROR ${err}`);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"configProperties": {
"STARTUP_HOOKS": "StartupHookForFunctionalTest"
}
}
Loading

0 comments on commit 3c3cc44

Please sign in to comment.