Add application update support in case Hot Reload without process stop. accepted/tizen/unified/20220805.131740 submit/tizen/20220804.224219
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Tue, 2 Aug 2022 12:51:09 +0000 (15:51 +0300)
committerAlexander Soldatov/Platform Lab /SRR/Staff Engineer/Samsung Electronics <soldatov.a@samsung.com>
Thu, 4 Aug 2022 15:42:55 +0000 (18:42 +0300)
18 files changed:
packaging/netcoredbg.spec
src/CMakeLists.txt
src/debugger/breakpoint_hotreload.cpp [new file with mode: 0644]
src/debugger/breakpoint_hotreload.h [new file with mode: 0644]
src/debugger/breakpoints.cpp
src/debugger/breakpoints.h
src/debugger/evalhelpers.cpp
src/debugger/evalhelpers.h
src/debugger/evalwaiter.cpp
src/debugger/hotreloadhelpers.cpp [new file with mode: 0644]
src/debugger/hotreloadhelpers.h [new file with mode: 0644]
src/debugger/managedcallback.cpp
src/debugger/manageddebugger.cpp
src/debugger/threads.cpp
src/debugger/threads.h
src/ncdbhook/ncdbhook.cs [new file with mode: 0644]
src/ncdbhook/ncdbhook.csproj [new file with mode: 0644]
test-suite/MITestHotReloadUpdate/Program.cs

index 1d654fde26c28bc85e43199c676aebcb6b188c57..c3d7f9327bec58bf24da201eabcc139cc551376a 100644 (file)
@@ -42,6 +42,7 @@ Requires: coreclr
 %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
@@ -89,6 +90,10 @@ export CXXFLAGS=$(echo $CXXFLAGS | sed -e 's/--target=i686/--target=i586/')
 
 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 .. \
@@ -100,12 +105,17 @@ 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
@@ -113,6 +123,9 @@ 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)
index 290004a4c22b38a15306007277e655b8315a5b73..59edaacc6c39e4778cd9c8b801a28d6dce1e815e 100644 (file)
@@ -83,6 +83,7 @@ add_custom_command(
 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
@@ -94,6 +95,7 @@ set(netcoredbg_SRC
     debugger/evalwaiter.cpp
     debugger/evalutils.cpp
     debugger/frames.cpp
+    debugger/hotreloadhelpers.cpp
     debugger/managedcallback.cpp
     debugger/manageddebugger.cpp
     debugger/threads.cpp
@@ -176,6 +178,10 @@ else()
     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)
@@ -234,6 +240,30 @@ if (BUILD_MANAGED)
     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)
diff --git a/src/debugger/breakpoint_hotreload.cpp b/src/debugger/breakpoint_hotreload.cpp
new file mode 100644 (file)
index 0000000..c95956e
--- /dev/null
@@ -0,0 +1,105 @@
+// 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
diff --git a/src/debugger/breakpoint_hotreload.h b/src/debugger/breakpoint_hotreload.h
new file mode 100644 (file)
index 0000000..8a6650f
--- /dev/null
@@ -0,0 +1,53 @@
+// 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
index 7db06de9f8af268ad82c0d426e865a83c5767469..4182470a69dc70aa64dbd405a9b3f4fb47321699 100644 (file)
@@ -7,6 +7,7 @@
 #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"
 
@@ -56,6 +57,7 @@ void Breakpoints::DeleteAll()
     m_uniqueFuncBreakpoints->DeleteAll();
     m_uniqueLineBreakpoints->DeleteAll();
     m_uniqueExceptionBreakpoints->DeleteAll();
+    m_uniqueHotReloadBreakpoint->Delete();
 }
 
 HRESULT Breakpoints::DisableAll(ICorDebugProcess *pProcess)
@@ -165,6 +167,12 @@ HRESULT Breakpoints::ManagedCallbackLoadModule(ICorDebugModule *pModule, std::ve
     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);
@@ -213,4 +221,19 @@ HRESULT Breakpoints::ManagedCallbackExitThread(ICorDebugThread *pThread)
     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
index 54c5beec711c52fbc78b043693cc5671d36dbada..f7ab7e2450f9c53b0b5f4dcf240fee6ff8b07765 100644 (file)
@@ -21,17 +21,19 @@ class EntryBreakpoint;
 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)
     {}
 
@@ -44,6 +46,7 @@ public:
     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);
@@ -63,8 +66,14 @@ public:
     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;
@@ -72,6 +81,7 @@ private:
     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;
index 7587d3c9cd2154a8b336d3bfba3799360fd72604..45848c3be5be221191a64c472ff7b2b0207ed986 100644 (file)
@@ -110,10 +110,7 @@ static HRESULT GetMethodToken(IMetaDataImport *pMD, mdTypeDef cl, const WCHAR *m
     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;
 
index fd3071ee9ce5c4dca782fa4a39dffb95a979599a..9b9dadd4ba0d0a8eb05ed41d3c3e25a6fcdc92ea 100644 (file)
@@ -19,6 +19,8 @@ namespace netcoredbg
 class Modules;
 class EvalWaiter;
 
+HRESULT FindFunction(ICorDebugModule *pModule, const WCHAR *typeName, const WCHAR *methodName, ICorDebugFunction **ppFunction);
+
 class EvalHelpers
 {
 public:
index c3bde33cc1075a4fe41cdb9a60af13361f04f9a3..1cc511a771c996f327f9affbb3cb00636f1345f0 100644 (file)
@@ -115,8 +115,8 @@ HRESULT EvalWaiter::WaitEvalResult(ICorDebugThread *pThread,
     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
@@ -124,13 +124,13 @@ HRESULT EvalWaiter::WaitEvalResult(ICorDebugThread *pThread,
     // 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
diff --git a/src/debugger/hotreloadhelpers.cpp b/src/debugger/hotreloadhelpers.cpp
new file mode 100644 (file)
index 0000000..55fa83e
--- /dev/null
@@ -0,0 +1,80 @@
+// 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
diff --git a/src/debugger/hotreloadhelpers.h b/src/debugger/hotreloadhelpers.h
new file mode 100644 (file)
index 0000000..92b5459
--- /dev/null
@@ -0,0 +1,25 @@
+// 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
index 5e0511dc17c5aa537d3cdd1d8cd8e2ca3ab490c8..1f8b957a4b43b332026269e8e342bd98d17934e5 100644 (file)
@@ -14,6 +14,7 @@
 #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"
@@ -33,6 +34,11 @@ namespace netcoredbg
 
 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;
@@ -62,6 +68,8 @@ bool ManagedCallback::CallbacksWorkerBreakpoint(ICorDebugAppDomain *pAppDomain,
 
 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;
@@ -83,6 +91,8 @@ bool ManagedCallback::CallbacksWorkerStepComplete(ICorDebugAppDomain *pAppDomain
 
 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;
@@ -107,6 +117,8 @@ bool ManagedCallback::CallbacksWorkerBreak(ICorDebugAppDomain *pAppDomain, ICorD
 
 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);
 
@@ -609,6 +621,7 @@ HRESULT STDMETHODCALLTYPE ManagedCallback::LoadModule(ICorDebugAppDomain *pAppDo
         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")
index 69a8f7134bf3e5d80b887cdbabb9a5e5d36a3d4d..462729f4b18c054cd9d24366f578eccbbc2d0254 100644 (file)
@@ -30,7 +30,9 @@
 #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"
@@ -61,6 +63,14 @@ extern "C" const IID IID_IUnknown = { 0x00000000, 0x0000, 0x0000, {0xC0, 0x00, 0
 
 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();
@@ -168,7 +178,7 @@ ManagedDebugger::ManagedDebugger() :
     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),
@@ -183,6 +193,7 @@ ManagedDebugger::ManagedDebugger() :
     )
 {
     m_sharedEvalStackMachine->SetupEval(m_sharedEvaluator, m_sharedEvalHelpers, m_sharedEvalWaiter);
+    m_sharedThreads->SetEvaluator(m_sharedEvaluator);
 }
 
 ManagedDebugger::~ManagedDebugger()
@@ -608,41 +619,53 @@ HRESULT ManagedDebugger::RunProcess(const std::string& fileExec, const std::vect
     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())
@@ -1314,83 +1337,24 @@ HRESULT ManagedDebugger::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileN
     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;
@@ -1421,9 +1385,9 @@ HRESULT ManagedDebugger::HotReloadApplyDeltas(const std::string &dllFileName, co
 
     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));
index 874acbf96c372310b565dd1a958d0f908d9ce5ed..c763b6138149ccbf56b67eae43ec17ef9c7b0ce8 100644 (file)
@@ -3,6 +3,8 @@
 // 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
@@ -17,14 +19,17 @@ ThreadId getThreadId(ICorDebugThread *pThread)
 
 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())
@@ -33,23 +38,84 @@ void Threads::Remove(const ThreadId &threadId)
     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
index b29de37b55e1e23d94de045a31a99ee9bd93e345..e24f72833db78c2b30329826bfedad297bd357f5 100644 (file)
@@ -6,26 +6,32 @@
 #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
diff --git a/src/ncdbhook/ncdbhook.cs b/src/ncdbhook/ncdbhook.cs
new file mode 100644 (file)
index 0000000..dbd1e66
--- /dev/null
@@ -0,0 +1,53 @@
+// 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
diff --git a/src/ncdbhook/ncdbhook.csproj b/src/ncdbhook/ncdbhook.csproj
new file mode 100644 (file)
index 0000000..3ce4a7a
--- /dev/null
@@ -0,0 +1,9 @@
+<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
index 162b748dd86c1985124db015d076aec107f23313..a1fa5d88a8622b722a6c91f036ac4fcdc23591d2 100644 (file)
@@ -102,6 +102,37 @@ namespace NetcoreDbgTest.Script
                         @"__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
@@ -298,12 +329,22 @@ namespace MITestHotReloadUpdate
                         {\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
@@ -388,12 +429,22 @@ namespace MITestHotReloadUpdate
                         {\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
@@ -446,7 +497,7 @@ namespace MITestHotReloadUpdate
                 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
@@ -460,6 +511,105 @@ namespace MITestHotReloadUpdate
                 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