Skip to content

Commit 0109ee0

Browse files
author
Mike McLaughlin
committed
Add hot reload apply changes API: AssemblyExtensions.ApplyUpdate
Issue: dotnet#45689 Currently Windows only. Fail hot reload API if debugging.
1 parent 1667fcd commit 0109ee0

File tree

8 files changed

+135
-39
lines changed

8 files changed

+135
-39
lines changed

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,54 @@ 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 int ApplyHotReloadUpdate(QCallAssembly assembly, byte* metadataDelta, int metadataDeltaLength, byte* ilDelta, int ilDeltaLength, byte* pdbDelta, int pdbDeltaLength);
46+
47+
/// <summary>
48+
/// Hot reload update API
49+
/// Applies an update to the given assembly using the metadata, IL and PDB deltas. Currently executing
50+
/// methods will continue to use the existing IL. New executions of modified methods will use the new
51+
/// IL. The supported changes are runtime specific - different .NET runtimes may have different
52+
/// limitations. The runtime makes no guarantees if the delta includes unsupported changes.
53+
/// </summary>
54+
/// <param name="assembly">The assembly to update</param>
55+
/// <param name="metadataDelta">The metadata changes</param>
56+
/// <param name="ilDelta">The IL changes</param>
57+
/// <param name="pdbDelta">The PDB changes. Current not supported on .NET Core</param>
58+
/// <exception cref="ArgumentNullException">if assembly parameter is null</exception>
59+
/// <exception cref="NotSupportedException">update failed</exception>
60+
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta = default)
61+
{
62+
if (assembly == null)
63+
{
64+
throw new ArgumentNullException(nameof(assembly));
65+
}
66+
67+
RuntimeAssembly? runtimeAssembly = assembly as RuntimeAssembly;
68+
if (runtimeAssembly == null)
69+
{
70+
throw new ArgumentException("Not a RuntimeAssembly", nameof(assembly));
71+
}
72+
73+
unsafe
74+
{
75+
RuntimeAssembly rtAsm = runtimeAssembly;
76+
fixed (byte* metadataDeltaPtr = metadataDelta, ilDeltaPtr = ilDelta, pdbDeltaPtr = pdbDelta)
77+
{
78+
if (ApplyHotReloadUpdate(
79+
new QCallAssembly(ref rtAsm),
80+
metadataDeltaPtr,
81+
metadataDelta.Length,
82+
ilDeltaPtr,
83+
ilDelta.Length,
84+
pdbDeltaPtr,
85+
pdbDelta.Length) != 0)
86+
{
87+
throw new NotSupportedException();
88+
}
89+
}
90+
}
91+
}
4392
}
4493
}

src/coreclr/vm/assemblynative.cpp

Lines changed: 56 additions & 0 deletions
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,57 @@ void QCALLTYPE AssemblyNative::TraceSatelliteSubdirectoryPathProbed(LPCWSTR file
14071409

14081410
END_QCALL;
14091411
}
1412+
1413+
// static
1414+
INT32 QCALLTYPE AssemblyNative::ApplyHotReloadUpdate(
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+
INT32 result = E_NOTIMPL;
1426+
1427+
BEGIN_QCALL;
1428+
1429+
_ASSERTE(assembly != nullptr);
1430+
_ASSERTE(metadataDelta != nullptr);
1431+
_ASSERTE(metadataDeltaLength > 0);
1432+
_ASSERTE(ilDelta != nullptr);
1433+
_ASSERTE(ilDeltaLength > 0);
1434+
1435+
#ifdef EnC_SUPPORTED
1436+
GCX_COOP();
1437+
{
1438+
if (!CORDebuggerAttached())
1439+
{
1440+
// Suspend the runtime.
1441+
ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_OTHER);
1442+
1443+
Module* pModule = assembly->GetDomainAssembly()->GetModule();
1444+
if (pModule->IsEditAndContinueEnabled())
1445+
{
1446+
result = ((EditAndContinueModule*)pModule)->ApplyEditAndContinue(metadataDeltaLength, metadataDelta, ilDeltaLength, ilDelta);
1447+
}
1448+
else
1449+
{
1450+
result = E_INVALIDARG;
1451+
}
1452+
1453+
ThreadSuspend::RestartEE(FALSE, TRUE);
1454+
}
1455+
else
1456+
{
1457+
result = E_ACCESSDENIED;
1458+
}
1459+
}
1460+
#endif
1461+
1462+
END_QCALL;
1463+
1464+
return result;
1465+
}

src/coreclr/vm/assemblynative.hpp

Lines changed: 2 additions & 0 deletions
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 INT32 QCALLTYPE ApplyHotReloadUpdate(QCall::AssemblyHandle assembly, UINT8* metadataDelta, INT32 metadataDeltaLength, UINT8* ilDelta, INT32 ilDeltaLength, UINT8* pdbDelta, INT32 pdbDeltaLength);
130132
};
131133

132134
#endif

src/coreclr/vm/ecalllist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ FCFuncEnd()
440440

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

445446
FCFuncStart(gAssemblyLoadContextFuncs)

src/coreclr/vm/encee.cpp

Lines changed: 25 additions & 13 deletions
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

@@ -392,11 +398,14 @@ HRESULT EditAndContinueModule::AddMethod(mdMethodDef token)
392398
}
393399

394400
// 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))
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

Lines changed: 0 additions & 24 deletions
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

Lines changed: 1 addition & 2 deletions
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.Runtime.Loader/ref/System.Runtime.Loader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public static partial class AssemblyExtensions
1010
{
1111
[System.CLSCompliantAttribute(false)]
1212
public unsafe static bool TryGetRawMetadata(this System.Reflection.Assembly assembly, out byte* blob, out int length) { throw null; }
13+
public static void ApplyUpdate(Assembly assembly, ReadOnlySpan<byte> metadataDelta, ReadOnlySpan<byte> ilDelta, ReadOnlySpan<byte> pdbDelta = default) { throw null; }
1314
}
1415
}
1516
namespace System.Runtime.Loader

0 commit comments

Comments
 (0)