Skip to content

Commit 3464c88

Browse files
author
Mike McLaughlin
authored
Add hot reload apply changes API: AssemblyExtensions.ApplyUpdate (#48366)
Add hot reload apply changes API: AssemblyExtensions.ApplyUpdate Issue: #45689 Currently Windows only. Fail hot reload API if debugging. Added some simple invalid parameter testing to the new ApplyUpdate API.
1 parent ae40145 commit 3464c88

File tree

13 files changed

+277
-145
lines changed

13 files changed

+277
-145
lines changed

src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs

+41
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,46 @@ public static unsafe bool TryGetRawMetadata(this Assembly assembly, out byte* bl
4040

4141
return InternalTryGetRawMetadata(new QCallAssembly(ref rtAsm), ref blob, ref length);
4242
}
43+
44+
[DllImport(RuntimeHelpers.QCall)]
45+
private static extern unsafe void ApplyUpdate(QCallAssembly assembly, byte* metadataDelta, int metadataDeltaLength, byte* ilDelta, int ilDeltaLength, byte* pdbDelta, int pdbDeltaLength);
46+
47+
/// <summary>
48+
/// Updates the specified assembly using the provided metadata, IL and PDB deltas.
49+
/// </summary>
50+
/// <remarks>
51+
/// Currently executing methods will continue to use the existing IL. New executions of modified methods will
52+
/// use the new IL. Different runtimes may have different limitations on what kinds of changes are supported,
53+
/// and runtimes make no guarantees as to the state of the assembly and process if the delta includes
54+
/// unsupported changes.
55+
/// </remarks>
56+
/// <param name="assembly">The assembly to update.</param>
57+
/// <param name="metadataDelta">The metadata changes to be applied.</param>
58+
/// <param name="ilDelta">The IL changes to be applied.</param>
59+
/// <param name="pdbDelta">The PDB changes to be applied.</param>
60+
/// <exception cref="ArgumentNullException">The assembly argument is null.</exception>
61+
/// <exception cref="NotSupportedException">The update could not be applied.</exception>
62+
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta)
63+
{
64+
if (assembly == null)
65+
{
66+
throw new ArgumentNullException(nameof(assembly));
67+
}
68+
69+
RuntimeAssembly? runtimeAssembly = assembly as RuntimeAssembly;
70+
if (runtimeAssembly == null)
71+
{
72+
throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);
73+
}
74+
75+
unsafe
76+
{
77+
RuntimeAssembly rtAsm = runtimeAssembly;
78+
fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
79+
{
80+
ApplyUpdate(new QCallAssembly(ref rtAsm), metadataDeltaPtr, metadataDelta.Length, ilDeltaPtr, ilDelta.Length, pdbDeltaPtr, pdbDelta.Length);
81+
}
82+
}
83+
}
4384
}
4485
}

src/coreclr/vm/assemblynative.cpp

+47
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "interoputil.h"
2626
#include "frames.h"
2727
#include "typeparse.h"
28+
#include "encee.h"
29+
#include "threadsuspend.h"
2830

2931
#include "appdomainnative.hpp"
3032
#include "../binder/inc/bindertracing.h"
@@ -1407,3 +1409,48 @@ void QCALLTYPE AssemblyNative::TraceSatelliteSubdirectoryPathProbed(LPCWSTR file
14071409

14081410
END_QCALL;
14091411
}
1412+
1413+
// static
1414+
void QCALLTYPE AssemblyNative::ApplyUpdate(
1415+
QCall::AssemblyHandle assembly,
1416+
UINT8* metadataDelta,
1417+
INT32 metadataDeltaLength,
1418+
UINT8* ilDelta,
1419+
INT32 ilDeltaLength,
1420+
UINT8* pdbDelta,
1421+
INT32 pdbDeltaLength)
1422+
{
1423+
QCALL_CONTRACT;
1424+
1425+
BEGIN_QCALL;
1426+
1427+
_ASSERTE(assembly != nullptr);
1428+
_ASSERTE(metadataDelta != nullptr);
1429+
_ASSERTE(metadataDeltaLength > 0);
1430+
_ASSERTE(ilDelta != nullptr);
1431+
_ASSERTE(ilDeltaLength > 0);
1432+
1433+
#ifdef EnC_SUPPORTED
1434+
GCX_COOP();
1435+
{
1436+
if (CORDebuggerAttached())
1437+
{
1438+
COMPlusThrow(kNotSupportedException);
1439+
}
1440+
Module* pModule = assembly->GetDomainAssembly()->GetModule();
1441+
if (!pModule->IsEditAndContinueEnabled())
1442+
{
1443+
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_AssemblyNotEditable"));
1444+
}
1445+
HRESULT hr = ((EditAndContinueModule*)pModule)->ApplyEditAndContinue(metadataDeltaLength, metadataDelta, ilDeltaLength, ilDelta);
1446+
if (FAILED(hr))
1447+
{
1448+
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_EditFailed"));
1449+
}
1450+
}
1451+
#else
1452+
COMPlusThrow(kNotImplementedException);
1453+
#endif
1454+
1455+
END_QCALL;
1456+
}

src/coreclr/vm/assemblynative.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ class AssemblyNative
127127
static void QCALLTYPE TraceAssemblyResolveHandlerInvoked(LPCWSTR assemblyName, LPCWSTR handlerName, LPCWSTR resultAssemblyName, LPCWSTR resultAssemblyPath);
128128
static void QCALLTYPE TraceAssemblyLoadFromResolveHandlerInvoked(LPCWSTR assemblyName, bool isTrackedAssembly, LPCWSTR requestingAssemblyPath, LPCWSTR requestedAssemblyPath);
129129
static void QCALLTYPE TraceSatelliteSubdirectoryPathProbed(LPCWSTR filePath, HRESULT hr);
130+
131+
static void QCALLTYPE ApplyUpdate(QCall::AssemblyHandle assembly, UINT8* metadataDelta, INT32 metadataDeltaLength, UINT8* ilDelta, INT32 ilDeltaLength, UINT8* pdbDelta, INT32 pdbDeltaLength);
130132
};
131133

132134
#endif

src/coreclr/vm/ecalllist.h

+1
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ FCFuncEnd()
440440

441441
FCFuncStart(gAssemblyExtensionsFuncs)
442442
QCFuncElement("InternalTryGetRawMetadata", AssemblyNative::InternalTryGetRawMetadata)
443+
QCFuncElement("ApplyUpdate", AssemblyNative::ApplyUpdate)
443444
FCFuncEnd()
444445

445446
FCFuncStart(gAssemblyLoadContextFuncs)

src/coreclr/vm/encee.cpp

+26-14
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ HRESULT EditAndContinueModule::ApplyEditAndContinue(
173173
// Ensure the metadata is RW.
174174
EX_TRY
175175
{
176-
// ConvertMetadataToRWForEnC should only ever be called on EnC capable files.
176+
// ConvertMDInternalToReadWrite should only ever be called on EnC capable files.
177177
_ASSERTE(IsEditAndContinueCapable()); // this also checks that the file is EnC capable
178-
GetFile()->ConvertMetadataToRWForEnC();
178+
GetFile()->ConvertMDInternalToReadWrite();
179179
}
180180
EX_CATCH_HRESULT(hr);
181181

@@ -310,10 +310,13 @@ HRESULT EditAndContinueModule::UpdateMethod(MethodDesc *pMethod)
310310
CONTRACTL_END;
311311

312312
// Notify the debugger of the update
313-
HRESULT hr = g_pDebugInterface->UpdateFunction(pMethod, m_applyChangesCount);
314-
if (FAILED(hr))
313+
if (CORDebuggerAttached())
315314
{
316-
return hr;
315+
HRESULT hr = g_pDebugInterface->UpdateFunction(pMethod, m_applyChangesCount);
316+
if (FAILED(hr))
317+
{
318+
return hr;
319+
}
317320
}
318321

319322
// Notify the JIT that we've got new IL for this method
@@ -375,7 +378,10 @@ HRESULT EditAndContinueModule::AddMethod(mdMethodDef token)
375378
// Class isn't loaded yet, don't have to modify any existing EE data structures beyond the metadata.
376379
// Just notify debugger and return.
377380
LOG((LF_ENC, LL_INFO100, "EnCModule::AM class %p not loaded, our work is done\n", parentTypeDef));
378-
hr = g_pDebugInterface->UpdateNotYetLoadedFunction(token, this, m_applyChangesCount);
381+
if (CORDebuggerAttached())
382+
{
383+
hr = g_pDebugInterface->UpdateNotYetLoadedFunction(token, this, m_applyChangesCount);
384+
}
379385
return hr;
380386
}
381387

@@ -391,12 +397,15 @@ HRESULT EditAndContinueModule::AddMethod(mdMethodDef token)
391397
return hr;
392398
}
393399

394-
// Tell the debugger about the new method so it get's the version number properly
395-
hr = g_pDebugInterface->AddFunction(pMethod, m_applyChangesCount);
396-
if (FAILED(hr))
400+
// Tell the debugger about the new method so it gets the version number properly
401+
if (CORDebuggerAttached())
397402
{
398-
_ASSERTE(!"Failed to add function");
399-
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add method %p to debugger with hr 0x%x\n", token));
403+
hr = g_pDebugInterface->AddFunction(pMethod, m_applyChangesCount);
404+
if (FAILED(hr))
405+
{
406+
_ASSERTE(!"Failed to add function");
407+
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add method %p to debugger with hr 0x%x\n", token));
408+
}
400409
}
401410

402411
return hr;
@@ -463,10 +472,13 @@ HRESULT EditAndContinueModule::AddField(mdFieldDef token)
463472
}
464473

465474
// Tell the debugger about the new field
466-
hr = g_pDebugInterface->AddField(pField, m_applyChangesCount);
467-
if (FAILED(hr))
475+
if (CORDebuggerAttached())
468476
{
469-
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add field %p to debugger with hr 0x%x\n", token));
477+
hr = g_pDebugInterface->AddField(pField, m_applyChangesCount);
478+
if (FAILED(hr))
479+
{
480+
LOG((LF_ENC, LL_INFO100000, "**Error** EACM::AF: Failed to add field %p to debugger with hr 0x%x\n", token));
481+
}
470482
}
471483

472484
#ifdef _DEBUG

src/coreclr/vm/pefile.cpp

-24
Original file line numberDiff line numberDiff line change
@@ -747,30 +747,6 @@ void PEFile::ConvertMDInternalToReadWrite()
747747
}
748748
}
749749

750-
void PEFile::ConvertMetadataToRWForEnC()
751-
{
752-
CONTRACTL
753-
{
754-
THROWS;
755-
GC_NOTRIGGER;
756-
MODE_ANY;
757-
}
758-
CONTRACTL_END;
759-
760-
// This should only ever be called on EnC capable files.
761-
// One can check this using Module::IsEditAndContinueCapable().
762-
763-
// This should only be called if we're debugging, stopped, and on the helper thread.
764-
_ASSERTE(CORDebuggerAttached());
765-
_ASSERTE((g_pDebugInterface != NULL) && g_pDebugInterface->ThisIsHelperThread());
766-
_ASSERTE((g_pDebugInterface != NULL) && g_pDebugInterface->IsStopped());
767-
768-
// Convert the metadata to RW for Edit and Continue, properly replacing the metadata import interface pointer and
769-
// properly preserving the old importer. This will be called before the EnC system tries to apply a delta to the module's
770-
// metadata. ConvertMDInternalToReadWrite() does that quite nicely for us.
771-
ConvertMDInternalToReadWrite();
772-
}
773-
774750
void PEFile::OpenMDImport_Unsafe()
775751
{
776752
CONTRACTL

src/coreclr/vm/pefile.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,6 @@ class PEFile
427427
void OpenImporter();
428428
void OpenEmitter();
429429

430-
void ConvertMDInternalToReadWrite();
431430
void ReleaseMetadataInterfaces(BOOL bDestructor, BOOL bKeepNativeData=FALSE);
432431

433432

@@ -536,7 +535,7 @@ class PEFile
536535

537536
static PEFile* Dummy();
538537
void MarkNativeImageInvalidIfOwned();
539-
void ConvertMetadataToRWForEnC();
538+
void ConvertMDInternalToReadWrite();
540539

541540
protected:
542541
PTR_ICLRPrivAssembly m_pHostAssembly;

src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

+6
Original file line numberDiff line numberDiff line change
@@ -3673,4 +3673,10 @@
36733673
<data name="ResourceManager_ReflectionNotAllowed" xml:space="preserve">
36743674
<value>Use of ResourceManager for custom types is disabled. Set the MSBuild Property CustomResourceTypesSupport to true in order to enable it.</value>
36753675
</data>
3676+
<data name="InvalidOperation_AssemblyNotEditable" xml:space="preserve">
3677+
<value>The assembly can not be edited or changed.</value>
3678+
</data>
3679+
<data name="InvalidOperation_EditFailed" xml:space="preserve">
3680+
<value>The assembly update failed.</value>
3681+
</data>
36763682
</root>

0 commit comments

Comments
 (0)