%define sdktoolsdir /home/owner/share/tmp/sdk_tools
%define install_prefix /usr
%define sdk_install_prefix /home/owner/share/tmp/sdk_tools/netcoredbg
+%define netcoreapp_hotreload_min_ver %{_datarootdir}/%{netcoreapp}6.0.0
%ifarch x86_64
%define ARCH AMD64
export NETCOREAPPDIR=$(if [ -d %{_datarootdir}/%{netcoreappalias} ]; then echo %{_datarootdir}/%{netcoreappalias}; else find %{_datarootdir}/%{netcoreapp} -maxdepth 1 -type d -name '[0-9]*' -print | sort -n | tail -1; fi)
+if [ -d %{netcoreapp_hotreload_min_ver} ]; then
+ STARTUP_HOOK=%{sdk_install_prefix}/ncdbhook.dll
+fi
+
mkdir build
cd build
cmake .. \
-DCLR_CMAKE_LINUX_ID=tizen \
-DDBGSHIM_RUNTIME_DIR=$NETCOREAPPDIR \
-DBUILD_MANAGED=OFF \
+ -DNCDB_DOTNET_STARTUP_HOOK=$STARTUP_HOOK \
-DBUILD_TESTING=%{build_testing}
make %{?jobs:-j%jobs} %{?verbose:VERBOSE=1}
%define _dotnet_build_conf %{build_type}
%dotnet_build -s ../packaging/pkgs ../src/managed
+# Build Debug only, code must be not optimized.
+if [ -d %{netcoreapp_hotreload_min_ver} ]; then
+ NCDB_HOOK_TargetFramework=net6.0 dotnet build -c Debug ../src/ncdbhook
+fi
%install
cd build
mkdir -p %{buildroot}%{sdk_install_prefix}
mv %{buildroot}%{install_prefix}/netcoredbg %{buildroot}%{sdk_install_prefix}
install -p -m 644 ../src/managed/bin/*/*/ManagedPart.dll %{buildroot}%{sdk_install_prefix}
+if [ -d %{netcoreapp_hotreload_min_ver} ]; then
+ install -p -m 644 ../src/ncdbhook/bin/*/*/ncdbhook.dll %{buildroot}%{sdk_install_prefix}
+fi
export CSVER=$(ls /nuget/microsoft.codeanalysis.common.*.nupkg | sort -n | tail -1 | cut -d "." -f4-6)
export SYSCODEPAGES=$(ls /nuget/system.text.encoding.codepages.4.*.nupkg | sort -n | tail -1)
set(netcoredbg_SRC
debugger/breakpoint_break.cpp
debugger/breakpoint_entry.cpp
+ debugger/breakpoint_hotreload.cpp
debugger/breakpoints_exception.cpp
debugger/breakpoints_func.cpp
debugger/breakpoints_line.cpp
debugger/evalwaiter.cpp
debugger/evalutils.cpp
debugger/frames.cpp
+ debugger/hotreloadhelpers.cpp
debugger/managedcallback.cpp
debugger/manageddebugger.cpp
debugger/threads.cpp
target_link_libraries(netcoredbg corguids dl pthread linenoise)
endif()
+if (NCDB_DOTNET_STARTUP_HOOK)
+ add_definitions(-DNCDB_DOTNET_STARTUP_HOOK="${NCDB_DOTNET_STARTUP_HOOK}")
+endif (NCDB_DOTNET_STARTUP_HOOK)
+
if (CLR_CMAKE_TARGET_TIZEN_LINUX)
add_definitions(-DDEBUGGER_FOR_TIZEN)
target_link_libraries(netcoredbg dlog)
install(FILES ${DLLS_TO_DEPLOY} DESTINATION ${CMAKE_INSTALL_PREFIX})
endif()
+# Build Hot Reload managed dll (ncdbhook.dll)
+
+if (BUILD_MANAGED AND NCDB_DOTNET_STARTUP_HOOK)
+ set(NCDBHOOK_PROJECT ${CMAKE_CURRENT_SOURCE_DIR}/ncdbhook/ncdbhook.csproj)
+ set(NCDBHOOK_DLL_NAME ncdbhook.dll)
+ set(NCDBHOOK_DOTNET_BUILD_RESULT ${CMAKE_CURRENT_BINARY_DIR}/${NCDBHOOK_DLL_NAME})
+ set(NCDB_HOOK_TargetFramework "net6.0")
+
+ find_program(DOTNETCLI dotnet PATHS "${DOTNET_DIR}" ENV PATH NO_DEFAULT_PATH)
+
+ # Build Debug only, code must be not optimized.
+ add_custom_command(OUTPUT ${NCDBHOOK_DOTNET_BUILD_RESULT}
+ COMMAND NCDB_HOOK_TargetFramework=${NCDB_HOOK_TargetFramework} ${DOTNETCLI} publish ${NCDBHOOK_PROJECT} -c Debug -o ${CMAKE_CURRENT_BINARY_DIR} /p:BaseIntermediateOutputPath=${CMAKE_CURRENT_BINARY_DIR}/obj/ /p:BaseOutputPath=${CMAKE_CURRENT_BINARY_DIR}/bin/
+ WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/ncdbhook/*.cs" "${NCDBHOOK_PROJECT}"
+ COMMENT "Compiling ${NCDBHOOK_DLL_NAME}"
+ VERBATIM
+ )
+
+ add_custom_target(ncdbhook_dll ALL DEPENDS ${NCDBHOOK_DOTNET_BUILD_RESULT})
+
+ install(FILES ${NCDBHOOK_DOTNET_BUILD_RESULT} DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif()
+
# documentation
option(BUILD_DOC "Build documentation" OFF)
if (BUILD_DOC)
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD\r
+// Distributed under the MIT License.\r
+// See the LICENSE file in the project root for more information.\r
+\r
+#include "debugger/breakpoint_hotreload.h"\r
+#include "debugger/breakpointutils.h"\r
+#include "debugger/hotreloadhelpers.h"\r
+#include "debugger/evalhelpers.h"\r
+#include "metadata/modules.h"\r
+\r
+namespace netcoredbg\r
+{\r
+\r
+HRESULT HotReloadBreakpoint::SetHotReloadBreakpoint()\r
+{\r
+#ifdef NCDB_DOTNET_STARTUP_HOOK\r
+\r
+ std::lock_guard<std::mutex> lock(m_reloadMutex);\r
+\r
+ if (!m_iCorFunc)\r
+ return E_FAIL;\r
+\r
+ HRESULT Status;\r
+ IfFailRet(m_iCorFunc->CreateBreakpoint(&m_iCorFuncBreakpoint));\r
+ IfFailRet(m_iCorFuncBreakpoint->Activate(TRUE));\r
+\r
+ return S_OK;\r
+\r
+#else // NCDB_DOTNET_STARTUP_HOOK\r
+\r
+ return E_NOTIMPL;\r
+\r
+#endif // NCDB_DOTNET_STARTUP_HOOK\r
+}\r
+\r
+HRESULT HotReloadBreakpoint::ManagedCallbackLoadModuleAll(ICorDebugModule *pModule)\r
+{\r
+#ifdef NCDB_DOTNET_STARTUP_HOOK\r
+\r
+ static std::string dllName(NCDB_DOTNET_STARTUP_HOOK);\r
+\r
+ std::lock_guard<std::mutex> lock(m_reloadMutex);\r
+\r
+ if (dllName != GetModuleFileName(pModule))\r
+ return S_OK;\r
+\r
+ HRESULT Status;\r
+ static const WCHAR className[] = W("StartupHook");\r
+ static const WCHAR methodName[] = W("ncdbfunc");\r
+ IfFailRet(FindFunction(pModule, className, methodName, &m_iCorFunc));\r
+\r
+#endif // NCDB_DOTNET_STARTUP_HOOK\r
+\r
+ return S_OK;\r
+}\r
+\r
+HRESULT HotReloadBreakpoint::CheckApplicationReload(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)\r
+{\r
+ std::lock_guard<std::mutex> lock(m_reloadMutex);\r
+\r
+ if (!m_iCorFuncBreakpoint)\r
+ return S_FALSE; // S_FALSE - no error, but not affect on callback\r
+\r
+ HRESULT Status;\r
+ HRESULT ReturnStatus;\r
+ ToRelease<ICorDebugFunctionBreakpoint> pFunctionBreakpoint;\r
+ IfFailRet(pBreakpoint->QueryInterface(IID_ICorDebugFunctionBreakpoint, (LPVOID*) &pFunctionBreakpoint));\r
+ if (FAILED(BreakpointUtils::IsSameFunctionBreakpoint(pFunctionBreakpoint, m_iCorFuncBreakpoint)))\r
+ ReturnStatus = S_FALSE; // Probably, we stopped on other breakpoint, need check this and emit event.\r
+ else\r
+ ReturnStatus = S_OK;\r
+\r
+ IfFailRet(HotReloadHelpers::UpdateApplication(pThread, m_sharedModules.get(), m_sharedEvaluator.get(), m_sharedEvalHelpers.get()));\r
+\r
+ m_iCorFuncBreakpoint->Activate(FALSE);\r
+ m_iCorFuncBreakpoint.Free();\r
+\r
+ return ReturnStatus;\r
+}\r
+\r
+void HotReloadBreakpoint::CheckApplicationReload(ICorDebugThread *pThread)\r
+{\r
+ std::lock_guard<std::mutex> lock(m_reloadMutex);\r
+\r
+ if (!m_iCorFuncBreakpoint)\r
+ return;\r
+\r
+ HotReloadHelpers::UpdateApplication(pThread, m_sharedModules.get(), m_sharedEvaluator.get(), m_sharedEvalHelpers.get());\r
+\r
+ m_iCorFuncBreakpoint->Activate(FALSE);\r
+ m_iCorFuncBreakpoint.Free();\r
+}\r
+\r
+void HotReloadBreakpoint::Delete()\r
+{\r
+ std::lock_guard<std::mutex> lock(m_reloadMutex);\r
+\r
+ if (!m_iCorFuncBreakpoint)\r
+ return;\r
+\r
+ m_iCorFuncBreakpoint->Activate(FALSE);\r
+ m_iCorFuncBreakpoint.Free();\r
+}\r
+\r
+} // namespace netcoredbg\r
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD\r
+// Distributed under the MIT License.\r
+// See the LICENSE file in the project root for more information.\r
+\r
+#pragma once\r
+\r
+#include "cor.h"\r
+#include "cordebug.h"\r
+\r
+#include <mutex>\r
+#include <memory>\r
+#include "utils/torelease.h"\r
+\r
+namespace netcoredbg\r
+{\r
+\r
+class Modules;\r
+class Evaluator;\r
+class EvalHelpers;\r
+\r
+class HotReloadBreakpoint\r
+{\r
+public:\r
+\r
+ HotReloadBreakpoint(std::shared_ptr<Modules> &sharedModules, std::shared_ptr<Evaluator> &sharedEvaluator, std::shared_ptr<EvalHelpers> &sharedEvalHelpers) :\r
+ m_sharedModules(sharedModules),\r
+ m_sharedEvaluator(sharedEvaluator),\r
+ m_sharedEvalHelpers(sharedEvalHelpers)\r
+ {}\r
+\r
+ HRESULT SetHotReloadBreakpoint();\r
+ void Delete();\r
+\r
+ // Important! Must provide succeeded return code:\r
+ // S_OK - internal HotReload breakpoint hit\r
+ // S_FALSE - not internal HotReload breakpoint hit\r
+ HRESULT CheckApplicationReload(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint);\r
+ void CheckApplicationReload(ICorDebugThread *pThread);\r
+\r
+ HRESULT ManagedCallbackLoadModuleAll(ICorDebugModule *pModule);\r
+\r
+private:\r
+\r
+ std::mutex m_reloadMutex;\r
+ std::shared_ptr<Modules> m_sharedModules;\r
+ std::shared_ptr<Evaluator> m_sharedEvaluator;\r
+ std::shared_ptr<EvalHelpers> m_sharedEvalHelpers;\r
+ ToRelease<ICorDebugFunction> m_iCorFunc;\r
+ ToRelease<ICorDebugFunctionBreakpoint> m_iCorFuncBreakpoint;\r
+\r
+};\r
+\r
+} // namespace netcoredbg\r
#include "debugger/breakpoints_exception.h"
#include "debugger/breakpoints_func.h"
#include "debugger/breakpoints_line.h"
+#include "debugger/breakpoint_hotreload.h"
#include "debugger/breakpoints.h"
#include "debugger/breakpointutils.h"
m_uniqueFuncBreakpoints->DeleteAll();
m_uniqueLineBreakpoints->DeleteAll();
m_uniqueExceptionBreakpoints->DeleteAll();
+ m_uniqueHotReloadBreakpoint->Delete();
}
HRESULT Breakpoints::DisableAll(ICorDebugProcess *pProcess)
return S_OK;
}
+HRESULT Breakpoints::ManagedCallbackLoadModuleAll(ICorDebugModule *pModule)
+{
+ m_uniqueHotReloadBreakpoint->ManagedCallbackLoadModuleAll(pModule);
+ return S_OK;
+}
+
HRESULT Breakpoints::ManagedCallbackException(ICorDebugThread *pThread, ExceptionCallbackType eventType, const std::string &excModule, StoppedEvent &event)
{
return m_uniqueExceptionBreakpoints->ManagedCallbackException(pThread, eventType, excModule, event);
return m_uniqueExceptionBreakpoints->ManagedCallbackExitThread(pThread);
}
+HRESULT Breakpoints::CheckApplicationReload(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)
+{
+ return m_uniqueHotReloadBreakpoint->CheckApplicationReload(pThread, pBreakpoint);
+}
+
+void Breakpoints::CheckApplicationReload(ICorDebugThread *pThread)
+{
+ m_uniqueHotReloadBreakpoint->CheckApplicationReload(pThread);
+}
+
+HRESULT Breakpoints::SetHotReloadBreakpoint()
+{
+ return m_uniqueHotReloadBreakpoint->SetHotReloadBreakpoint();
+}
+
} // namespace netcoredbg
class ExceptionBreakpoints;
class FuncBreakpoints;
class LineBreakpoints;
+class HotReloadBreakpoint;
class Breakpoints
{
public:
- Breakpoints(std::shared_ptr<Modules> &sharedModules, std::shared_ptr<Evaluator> &sharedEvaluator, std::shared_ptr<Variables> &sharedVariables) :
+ Breakpoints(std::shared_ptr<Modules> &sharedModules, std::shared_ptr<Evaluator> &sharedEvaluator, std::shared_ptr<EvalHelpers> &sharedEvalHelpers, std::shared_ptr<Variables> &sharedVariables) :
m_uniqueBreakBreakpoint(new BreakBreakpoint(sharedModules)),
m_uniqueEntryBreakpoint(new EntryBreakpoint(sharedModules)),
m_uniqueExceptionBreakpoints(new ExceptionBreakpoints(sharedEvaluator)),
m_uniqueFuncBreakpoints(new FuncBreakpoints(sharedModules, sharedVariables)),
m_uniqueLineBreakpoints(new LineBreakpoints(sharedModules, sharedVariables)),
+ m_uniqueHotReloadBreakpoint(new HotReloadBreakpoint(sharedModules, sharedEvaluator, sharedEvalHelpers)),
m_nextBreakpointId(1)
{}
HRESULT SetFuncBreakpoints(bool haveProcess, const std::vector<FuncBreakpoint> &funcBreakpoints, std::vector<Breakpoint> &breakpoints);
HRESULT SetLineBreakpoints(bool haveProcess, const std::string &filename, const std::vector<LineBreakpoint> &lineBreakpoints, std::vector<Breakpoint> &breakpoints);
HRESULT SetExceptionBreakpoints(const std::vector<ExceptionBreakpoint> &exceptionBreakpoints, std::vector<Breakpoint> &breakpoints);
+ HRESULT SetHotReloadBreakpoint();
HRESULT UpdateBreakpointsOnHotReload(ICorDebugModule *pModule, std::unordered_set<mdMethodDef> &methodTokens, std::vector<BreakpointEvent> &events);
HRESULT GetExceptionInfo(ICorDebugThread *pThread, ExceptionInfo &exceptionInfo);
HRESULT ManagedCallbackBreakpoint(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint, Breakpoint &breakpoint, bool &atEntry);
HRESULT ManagedCallbackException(ICorDebugThread *pThread, ExceptionCallbackType eventType, const std::string &excModule, StoppedEvent &event);
HRESULT ManagedCallbackLoadModule(ICorDebugModule *pModule, std::vector<BreakpointEvent> &events);
+ HRESULT ManagedCallbackLoadModuleAll(ICorDebugModule *pModule);
HRESULT ManagedCallbackExitThread(ICorDebugThread *pThread);
+ // S_OK - internal HotReload breakpoint hit
+ // S_FALSE - not internal HotReload breakpoint hit
+ HRESULT CheckApplicationReload(ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint);
+ void CheckApplicationReload(ICorDebugThread *pThread);
+
private:
std::unique_ptr<BreakBreakpoint> m_uniqueBreakBreakpoint;
std::unique_ptr<ExceptionBreakpoints> m_uniqueExceptionBreakpoints;
std::unique_ptr<FuncBreakpoints> m_uniqueFuncBreakpoints;
std::unique_ptr<LineBreakpoints> m_uniqueLineBreakpoints;
+ std::unique_ptr<HotReloadBreakpoint> m_uniqueHotReloadBreakpoint;
std::mutex m_nextBreakpointIdMutex;
uint32_t m_nextBreakpointId;
return methodDef;
}
-static HRESULT FindFunction(ICorDebugModule *pModule,
- const WCHAR *typeName,
- const WCHAR *methodName,
- ICorDebugFunction **ppFunction)
+HRESULT FindFunction(ICorDebugModule *pModule, const WCHAR *typeName, const WCHAR *methodName, ICorDebugFunction **ppFunction)
{
HRESULT Status;
class Modules;
class EvalWaiter;
+HRESULT FindFunction(ICorDebugModule *pModule, const WCHAR *typeName, const WCHAR *methodName, ICorDebugFunction **ppFunction);
+
class EvalHelpers
{
public:
IfFailRet(pThread->GetProcess(&iCorProcess));\r
if (!iCorProcess)\r
return E_FAIL;\r
- std::vector<Thread> userThreads;\r
- IfFailRet(m_sharedThreads->GetThreadsWithState(iCorProcess, userThreads));\r
+ std::vector<ThreadId> userThreadIds;\r
+ IfFailRet(m_sharedThreads->GetThreadIds(userThreadIds));\r
ThreadId threadId(getThreadId(pThread));\r
if (!threadId)\r
return E_FAIL;\r
// Note, we need suspend during eval only user's threads, that not used for eval.\r
auto ChangeThreadsState = [&](CorDebugThreadState state)\r
{\r
- for (const auto &userThread : userThreads)\r
+ for (const auto &userThreadId : userThreadIds)\r
{\r
- if (threadId == userThread.id)\r
+ if (threadId == userThreadId)\r
continue;\r
\r
ToRelease<ICorDebugThread> iCorThread;\r
- if (FAILED(iCorProcess->GetThread(int(userThread.id), &iCorThread)) ||\r
+ if (FAILED(iCorProcess->GetThread(int(userThreadId), &iCorThread)) ||\r
FAILED(iCorThread->SetDebugState(state)))\r
{\r
if (state == THREAD_SUSPEND)\r
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include <list>
+#include <vector>
+
+#include "debugger/hotreloadhelpers.h"
+#include "debugger/evaluator.h"
+#include "debugger/evalhelpers.h"
+#include "metadata/modules.h"
+#include "utils/torelease.h"
+
+namespace netcoredbg
+{
+namespace HotReloadHelpers
+{
+
+// Call all ClearCache() and UpdateApplication() methods from UpdateHandlerTypes.
+HRESULT UpdateApplication(ICorDebugThread *pThread, Modules *pModules, Evaluator *pEvaluator, EvalHelpers *pEvalHelpers)
+{
+ HRESULT Status;
+ std::vector<ToRelease<ICorDebugType>> modulesUpdateHandlerTypes;
+ pModules->CopyModulesUpdateHandlerTypes(modulesUpdateHandlerTypes);
+
+ std::list<ToRelease<ICorDebugFunction>> listClearCache;
+ std::list<ToRelease<ICorDebugFunction>> listUpdateApplication;
+ for (auto &updateHandlerType : modulesUpdateHandlerTypes)
+ {
+ pEvaluator->WalkMethods(updateHandlerType.GetPtr(), [&](
+ bool is_static,
+ const std::string &methodName,
+ Evaluator::ReturnElementType &methodRet,
+ std::vector<Evaluator::ArgElementType> &methodArgs,
+ Evaluator::GetFunctionCallback getFunction)
+ {
+
+ if (!is_static ||
+ methodRet.corType != ELEMENT_TYPE_VOID ||
+ methodArgs.size() != 1 ||
+ methodArgs[0].corType != ELEMENT_TYPE_SZARRAY ||
+ methodArgs[0].typeName != "System.Type[]")
+ return S_OK;
+
+ if (methodName == "ClearCache")
+ {
+ listClearCache.emplace_back();
+ IfFailRet(getFunction(&listClearCache.back()));
+ }
+ else if (methodName == "UpdateApplication")
+ {
+ listUpdateApplication.emplace_back();
+ IfFailRet(getFunction(&listUpdateApplication.back()));
+ }
+
+ return S_OK;
+ });
+ }
+
+ // TODO send real type array (for all changed types) instead of null
+ ToRelease<ICorDebugEval> iCorEval;
+ IfFailRet(pThread->CreateEval(&iCorEval));
+ ToRelease<ICorDebugValue> iCorNullValue;
+ IfFailRet(iCorEval->CreateValue(ELEMENT_TYPE_CLASS, nullptr, &iCorNullValue));
+
+ for (auto &func : listClearCache)
+ {
+ IfFailRet(pEvalHelpers->EvalFunction(pThread, func.GetPtr(), nullptr, 0, iCorNullValue.GetRef(), 1, nullptr, defaultEvalFlags));
+ }
+ for (auto &func : listUpdateApplication)
+ {
+ IfFailRet(pEvalHelpers->EvalFunction(pThread, func.GetPtr(), nullptr, 0, iCorNullValue.GetRef(), 1, nullptr, defaultEvalFlags));
+ }
+
+ return S_OK;
+}
+
+} // namespace HotReloadHelpers
+
+} // namespace netcoredbg
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "cor.h"
+#include "cordebug.h"
+
+namespace netcoredbg
+{
+
+class Modules;
+class Evaluator;
+class EvalHelpers;
+
+namespace HotReloadHelpers
+{
+
+ // Call all ClearCache() and UpdateApplication() methods from UpdateHandlerTypes.
+ HRESULT UpdateApplication(ICorDebugThread *pThread, Modules *pModules, Evaluator *pEvaluator, EvalHelpers *pEvalHelpers);
+
+} // namespace HotReloadHelpers
+
+} // namespace netcoredbg
#include "debugger/breakpoints_exception.h"
#include "debugger/breakpoints_func.h"
#include "debugger/breakpoints_line.h"
+#include "debugger/breakpoint_hotreload.h"
#include "debugger/breakpoints.h"
#include "debugger/valueprint.h"
#include "debugger/waitpid.h"
bool ManagedCallback::CallbacksWorkerBreakpoint(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint)
{
+ // S_FALSE or error - continue callback.
+ // S_OK - this is internal Hot Reload breakpoint, ignore this callback call.
+ if (S_OK == m_debugger.m_uniqueBreakpoints->CheckApplicationReload(pThread, pBreakpoint))
+ return false;
+
// S_FALSE - not error and steppers not affect on callback
if (S_FALSE != m_debugger.m_uniqueSteppers->ManagedCallbackBreakpoint(pAppDomain, pThread))
return false;
bool ManagedCallback::CallbacksWorkerStepComplete(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, CorDebugStepReason reason)
{
+ m_debugger.m_uniqueBreakpoints->CheckApplicationReload(pThread);
+
// S_FALSE - not error and steppers not affect on callback (callback will emit stop event)
if (S_FALSE != m_debugger.m_uniqueSteppers->ManagedCallbackStepComplete(pThread, reason))
return false;
bool ManagedCallback::CallbacksWorkerBreak(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread)
{
+ m_debugger.m_uniqueBreakpoints->CheckApplicationReload(pThread);
+
// S_FALSE - not error and not affect on callback (callback will emit stop event)
if (S_FALSE != m_debugger.m_uniqueBreakpoints->ManagedCallbackBreak(pThread, m_debugger.GetLastStoppedThreadId()))
return false;
bool ManagedCallback::CallbacksWorkerException(ICorDebugAppDomain *pAppDomain, ICorDebugThread *pThread, ExceptionCallbackType eventType, const std::string &excModule)
{
+ m_debugger.m_uniqueBreakpoints->CheckApplicationReload(pThread);
+
ThreadId threadId(getThreadId(pThread));
StoppedEvent event(StopException, threadId);
for (const BreakpointEvent &event : events)
m_debugger.m_sharedProtocol->EmitBreakpointEvent(event);
}
+ m_debugger.m_uniqueBreakpoints->ManagedCallbackLoadModuleAll(pModule);
// enable Debugger.NotifyOfCrossThreadDependency after System.Private.CoreLib.dll loaded (trigger for 1 time call only)
if (module.name == "System.Private.CoreLib.dll")
#include "debugger/breakpoints_exception.h"
#include "debugger/breakpoints_func.h"
#include "debugger/breakpoints_line.h"
+#include "debugger/breakpoint_hotreload.h"
#include "debugger/breakpoints.h"
+#include "debugger/hotreloadhelpers.h"
#include "debugger/manageddebugger.h"
#include "debugger/managedcallback.h"
#include "debugger/stepper_simple.h"
namespace
{
+
+ const std::string envDOTNET_STARTUP_HOOKS = "DOTNET_STARTUP_HOOKS";
+#ifdef FEATURE_PAL
+ const char delimiterDOTNET_STARTUP_HOOKS = ':';
+#else // FEATURE_PAL
+ const char delimiterDOTNET_STARTUP_HOOKS = ';';
+#endif // FEATURE_PAL
+
int GetSystemEnvironmentAsMap(std::map<std::string, std::string>& outMap)
{
char*const*const pEnv = GetSystemEnvironment();
m_sharedEvaluator(new Evaluator(m_sharedModules, m_sharedEvalHelpers, m_sharedEvalStackMachine)),
m_sharedVariables(new Variables(m_sharedEvalHelpers, m_sharedEvaluator, m_sharedEvalStackMachine)),
m_uniqueSteppers(new Steppers(m_sharedModules, m_sharedEvalHelpers)),
- m_uniqueBreakpoints(new Breakpoints(m_sharedModules, m_sharedEvaluator, m_sharedVariables)),
+ m_uniqueBreakpoints(new Breakpoints(m_sharedModules, m_sharedEvaluator, m_sharedEvalHelpers, m_sharedVariables)),
m_managedCallback(nullptr),
m_justMyCode(true),
m_stepFiltering(true),
)
{
m_sharedEvalStackMachine->SetupEval(m_sharedEvaluator, m_sharedEvalHelpers, m_sharedEvalWaiter);
+ m_sharedThreads->SetEvaluator(m_sharedEvaluator);
}
ManagedDebugger::~ManagedDebugger()
HANDLE resumeHandle = 0; // Fake thread handle for the process resume
std::vector<char> outEnv;
- if (!m_env.empty())
+
+ // We need to append the environ values with keeping the current process environment block.
+ // It works equal for any platrorms in coreclr CreateProcessW(), but not critical for Linux.
+ std::map<std::string, std::string> envMap;
+ if (GetSystemEnvironmentAsMap(envMap) != -1)
{
- // We need to append the environ values with keeping the current process environment block.
- // It works equal for any platrorms in coreclr CreateProcessW(), but not critical for Linux.
- std::map<std::string, std::string> envMap;
- if (GetSystemEnvironmentAsMap(envMap) != -1)
+ // Override the system value (PATHs appending needs a complex implementation)
+ for (const auto &pair : m_env)
{
- auto it = m_env.begin();
- auto end = m_env.end();
- // Override the system value (PATHs appending needs a complex implementation)
- while (it != end)
+ if (pair.first == envDOTNET_STARTUP_HOOKS && !envMap[pair.first].empty())
{
- envMap[it->first] = it->second;
- ++it;
- }
- for (const auto &pair : envMap)
- {
- outEnv.insert(outEnv.end(), pair.first.begin(), pair.first.end());
- outEnv.push_back('=');
- outEnv.insert(outEnv.end(), pair.second.begin(), pair.second.end());
- outEnv.push_back('\0');
+ envMap[pair.first] = envMap[pair.first] + delimiterDOTNET_STARTUP_HOOKS + pair.second;
+ continue;
}
+ envMap[pair.first] = pair.second;
+ }
+#ifdef NCDB_DOTNET_STARTUP_HOOK
+ if (m_hotReload)
+ {
+ auto find = envMap.find(envDOTNET_STARTUP_HOOKS);
+ if (find != envMap.end())
+ find->second = find->second + delimiterDOTNET_STARTUP_HOOKS + NCDB_DOTNET_STARTUP_HOOK;
+ else
+ envMap[envDOTNET_STARTUP_HOOKS] = NCDB_DOTNET_STARTUP_HOOK;
+ }
+#endif // NCDB_DOTNET_STARTUP_HOOK
+ for (const auto &pair : envMap)
+ {
+ outEnv.insert(outEnv.end(), pair.first.begin(), pair.first.end());
+ outEnv.push_back('=');
+ outEnv.insert(outEnv.end(), pair.second.begin(), pair.second.end());
outEnv.push_back('\0');
}
- else
+ }
+ else
+ {
+ for (const auto &pair : m_env)
{
- for (const auto &pair : m_env)
- {
- outEnv.insert(outEnv.end(), pair.first.begin(), pair.first.end());
- outEnv.push_back('=');
- outEnv.insert(outEnv.end(), pair.second.begin(), pair.second.end());
- outEnv.push_back('\0');
- }
+ outEnv.insert(outEnv.end(), pair.first.begin(), pair.first.end());
+ outEnv.push_back('=');
+ outEnv.insert(outEnv.end(), pair.second.begin(), pair.second.end());
+ outEnv.push_back('\0');
}
}
+ // Environtment variable should looks like: "Var=Value\0OtherVar=OtherValue\0\0"
+ if (!outEnv.empty())
+ outEnv.push_back('\0');
// cwd in launch.json set working directory for debugger https://code.visualstudio.com/docs/python/debugging#_cwd
if (!m_cwd.empty())
return S_OK;
}
-// Call all ClearCache() and UpdateApplication() methods from UpdateHandlerTypes.
-static HRESULT UpdateApplication(ICorDebugThread *pThread, Modules *pModules, Evaluator *pEvaluator, EvalHelpers *pEvalHelpers)
-{
- HRESULT Status;
- std::vector<ToRelease<ICorDebugType>> modulesUpdateHandlerTypes;
- pModules->CopyModulesUpdateHandlerTypes(modulesUpdateHandlerTypes);
-
- std::list<ToRelease<ICorDebugFunction>> listClearCache;
- std::list<ToRelease<ICorDebugFunction>> listUpdateApplication;
- for (auto &updateHandlerType : modulesUpdateHandlerTypes)
- {
- pEvaluator->WalkMethods(updateHandlerType.GetPtr(), [&](
- bool is_static,
- const std::string &methodName,
- Evaluator::ReturnElementType &methodRet,
- std::vector<Evaluator::ArgElementType> &methodArgs,
- Evaluator::GetFunctionCallback getFunction)
- {
-
- if (!is_static ||
- methodRet.corType != ELEMENT_TYPE_VOID ||
- methodArgs.size() != 1 ||
- methodArgs[0].corType != ELEMENT_TYPE_SZARRAY ||
- methodArgs[0].typeName != "System.Type[]")
- return S_OK;
-
- if (methodName == "ClearCache")
- {
- listClearCache.emplace_back();
- IfFailRet(getFunction(&listClearCache.back()));
- }
- else if (methodName == "UpdateApplication")
- {
- listUpdateApplication.emplace_back();
- IfFailRet(getFunction(&listUpdateApplication.back()));
- }
-
- return S_OK;
- });
- }
-
- // TODO send real type array (for all changed types) instead of null
- ToRelease<ICorDebugEval> iCorEval;
- IfFailRet(pThread->CreateEval(&iCorEval));
- ToRelease<ICorDebugValue> iCorNullValue;
- IfFailRet(iCorEval->CreateValue(ELEMENT_TYPE_CLASS, nullptr, &iCorNullValue));
-
- for (auto &func : listClearCache)
- {
- IfFailRet(pEvalHelpers->EvalFunction(pThread, func.GetPtr(), nullptr, 0, iCorNullValue.GetRef(), 1, nullptr, defaultEvalFlags));
- }
- for (auto &func : listUpdateApplication)
- {
- IfFailRet(pEvalHelpers->EvalFunction(pThread, func.GetPtr(), nullptr, 0, iCorNullValue.GetRef(), 1, nullptr, defaultEvalFlags));
- }
-
- return S_OK;
-}
-
HRESULT ManagedDebugger::FindEvalCapableThread(ToRelease<ICorDebugThread> &pThread)
{
ThreadId lastStoppedId = GetLastStoppedThreadId();
- std::vector<Thread> threads;
- GetThreads(threads);
- for (size_t i = 0; i < threads.size(); ++i)
+ std::vector<ThreadId> threadIds;
+ m_sharedThreads->GetThreadIds(threadIds);
+ for (size_t i = 0; i < threadIds.size(); ++i)
{
- if (threads[i].id == lastStoppedId)
+ if (threadIds[i] == lastStoppedId)
{
- std::swap(threads[0], threads[i]);
+ std::swap(threadIds[0], threadIds[i]);
break;
}
}
- for (auto &thread : threads)
+ for (auto &threadId : threadIds)
{
ToRelease<ICorDebugValue> iCorValue;
- if (SUCCEEDED(m_iCorProcess->GetThread(int(thread.id), &pThread)) &&
+ if (SUCCEEDED(m_iCorProcess->GetThread(int(threadId), &pThread)) &&
SUCCEEDED(m_sharedEvalHelpers->CreateString(pThread, "test_string", &iCorValue)))
{
return S_OK;
ToRelease<ICorDebugThread> pThread;
if (SUCCEEDED(FindEvalCapableThread(pThread)))
- IfFailRet(UpdateApplication(pThread, m_sharedModules.get(), m_sharedEvaluator.get(), m_sharedEvalHelpers.get()));
- //else
- // TODO implement async UpdateApplication() call for deltas apply on running process and process Paused by user.
+ IfFailRet(HotReloadHelpers::UpdateApplication(pThread, m_sharedModules.get(), m_sharedEvaluator.get(), m_sharedEvalHelpers.get()));
+ else
+ IfFailRet(m_uniqueBreakpoints->SetHotReloadBreakpoint());
if (continueProcess)
IfFailRet(m_managedCallback->Continue(m_iCorProcess));
// See the LICENSE file in the project root for more information.
#include "debugger/threads.h"
+#include "debugger/evaluator.h"
+#include "debugger/valueprint.h"
#include "utils/torelease.h"
namespace netcoredbg
void Threads::Add(const ThreadId &threadId)
{
- m_userThreadsMutex.lock();
+ std::unique_lock<Utility::RWLock::Writer> write_lock(m_userThreadsRWLock.writer);
+
m_userThreads.emplace(threadId);
- m_userThreadsMutex.unlock();
+ // First added user thread is Main thread for sure.
+ if (!MainThread)
+ MainThread = threadId;
}
void Threads::Remove(const ThreadId &threadId)
{
- std::lock_guard<std::mutex> lock(m_userThreadsMutex);
+ std::unique_lock<Utility::RWLock::Writer> write_lock(m_userThreadsRWLock.writer);
auto it = m_userThreads.find(threadId);
if (it == m_userThreads.end())
m_userThreads.erase(it);
}
+std::string Threads::GetThreadName(ICorDebugProcess *pProcess, const ThreadId &userThread)
+{
+ if (MainThread == userThread)
+ return "Main Thread";
+
+ std::string threadName = "<No name>";
+
+ if (m_sharedEvaluator)
+ {
+ ToRelease<ICorDebugThread> pThread;
+ ToRelease<ICorDebugValue> iCorThreadObject;
+ if (SUCCEEDED(pProcess->GetThread(int(userThread), &pThread)) &&
+ SUCCEEDED(pThread->GetObject(&iCorThreadObject)))
+ {
+ HRESULT Status;
+ m_sharedEvaluator->WalkMembers(iCorThreadObject, nullptr, FrameLevel{0}, false, [&](
+ ICorDebugType *,
+ bool,
+ const std::string &memberName,
+ Evaluator::GetValueCallback getValue,
+ Evaluator::SetterData*)
+ {
+ // Note, only field here (not `Name` property), since we can't guarantee code execution (call property's getter),
+ // this thread can be in not consistent state for evaluation or thread could break in optimized code.
+ if (memberName != "_name")
+ return S_OK;
+
+ ToRelease<ICorDebugValue> iCorResultValue;
+ IfFailRet(getValue(&iCorResultValue, defaultEvalFlags));
+
+ BOOL isNull = TRUE;
+ ToRelease<ICorDebugValue> pValue;
+ IfFailRet(DereferenceAndUnboxValue(iCorResultValue, &pValue, &isNull));
+ if (!isNull)
+ IfFailRet(PrintStringValue(pValue, threadName));
+
+ return E_ABORT; // Fast exit from cycle.
+ });
+ }
+ }
+
+ return threadName;
+}
+
// Caller should guarantee, that pProcess is not null.
HRESULT Threads::GetThreadsWithState(ICorDebugProcess *pProcess, std::vector<Thread> &threads)
{
- std::lock_guard<std::mutex> lock(m_userThreadsMutex);
+ std::unique_lock<Utility::RWLock::Reader> read_lock(m_userThreadsRWLock.reader);
HRESULT Status;
BOOL procRunning = FALSE;
IfFailRet(pProcess->IsRunning(&procRunning));
- const std::string threadName = "<No name>";
+ threads.reserve(m_userThreads.size());
for (auto &userThread : m_userThreads)
{
// ICorDebugThread::GetUserState not available for running thread.
- threads.emplace_back(userThread, threadName, procRunning);
+ threads.emplace_back(userThread, GetThreadName(pProcess, userThread), procRunning);
}
return S_OK;
}
+HRESULT Threads::GetThreadIds(std::vector<ThreadId> &threads)
+{
+ std::unique_lock<Utility::RWLock::Reader> read_lock(m_userThreadsRWLock.reader);
+
+ threads.reserve(m_userThreads.size());
+ for (auto &userThread : m_userThreads)
+ {
+ threads.emplace_back(userThread);
+ }
+ return S_OK;
+}
+
+void Threads::SetEvaluator(std::shared_ptr<Evaluator> &sharedEvaluator)
+{
+ m_sharedEvaluator = sharedEvaluator;
+}
+
} // namespace netcoredbg
#include "cor.h"
#include "cordebug.h"
-#include <mutex>
#include <set>
#include <vector>
#include "interfaces/types.h"
+#include "utils/rwlock.h"
namespace netcoredbg
{
+class Evaluator;
ThreadId getThreadId(ICorDebugThread *pThread);
class Threads
{
- std::mutex m_userThreadsMutex;
+ Utility::RWLock m_userThreadsRWLock;
std::set<ThreadId> m_userThreads;
+ ThreadId MainThread;
+ std::shared_ptr<Evaluator> m_sharedEvaluator;
public:
void Add(const ThreadId &threadId);
void Remove(const ThreadId &threadId);
HRESULT GetThreadsWithState(ICorDebugProcess *pProcess, std::vector<Thread> &threads);
+ HRESULT GetThreadIds(std::vector<ThreadId> &threads);
+ std::string GetThreadName(ICorDebugProcess *pProcess, const ThreadId &userThread);
+ void SetEvaluator(std::shared_ptr<Evaluator> &sharedEvaluator);
};
} // namespace netcoredbg
--- /dev/null
+// Copyright (c) .NET Foundation. All rights reserved.\r
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.\r
+\r
+using System;\r
+using System.Threading.Tasks;\r
+\r
+internal sealed class StartupHook\r
+{\r
+ internal static void ClearHotReloadEnvironmentVariables(\r
+ Func<string, string?> getEnvironmentVariable,\r
+ Action<string, string?> setEnvironmentVariable)\r
+ {\r
+ // Workaround for https://github.com/dotnet/runtime/issues/58000\r
+ // Clear any hot-reload specific environment variables. This should prevent child processes from being\r
+ // affected by the current app's hot reload settings.\r
+ const string StartupHooksEnvironment = "DOTNET_STARTUP_HOOKS";\r
+ var environment = getEnvironmentVariable(StartupHooksEnvironment);\r
+ setEnvironmentVariable(StartupHooksEnvironment, RemoveCurrentAssembly(environment));\r
+\r
+ static string? RemoveCurrentAssembly(string? environment)\r
+ {\r
+ if (string.IsNullOrEmpty(environment))\r
+ {\r
+ return environment;\r
+ }\r
+\r
+ var assemblyLocation = typeof(StartupHook).Assembly.Location;\r
+ var updatedValues = environment.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)\r
+ .Where(e => !string.Equals(e, assemblyLocation, StringComparison.OrdinalIgnoreCase));\r
+\r
+ return string.Join(Path.PathSeparator, updatedValues);\r
+ }\r
+ }\r
+\r
+ public static void Initialize()\r
+ {\r
+ ClearHotReloadEnvironmentVariables(Environment.GetEnvironmentVariable, Environment.SetEnvironmentVariable);\r
+ Task.Run((Action)ncdbloop);\r
+ }\r
+\r
+ public static void ncdbloop()\r
+ {\r
+ while (true)\r
+ {\r
+ ncdbfunc();\r
+ System.Threading.Thread.Sleep(200);\r
+ }\r
+ }\r
+\r
+ public static void ncdbfunc()\r
+ {\r
+ }\r
+}\r
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+ <PropertyGroup>\r
+ <TargetFramework>$(NCDB_HOOK_TargetFramework)</TargetFramework>\r
+ <ImplicitUsings>enable</ImplicitUsings>\r
+ <Nullable>enable</Nullable>\r
+ </PropertyGroup>\r
+\r
+</Project>\r
@"__FILE__:__LINE__"+"\n"+caller_trace);\r
}\r
\r
+ public void WasBreakHit(string caller_trace, string bpFileName, int bpNumLine)\r
+ {\r
+ Func<MIOutOfBandRecord, bool> filter = (record) => {\r
+ if (!IsStoppedEvent(record)) {\r
+ return false;\r
+ }\r
+\r
+ var output = ((MIAsyncRecord)record).Output;\r
+ var reason = (MIConst)output["reason"];\r
+ var signal_name = (MIConst)output["signal-name"];\r
+\r
+ if (reason.CString != "signal-received" &&\r
+ signal_name.CString != "SIGINT") {\r
+ return false;\r
+ }\r
+\r
+ var frame = (MITuple)output["frame"];\r
+ var fileName = (MIConst)frame["file"];\r
+ var line = ((MIConst)frame["line"]).Int;\r
+\r
+ if (fileName.CString == bpFileName &&\r
+ line == bpNumLine) {\r
+ return true;\r
+ }\r
+\r
+ return false;\r
+ };\r
+\r
+ Assert.True(MIDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+ }\r
+\r
public void WasExit(string caller_trace)\r
{\r
Func<MIOutOfBandRecord, bool> filter = (record) => {\r
{\r
Console.WriteLine(""Updated string."");\r
HotReloadBreakpointTest1(); // line 20\r
+ System.Threading.Thread.Sleep(1000);\r
HotReloadBreakpointTest1();\r
+ System.Threading.Thread.Sleep(10000);\r
+ if (i_test1 == 1000 && i_test2 == 1001 &&\r
+ i_test3 == 2000 && i_test4 == 2001 &&\r
+ i_test5 == 3000 && i_test6 == 3001)\r
+ exit_correct();\r
}\r
static void HotReloadBreakpointTest1()\r
{\r
Console.WriteLine(""Added string."");\r
}\r
+ static void exit_correct()\r
+ {\r
+ Debugger.Break();\r
+ }\r
\r
internal static class HotReload\r
{\r
{\r
Console.WriteLine(""Updated string."");\r
HotReloadBreakpointTest1();\r
+ System.Threading.Thread.Sleep(1000);\r
HotReloadBreakpointTest1(); // line 21\r
+ System.Threading.Thread.Sleep(10000);\r
+ if (i_test1 == 1000 && i_test2 == 1001 &&\r
+ i_test3 == 2000 && i_test4 == 2001 &&\r
+ i_test5 == 3000 && i_test6 == 3001)\r
+ exit_correct();\r
}\r
static void HotReloadBreakpointTest1()\r
{\r
Console.WriteLine(""Updated added string."");\r
}\r
+ static void exit_correct()\r
+ {\r
+ Debugger.Break();\r
+ }\r
\r
internal static class HotReload\r
{\r
Context.Continue(@"__FILE__:__LINE__");\r
});\r
\r
- Label.Checkpoint("test_update2", "finish", (Object context) => {\r
+ Label.Checkpoint("test_update2", "test_update3", (Object context) => {\r
Context Context = (Context)context;\r
Context.WasBreakpointHit(@"__FILE__:__LINE__", @"Program.cs", 21);\r
\r
Context.Continue(@"__FILE__:__LINE__");\r
});\r
\r
+ Label.Checkpoint("test_update3", "test_update4", (Object context) => {\r
+ Context Context = (Context)context;\r
+\r
+ System.Threading.Thread.Sleep(2000);\r
+\r
+ Context.GetDelta(@"__FILE__:__LINE__",\r
+ @"using System;using System.Diagnostics;using System.Threading;using System.Reflection.Metadata;[assembly: MetadataUpdateHandler(typeof(TestAppHotReloadUpdate.HotReload1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111))][assembly: MetadataUpdateHandler(typeof(TestAppHotReloadUpdate.Program))][assembly: MetadataUpdateHandler(typeof(TestAppHotReloadUpdate.Program.HotReload))][assembly: MetadataUpdateHandler(typeof(TestAppHotReloadUpdate.Program.HotReload.HotReloadNested))]\r
+ namespace TestAppHotReloadUpdate\r
+ {\r
+ public class Program\r
+ {\r
+ static public int i_test1 = 0;\r
+ static public int i_test2 = 0;\r
+ static public int i_test3 = 0;\r
+ static public int i_test4 = 0;\r
+ static public int i_test5 = 0;\r
+ static public int i_test6 = 0;\r
+ static void Main(string[] args)\r
+ {\r
+ Console.WriteLine(""Hello World!"");\r
+ HotReloadTest();\r
+ }\r
+ static void HotReloadTest()\r
+ {\r
+ Console.WriteLine(""Updated string."");\r
+ HotReloadBreakpointTest1();\r
+ System.Threading.Thread.Sleep(1000);\r
+ HotReloadBreakpointTest1();\r
+ if (i_test1 == 1000 && i_test2 == 1001 &&\r
+ i_test3 == 2000 && i_test4 == 2001 &&\r
+ i_test5 == 3000 && i_test6 == 3001)\r
+ exit_correct();\r
+ }\r
+ static void HotReloadBreakpointTest1()\r
+ {\r
+ Console.WriteLine(""Updated added string."");\r
+ }\r
+ static void HotReloadBreakpointTest2()\r
+ {\r
+ }\r
+ static void exit_correct()\r
+ {\r
+ Debugger.Break(); // line 37\r
+ }\r
+\r
+ internal static class HotReload\r
+ {\r
+ public static void ClearCache(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""ClearCache2"");\r
+ TestAppHotReloadUpdate.Program.i_test1 = 1000;\r
+ }\r
+\r
+ public static void UpdateApplication(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""UpdateApplication2"");\r
+ TestAppHotReloadUpdate.Program.i_test2 = TestAppHotReloadUpdate.Program.i_test1 + 1;\r
+ }\r
+ internal static class HotReloadNested\r
+ {\r
+ public static void ClearCache(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""ClearCache3"");\r
+ TestAppHotReloadUpdate.Program.i_test3 = 2000;\r
+ }\r
+\r
+ public static void UpdateApplication(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""UpdateApplication3"");\r
+ TestAppHotReloadUpdate.Program.i_test4 = TestAppHotReloadUpdate.Program.i_test3 + 1;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ internal static class HotReload1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111\r
+ {\r
+ public static void ClearCache(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""ClearCache1"");\r
+ TestAppHotReloadUpdate.Program.i_test5 = 3000;\r
+ }\r
+\r
+ public static void UpdateApplication(Type[]? changedTypes)\r
+ {\r
+ Console.WriteLine(""UpdateApplication1"");\r
+ TestAppHotReloadUpdate.Program.i_test6 = TestAppHotReloadUpdate.Program.i_test5 + 1;\r
+ }\r
+ }\r
+ }", @"Program.cs");\r
+ Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta3");\r
+ Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta3");\r
+ });\r
+\r
+ Label.Checkpoint("test_update4", "finish", (Object context) => {\r
+ Context Context = (Context)context;\r
+ Context.WasBreakHit(@"__FILE__:__LINE__", @"Program.cs", 37);\r
+ Context.Continue(@"__FILE__:__LINE__");\r
+ });\r
+\r
Label.Checkpoint("finish", "", (Object context) => {\r
Context Context = (Context)context;\r
Context.EndGenDeltaSession(@"__FILE__:__LINE__");\r