未验证 提交 3464c888 编写于 作者: M Mike McLaughlin 提交者: GitHub

Add hot reload apply changes API: AssemblyExtensions.ApplyUpdate (#48366)

Add hot reload apply changes API: AssemblyExtensions.ApplyUpdate

Issue: https://github.com/dotnet/runtime/issues/45689

Currently Windows only. Fail hot reload API if debugging.

Added some simple invalid parameter testing to the new ApplyUpdate API. 
上级 ae401454
......@@ -40,5 +40,46 @@ public static unsafe bool TryGetRawMetadata(this Assembly assembly, out byte* bl
return InternalTryGetRawMetadata(new QCallAssembly(ref rtAsm), ref blob, ref length);
}
[DllImport(RuntimeHelpers.QCall)]
private static extern unsafe void ApplyUpdate(QCallAssembly assembly, byte* metadataDelta, int metadataDeltaLength, byte* ilDelta, int ilDeltaLength, byte* pdbDelta, int pdbDeltaLength);
/// <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 == null)
{
throw new ArgumentNullException(nameof(assembly));
}
RuntimeAssembly? runtimeAssembly = assembly as RuntimeAssembly;
if (runtimeAssembly == null)
{
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
}
unsafe
{
RuntimeAssembly rtAsm = runtimeAssembly;
fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
{
ApplyUpdate(new QCallAssembly(ref rtAsm), metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length);
}
}
}
}
}
......@@ -25,6 +25,8 @@
#include "interoputil.h"
#include "frames.h"
#include "typeparse.h"
#include "encee.h"
#include "threadsuspend.h"
#include "appdomainnative.hpp"
#include "../binder/inc/bindertracing.h"
......@@ -1407,3 +1409,48 @@ void QCALLTYPE AssemblyNative::TraceSatelliteSubdirectoryPathProbed(LPCWSTR file
END_QCALL;
}
// static
void QCALLTYPE AssemblyNative::ApplyUpdate(
QCall::AssemblyHandle assembly,
UINT8* metadataDelta,
INT32 metadataDeltaLength,
UINT8* ilDelta,
INT32 ilDeltaLength,
UINT8* pdbDelta,
INT32 pdbDeltaLength)
{
QCALL_CONTRACT;
BEGIN_QCALL;
_ASSERTE(assembly != nullptr);
_ASSERTE(metadataDelta != nullptr);
_ASSERTE(metadataDeltaLength > 0);
_ASSERTE(ilDelta != nullptr);
_ASSERTE(ilDeltaLength > 0);
#ifdef EnC_SUPPORTED
GCX_COOP();
{
if (CORDebuggerAttached())
{
COMPlusThrow(kNotSupportedException);
}
Module* pModule = assembly->GetDomainAssembly()->GetModule();
if (!pModule->IsEditAndContinueEnabled())
{
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_AssemblyNotEditable"));
}
HRESULT hr = ((EditAndContinueModule*)pModule)->ApplyEditAndContinue(metadataDeltaLength, metadataDelta, ilDeltaLength, ilDelta);
if (FAILED(hr))
{
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_EditFailed"));
}
}
#else
COMPlusThrow(kNotImplementedException);
#endif
END_QCALL;
}
......@@ -127,6 +127,8 @@ public:
static void QCALLTYPE TraceAssemblyResolveHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
static void QCALLTYPE TraceAssemblyLoadFromResolveHandlerInvoked(LPCWSTR assemblyName, bool isTrackedAssembly, LPCWSTR requestingAssemblyPath, LPCWSTR requestedAssemblyPath);
static void QCALLTYPE TraceSatelliteSubdirectoryPathProbed(LPCWSTR filePath, HRESULT hr);
static void QCALLTYPE ApplyUpdate(QCall::AssemblyHandle assembly, UINT8* metadataDelta, INT32 metadataDeltaLength, UINT8* ilDelta, INT32 ilDeltaLength, UINT8* pdbDelta, INT32 pdbDeltaLength);
};
#endif
......
......@@ -440,6 +440,7 @@ FCFuncEnd()
FCFuncStart(gAssemblyExtensionsFuncs)
QCFuncElement("InternalTryGetRawMetadata", AssemblyNative::InternalTryGetRawMetadata)
QCFuncElement("ApplyUpdate", AssemblyNative::ApplyUpdate)
FCFuncEnd()
FCFuncStart(gAssemblyLoadContextFuncs)
......
......@@ -173,9 +173,9 @@ HRESULT EditAndContinueModule::ApplyEditAndContinue(
// Ensure the metadata is RW.
EX_TRY
{
// ConvertMetadataToRWForEnC should only ever be called on EnC capable files.
// ConvertMDInternalToReadWrite should only ever be called on EnC capable files.
_ASSERTE(IsEditAndContinueCapable()); // this also checks that the file is EnC capable
GetFile()->ConvertMetadataToRWForEnC();
GetFile()->ConvertMDInternalToReadWrite();
}
EX_CATCH_HRESULT(hr);
......@@ -310,10 +310,13 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod)
CONTRACTL_END;
// Notify the debugger of the update
HRESULT hr = g_pDebugInterface->UpdateFunction(pMethod, m_applyChangesCount);
if (FAILED(hr))
if (CORDebuggerAttached())
{
return hr;
HRESULT hr = g_pDebugInterface->UpdateFunction(pMethod, m_applyChangesCount);
if (FAILED(hr))
{
return hr;
}
}
// Notify the JIT that we've got new IL for this method
......@@ -375,7 +378,10 @@ HRESULT EditAndContinueModule::AddMethod(mdMethodDef token)
// Class isn't loaded yet, don't have to modify any existing EE data structures beyond the metadata.
// Just notify debugger and return.
LOG((LF_ENC, LL_INFO100, "EnCModule::AM class %p not loaded, our work is done\n", parentTypeDef));
hr = g_pDebugInterface->UpdateNotYetLoadedFunction(token, this, m_applyChangesCount);
if (CORDebuggerAttached())
{
hr = g_pDebugInterface->UpdateNotYetLoadedFunction(token, this, m_applyChangesCount);
}
return hr;
}
......@@ -391,12 +397,15 @@ HRESULT EditAndContinueModule::AddMethod(mdMethodDef token)
return hr;
}
// Tell the debugger about the new method so it get's the version number properly
hr = g_pDebugInterface->AddFunction(pMethod, m_applyChangesCount);
if (FAILED(hr))
// Tell the debugger about the new method so it gets the version number properly
if (CORDebuggerAttached())
{
_ASSERTE(!"Failed to add function");
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add method %p to debugger with hr 0x%x\n", token));
hr = g_pDebugInterface->AddFunction(pMethod, m_applyChangesCount);
if (FAILED(hr))
{
_ASSERTE(!"Failed to add function");
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add method %p to debugger with hr 0x%x\n", token));
}
}
return hr;
......@@ -463,10 +472,13 @@ HRESULT EditAndContinueModule::AddField(mdFieldDef token)
}
// Tell the debugger about the new field
hr = g_pDebugInterface->AddField(pField, m_applyChangesCount);
if (FAILED(hr))
if (CORDebuggerAttached())
{
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add field %p to debugger with hr 0x%x\n", token));
hr = g_pDebugInterface->AddField(pField, m_applyChangesCount);
if (FAILED(hr))
{
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add field %p to debugger with hr 0x%x\n", token));
}
}
#ifdef _DEBUG
......
......@@ -747,30 +747,6 @@ void PEFile::ConvertMDInternalToReadWrite()
}
}
void PEFile::ConvertMetadataToRWForEnC()
{
CONTRACTL
{
THROWS;
GC_NOTRIGGER;
MODE_ANY;
}
CONTRACTL_END;
// This should only ever be called on EnC capable files.
// One can check this using Module::IsEditAndContinueCapable().
// This should only be called if we're debugging, stopped, and on the helper thread.
_ASSERTE(CORDebuggerAttached());
_ASSERTE((g_pDebugInterface != NULL) && g_pDebugInterface->ThisIsHelperThread());
_ASSERTE((g_pDebugInterface != NULL) && g_pDebugInterface->IsStopped());
// Convert the metadata to RW for Edit and Continue, properly replacing the metadata import interface pointer and
// properly preserving the old importer. This will be called before the EnC system tries to apply a delta to the module's
// metadata. ConvertMDInternalToReadWrite() does that quite nicely for us.
ConvertMDInternalToReadWrite();
}
void PEFile::OpenMDImport_Unsafe()
{
CONTRACTL
......
......@@ -427,7 +427,6 @@ protected:
void OpenImporter();
void OpenEmitter();
void ConvertMDInternalToReadWrite();
void ReleaseMetadataInterfaces(BOOL bDestructor, BOOL bKeepNativeData=FALSE);
......@@ -536,7 +535,7 @@ public:
static PEFile* Dummy();
void MarkNativeImageInvalidIfOwned();
void ConvertMetadataToRWForEnC();
void ConvertMDInternalToReadWrite();
protected:
PTR_ICLRPrivAssembly m_pHostAssembly;
......
......@@ -3673,4 +3673,10 @@
<data name="ResourceManager_ReflectionNotAllowed" xml:space="preserve">
<value>Use of ResourceManager for custom types is disabled. Set the MSBuild Property CustomResourceTypesSupport to true in order to enable it.</value>
</data>
<data name="InvalidOperation_AssemblyNotEditable" xml:space="preserve">
<value>The assembly can not be edited or changed.</value>
</data>
<data name="InvalidOperation_EditFailed" xml:space="preserve">
<value>The assembly update failed.</value>
</data>
</root>
\ No newline at end of file
......@@ -10,6 +10,7 @@ public static partial class AssemblyExtensions
{
[System.CLSCompliantAttribute(false)]
public unsafe static bool TryGetRawMetadata(this System.Reflection.Assembly assembly, out byte* blob, out int length) { throw null; }
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta) { throw null; }
}
}
namespace System.Runtime.Loader
......
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Xunit;
namespace System.Reflection.Metadata
{
public class AssemblyExtensionsTest
{
class NonRuntimeAssembly : Assembly
{
}
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45689", platforms: TestPlatforms.AnyUnix, runtimes: TestRuntimes.Mono)]
[PlatformSpecific(TestPlatforms.Windows)]
[SkipOnMono("Not yet implemented on Mono")]
public static void ApplyUpdateInvalidParameters()
{
// Dummy delta arrays
var metadataDelta = new byte[20];
var ilDelta = new byte[20];
// Assembly can't be null
Assert.Throws<ArgumentNullException>("assembly", () =>
AssemblyExtensions.ApplyUpdate(null, new ReadOnlySpan<byte>(metadataDelta), new ReadOnlySpan<byte>(ilDelta), ReadOnlySpan<byte>.Empty));
// Tests fail on non-runtime assemblies
Assert.Throws<ArgumentException>(() =>
AssemblyExtensions.ApplyUpdate(new NonRuntimeAssembly(), new ReadOnlySpan<byte>(metadataDelta), new ReadOnlySpan<byte>(ilDelta), ReadOnlySpan<byte>.Empty));
// Tests that this assembly isn't not editable
Assert.Throws<InvalidOperationException>(() =>
AssemblyExtensions.ApplyUpdate(typeof(AssemblyExtensions).Assembly, new ReadOnlySpan<byte>(metadataDelta), new ReadOnlySpan<byte>(ilDelta), ReadOnlySpan<byte>.Empty));
}
}
}
......@@ -8,6 +8,7 @@
<GenerateDependencyFile>false</GenerateDependencyFile>
</PropertyGroup>
<ItemGroup>
<Compile Include="AssemblyExtensionsTest.cs" />
<Compile Include="AssemblyLoadContextTest.cs" />
<Compile Include="CollectibleAssemblyLoadContextTest.cs" />
<Compile Include="ContextualReflection.cs" />
......
......@@ -7,5 +7,7 @@ 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();
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册