forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[mono] Implement public hot reload API (dotnet#48380)
* [mono] Implement public hot reload API Also add ApplyUpdateSdb that takes byte[] arguments for use with mono/debugger-libs as its awkward to call a ROS<byte> method from the debugger. Contributes to dotnet#45689 * Update src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs Co-authored-by: Marek Safar <[email protected]> * Don't allow changes to System.Private.CoreLib * formatting * more formatting * Add a TODO to remove ApplyUpdateSdb * update DeltaHelper * don't use ApplyUpdateSdb in DeltaHelper * throw InvalidOpertionException for corlib changes with hot reload disabled * Run the test on mono on all platoforms; CoreCLR windows only * Use resource string * Address review feedback Co-authored-by: Marek Safar <[email protected]>
- Loading branch information
1 parent
2e2eccc
commit 629ad50
Showing
9 changed files
with
162 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 56 additions & 1 deletion
57
src/mono/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,68 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Runtime.CompilerServices; | ||
|
||
namespace System.Reflection.Metadata | ||
{ | ||
public static class AssemblyExtensions | ||
{ | ||
[CLSCompliant(false)] | ||
public static unsafe bool TryGetRawMetadata(this Assembly assembly, out byte* blob, out int length) => throw new NotImplementedException(); | ||
|
||
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta) => throw new NotImplementedException(); | ||
/// <summary> | ||
/// Updates the specified assembly using the provided metadata, IL and PDB deltas. | ||
/// </summary> | ||
/// <remarks> | ||
/// Currently executing methods will continue to use the existing IL. New executions of modified methods will | ||
/// use the new IL. Different runtimes may have different limitations on what kinds of changes are supported, | ||
/// and runtimes make no guarantees as to the state of the assembly and process if the delta includes | ||
/// unsupported changes. | ||
/// </remarks> | ||
/// <param name="assembly">The assembly to update.</param> | ||
/// <param name="metadataDelta">The metadata changes to be applied.</param> | ||
/// <param name="ilDelta">The IL changes to be applied.</param> | ||
/// <param name="pdbDelta">The PDB changes to be applied.</param> | ||
/// <exception cref="ArgumentNullException">The assembly argument is null.</exception> | ||
/// <exception cref="NotSupportedException">The update could not be applied.</exception> | ||
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta) | ||
{ | ||
if (assembly is not RuntimeAssembly runtimeAssembly) | ||
{ | ||
if (assembly is null) throw new ArgumentNullException(nameof(assembly)); | ||
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly); | ||
} | ||
|
||
// System.Private.CoreLib is not editable | ||
if (runtimeAssembly == typeof(AssemblyExtensions).Assembly) | ||
throw new InvalidOperationException (SR.InvalidOperation_AssemblyNotEditable); | ||
|
||
#if !FEATURE_METADATA_UPDATE | ||
throw new NotSupportedException (SR.NotSupported_MethodBodyReplacement); | ||
#else | ||
unsafe | ||
{ | ||
IntPtr monoAssembly = runtimeAssembly.GetUnderlyingNativeHandle (); | ||
fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta) | ||
{ | ||
ApplyUpdate_internal(monoAssembly, metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length); | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
internal static void ApplyUpdateSdb(Assembly assembly, byte[] metadataDelta, byte[] ilDelta, byte[]? pdbDelta) | ||
{ | ||
ReadOnlySpan<byte> md = metadataDelta; | ||
ReadOnlySpan<byte> il = ilDelta; | ||
ReadOnlySpan<byte> dpdb = pdbDelta == null ? default : pdbDelta; | ||
ApplyUpdate (assembly, md, il, dpdb); | ||
} | ||
|
||
#if FEATURE_METADATA_UPDATE | ||
[MethodImpl (MethodImplOptions.InternalCall)] | ||
private static unsafe extern void ApplyUpdate_internal (IntPtr base_assm, byte* dmeta_bytes, int dmeta_length, byte *dil_bytes, int dil_length, byte *dpdb_bytes, int dpdb_length); | ||
#endif | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,104 @@ | ||
using System; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.Loader; | ||
using System.Collections.Generic; | ||
|
||
namespace MonoDelta { | ||
public class DeltaHelper { | ||
const string name = "System.Runtime.CompilerServices.RuntimeFeature"; | ||
public class DeltaHelper { | ||
private static Action<Assembly, byte[], byte[], byte[]> _updateMethod; | ||
|
||
private static MethodBase _updateMethod; | ||
private static Action<Assembly, byte[], byte[], byte[]> UpdateMethod => _updateMethod ?? InitUpdateMethod(); | ||
|
||
private static MethodBase UpdateMethod => _updateMethod ?? InitUpdateMethod(); | ||
private static Action<Assembly, byte[], byte[], byte[]> InitUpdateMethod () | ||
{ | ||
var monoType = typeof(System.Reflection.Metadata.AssemblyExtensions); | ||
const string methodName = "ApplyUpdate"; | ||
var mi = monoType.GetMethod (methodName, BindingFlags.Public | BindingFlags.Static); | ||
if (mi == null) | ||
throw new Exception ($"Couldn't get {methodName} from {monoType.FullName}"); | ||
_updateMethod = MakeUpdateMethod (mi); //Delegate.CreateDelegate (typeof(Action<Assembly, byte[], byte[], byte[]>), mi) as Action<Assembly, byte[], byte[], byte[]>; | ||
return _updateMethod; | ||
} | ||
|
||
private static MethodBase InitUpdateMethod () | ||
{ | ||
var monoType = Type.GetType (name, throwOnError: true); | ||
if (monoType == null) | ||
throw new Exception ($"Couldn't get the type {name}"); | ||
_updateMethod = monoType.GetMethod ("LoadMetadataUpdate", BindingFlags.NonPublic | BindingFlags.Static); | ||
if (_updateMethod == null) | ||
throw new Exception ($"Couldn't get LoadMetadataUpdate from {name}"); | ||
return _updateMethod; | ||
} | ||
private static Action<Assembly, byte[], byte[], byte[]> MakeUpdateMethod (MethodInfo applyUpdate) | ||
{ | ||
// Make | ||
// void ApplyUpdateArray (Assembly a, byte[] dmeta, byte[] dil, byte[] dpdb) | ||
// { | ||
// ApplyUpdate (a, (ReadOnlySpan<byte>)dmeta, (ReadOnlySpan<byte>)dil, (ReadOnlySpan<byte>)dpdb); | ||
// } | ||
var dm = new DynamicMethod ("CallApplyUpdate", typeof(void), new Type[] { typeof(Assembly), typeof(byte[]), typeof(byte[]), typeof(byte[])}, typeof (DeltaHelper).Module); | ||
var ilg = dm.GetILGenerator (); | ||
var conv = typeof(ReadOnlySpan<byte>).GetMethod("op_Implicit", new Type[] {typeof(byte[])}); | ||
|
||
private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data) | ||
{ | ||
UpdateMethod.Invoke (null, new object [] { assm, dmeta_data, dil_data}); | ||
} | ||
ilg.Emit (OpCodes.Ldarg_0); | ||
ilg.Emit (OpCodes.Ldarg_1); | ||
ilg.Emit (OpCodes.Call, conv); | ||
ilg.Emit (OpCodes.Ldarg_2); | ||
ilg.Emit (OpCodes.Call, conv); | ||
ilg.Emit (OpCodes.Ldarg_3); | ||
ilg.Emit (OpCodes.Call, conv); | ||
ilg.Emit (OpCodes.Call, applyUpdate); | ||
ilg.Emit (OpCodes.Ret); | ||
|
||
DeltaHelper () { } | ||
return dm.CreateDelegate(typeof(Action<Assembly, byte[], byte[], byte[]>)) as Action<Assembly, byte[], byte[], byte[]>; | ||
} | ||
|
||
public static DeltaHelper Make () | ||
{ | ||
return new DeltaHelper (); | ||
} | ||
private static void LoadMetadataUpdate (Assembly assm, byte[] dmeta_data, byte[] dil_data, byte[] dpdb_data) | ||
{ | ||
UpdateMethod (assm, dmeta_data, dil_data, dpdb_data); | ||
} | ||
|
||
public static void InjectUpdate (string assemblyName, string dmeta_base64, string dil_base64) { | ||
var an = new AssemblyName (assemblyName); | ||
Assembly assm = null; | ||
/* TODO: non-default ALCs */ | ||
foreach (var candidate in AssemblyLoadContext.Default.Assemblies) { | ||
if (candidate.GetName().Name == an.Name) { | ||
assm = candidate; | ||
break; | ||
} | ||
} | ||
if (assm == null) | ||
throw new ArgumentException ("assemblyName"); | ||
var dmeta_data = Convert.FromBase64String (dmeta_base64); | ||
var dil_data = Convert.FromBase64String (dil_base64); | ||
LoadMetadataUpdate (assm, dmeta_data, dil_data); | ||
} | ||
DeltaHelper () { } | ||
|
||
private Dictionary<Assembly, int> assembly_count = new Dictionary<Assembly, int> (); | ||
public static DeltaHelper Make () | ||
{ | ||
return new DeltaHelper (); | ||
} | ||
|
||
public void Update (Assembly assm) { | ||
int count; | ||
if (!assembly_count.TryGetValue (assm, out count)) | ||
count = 1; | ||
else | ||
count++; | ||
assembly_count [assm] = count; | ||
public static void InjectUpdate (string assemblyName, string dmeta_base64, string dil_base64) { | ||
var an = new AssemblyName (assemblyName); | ||
Assembly assm = null; | ||
/* TODO: non-default ALCs */ | ||
foreach (var candidate in AssemblyLoadContext.Default.Assemblies) { | ||
if (candidate.GetName().Name == an.Name) { | ||
assm = candidate; | ||
break; | ||
} | ||
} | ||
if (assm == null) | ||
throw new ArgumentException ("assemblyName"); | ||
var dmeta_data = Convert.FromBase64String (dmeta_base64); | ||
var dil_data = Convert.FromBase64String (dil_base64); | ||
byte[] dpdb_data = null; | ||
LoadMetadataUpdate (assm, dmeta_data, dil_data, dpdb_data); | ||
} | ||
|
||
/* FIXME WASM: Location is empty on wasm. Make up a name based on Name */ | ||
string basename = assm.Location; | ||
if (basename == "") | ||
basename = assm.GetName().Name + ".dll"; | ||
Console.WriteLine ($"Apply Delta Update for {basename}, revision {count}"); | ||
private Dictionary<Assembly, int> assembly_count = new Dictionary<Assembly, int> (); | ||
|
||
string dmeta_name = $"{basename}.{count}.dmeta"; | ||
string dil_name = $"{basename}.{count}.dil"; | ||
byte[] dmeta_data = System.IO.File.ReadAllBytes (dmeta_name); | ||
byte[] dil_data = System.IO.File.ReadAllBytes (dil_name); | ||
public void Update (Assembly assm) { | ||
int count; | ||
if (!assembly_count.TryGetValue (assm, out count)) | ||
count = 1; | ||
else | ||
count++; | ||
assembly_count [assm] = count; | ||
|
||
LoadMetadataUpdate (assm, dmeta_data, dil_data); | ||
} | ||
} | ||
/* FIXME WASM: Location is empty on wasm. Make up a name based on Name */ | ||
string basename = assm.Location; | ||
if (basename == "") | ||
basename = assm.GetName().Name + ".dll"; | ||
Console.WriteLine ($"Apply Delta Update for {basename}, revision {count}"); | ||
|
||
string dmeta_name = $"{basename}.{count}.dmeta"; | ||
string dil_name = $"{basename}.{count}.dil"; | ||
byte[] dmeta_data = System.IO.File.ReadAllBytes (dmeta_name); | ||
byte[] dil_data = System.IO.File.ReadAllBytes (dil_name); | ||
byte[] dpdb_data = null; // TODO also use the dpdb data | ||
|
||
LoadMetadataUpdate (assm, dmeta_data, dil_data, dpdb_data); | ||
} | ||
} | ||
} |