Split Modules class.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Tue, 16 Aug 2022 15:23:46 +0000 (18:23 +0300)
committerAlexander Soldatov/Platform Lab /SRR/Staff Engineer/Samsung Electronics <soldatov.a@samsung.com>
Thu, 18 Aug 2022 13:18:43 +0000 (16:18 +0300)
21 files changed:
src/CMakeLists.txt
src/debugger/breakpoint_entry.cpp
src/debugger/breakpoints_func.cpp
src/debugger/breakpoints_line.cpp
src/debugger/breakpointutils.cpp
src/debugger/stepper_async.cpp
src/debugger/stepper_async.h
src/debugger/steppers.cpp
src/debugger/valueprint.cpp
src/metadata/async_info.cpp [new file with mode: 0644]
src/metadata/async_info.h [new file with mode: 0644]
src/metadata/attributes.cpp [new file with mode: 0644]
src/metadata/attributes.h [new file with mode: 0644]
src/metadata/jmc.cpp
src/metadata/jmc.h [new file with mode: 0644]
src/metadata/modules.cpp
src/metadata/modules.h
src/metadata/modules_app_update.cpp [new file with mode: 0644]
src/metadata/modules_app_update.h [new file with mode: 0644]
src/metadata/modules_sources.cpp [new file with mode: 0644]
src/metadata/modules_sources.h [new file with mode: 0644]

index 59edaacc6c39e4778cd9c8b801a28d6dce1e815e..a7ac794838aca59b0b4fdbe1755b4c79e3e71dab 100644 (file)
@@ -107,8 +107,12 @@ set(netcoredbg_SRC
     debugger/waitpid.cpp
     interfaces/types.cpp
     managed/interop.cpp
+    metadata/attributes.cpp
+    metadata/async_info.cpp
     metadata/jmc.cpp
     metadata/modules.cpp
+    metadata/modules_app_update.cpp
+    metadata/modules_sources.cpp
     metadata/typeprinter.cpp
     protocols/cliprotocol.cpp
     protocols/escaped_string.cpp
index c73904cb5da0cd286aaad2c594802fd48d54ced9..e6416279fda84f48ec240004f6c6325b968d7633 100644 (file)
@@ -153,7 +153,7 @@ static HRESULT TrySetupAsyncEntryBreakpoint(ICorDebugModule *pModule, IMetaDataI
         return E_FAIL;\r
 \r
     // Note, in case of async `MoveNext` method, user code don't start from 0 IL offset.\r
-    ULONG32 ilNextOffset;\r
+    ULONG32 ilNextOffset = 0;\r
     const ULONG32 currentVersion = 1; // In case entry breakpoint, this can be only base PDB, not delta PDB for sure.\r
     IfFailRet(pModules->GetNextUserCodeILOffsetInMethod(pModule, resultToken, currentVersion, 0, ilNextOffset));\r
 \r
index ff568e29213ea3b53842e6aecfce7212c729e75e..91e9fd9976a058615c5c2bb04ae3c741b7c7b4b3 100644 (file)
@@ -262,7 +262,7 @@ HRESULT FuncBreakpoints::AddFuncBreakpoint(ManagedFuncBreakpoint &fbp, ResolvedF
         ULONG32 currentVersion; // Note, new breakpoints could be setup for last code version only, since protocols (MI, VSCode, ...) provide method name (sig) only.\r
         IfFailRet(pFunc->GetCurrentVersionNumber(&currentVersion));\r
 \r
-        ULONG32 ilNextOffset;\r
+        ULONG32 ilNextOffset = 0;\r
         if (FAILED(m_sharedModules->GetNextUserCodeILOffsetInMethod(entry.first, entry.second, currentVersion, 0, ilNextOffset)))\r
             return S_OK;\r
 \r
index 8db118a2b231d8cfbd109e5369d26df151ebb18c..a4fd5d30dfa3010e1faf0082e093a0b3aa461fee 100644 (file)
@@ -194,7 +194,7 @@ HRESULT LineBreakpoints::ResolveLineBreakpoint(ICorDebugModule *pModule, Managed
         IfFailRet(pModule->GetBaseAddress(&modAddress));\r
 \r
     std::string fullname = bp_fullname;\r
-    std::vector<Modules::resolved_bp_t> resolvedPoints;\r
+    std::vector<ModulesSources::resolved_bp_t> resolvedPoints;\r
     unsigned fullname_index = 0;\r
 \r
     IfFailRet(m_sharedModules->ResolveBreakpoint(modAddress, bp_fullname, fullname_index, bp.linenum, resolvedPoints));\r
index 26ad5efdb5d621ef4cf6f56d10c006faa0467fd9..d1feef02b02892e0497696fe96c24d1fb5454c31 100644 (file)
@@ -4,7 +4,7 @@
 \r
 #include "debugger/breakpointutils.h"\r
 #include "debugger/variables.h"\r
-#include "metadata/modules.h"\r
+#include "metadata/attributes.h"\r
 #include "utils/torelease.h"\r
 \r
 namespace netcoredbg\r
index 6d259dc41168423e8cf5b0f2e575c0fbbc6bc648..ef8f0f484334e31abf1d19f2bc0eb30d95fc3c50 100644 (file)
@@ -5,7 +5,6 @@
 #include "debugger/stepper_simple.h"\r
 #include "debugger/stepper_async.h"\r
 #include "debugger/threads.h"\r
-#include "metadata/modules.h"\r
 #include "metadata/typeprinter.h"\r
 #include "debugger/evalhelpers.h"\r
 #include "debugger/valueprint.h"\r
@@ -258,7 +257,7 @@ HRESULT AsyncStepper::SetupStep(ICorDebugThread *pThread, IDebugger::StepType st
     ULONG32 methodVersion;\r
     IfFailRet(pCode->GetVersionNumber(&methodVersion));\r
 \r
-    if (!m_sharedModules->IsMethodHaveAwait(modAddress, methodToken, methodVersion))\r
+    if (!m_uniqueAsyncInfo->IsMethodHaveAwait(modAddress, methodToken, methodVersion))\r
         return S_FALSE; // setup simple stepper\r
 \r
     ToRelease<ICorDebugILFrame> pILFrame;\r
@@ -272,7 +271,7 @@ HRESULT AsyncStepper::SetupStep(ICorDebugThread *pThread, IDebugger::StepType st
     // switch to step-out, so whole NotifyDebuggerOfWaitCompletion magic happens.\r
     ULONG32 lastIlOffset;\r
     if (stepType != IDebugger::StepType::STEP_OUT &&\r
-        m_sharedModules->FindLastIlOffsetAwaitInfo(modAddress, methodToken, methodVersion, lastIlOffset) &&\r
+        m_uniqueAsyncInfo->FindLastIlOffsetAwaitInfo(modAddress, methodToken, methodVersion, lastIlOffset) &&\r
         ipOffset >= lastIlOffset)\r
     {\r
         stepType = IDebugger::StepType::STEP_OUT;\r
@@ -296,8 +295,8 @@ HRESULT AsyncStepper::SetupStep(ICorDebugThread *pThread, IDebugger::StepType st
         return S_OK;\r
     }\r
 \r
-    Modules::AwaitInfo *awaitInfo = nullptr;\r
-    if (m_sharedModules->FindNextAwaitInfo(modAddress, methodToken, methodVersion, ipOffset, &awaitInfo))\r
+    AsyncInfo::AwaitInfo *awaitInfo = nullptr;\r
+    if (m_uniqueAsyncInfo->FindNextAwaitInfo(modAddress, methodToken, methodVersion, ipOffset, &awaitInfo))\r
     {\r
         // We have step inside async function with await, setup breakpoint at closest await's yield_offset.\r
         // Two possible cases here:\r
index 9be73f0629a4bcd3471ef97542e7a4529c90f017..f99f9cede3f8d2437590331ce9cba926639e6358 100644 (file)
@@ -9,12 +9,13 @@
 #include <mutex>\r
 #include "interfaces/idebugger.h"\r
 #include "utils/torelease.h"\r
+#include "metadata/async_info.h"\r
 \r
 \r
 namespace netcoredbg\r
 {\r
 \r
-class Modules;\r
+class AsyncInfo;\r
 class EvalHelpers;\r
 class SimpleStepper;\r
 \r
@@ -24,7 +25,7 @@ public:
 \r
     AsyncStepper(std::shared_ptr<SimpleStepper> simpleStepper, std::shared_ptr<Modules> &sharedModules, std::shared_ptr<EvalHelpers> &sharedEvalHelpers) :\r
         m_simpleStepper(simpleStepper),\r
-        m_sharedModules(sharedModules),\r
+        m_uniqueAsyncInfo(new AsyncInfo(sharedModules)),\r
         m_sharedEvalHelpers(sharedEvalHelpers),\r
         m_asyncStep(nullptr),\r
         m_asyncStepNotifyDebuggerOfWaitCompletion(nullptr)\r
@@ -47,7 +48,7 @@ public:
 private:\r
 \r
     std::shared_ptr<SimpleStepper> m_simpleStepper;\r
-    std::shared_ptr<Modules> m_sharedModules;\r
+    std::unique_ptr<AsyncInfo> m_uniqueAsyncInfo;\r
     std::shared_ptr<EvalHelpers> m_sharedEvalHelpers;\r
 \r
     enum class asyncStepStatus\r
index c2c887767124e42c0f7cbe93e05aa7b4a0cea40b..763cf1a35abb48b464cece83fa89efb66418d2c2 100644 (file)
@@ -6,7 +6,7 @@
 #include "debugger/stepper_simple.h"\r
 #include "debugger/stepper_async.h"\r
 #include "debugger/steppers.h"\r
-#include "metadata/modules.h"\r
+#include "metadata/attributes.h"\r
 #include "utils/utf.h"\r
 \r
 namespace netcoredbg\r
@@ -162,8 +162,8 @@ HRESULT Steppers::ManagedCallbackStepComplete(ICorDebugThread *pThread, CorDebug
     m_filteredPrevStep = false;\r
 \r
     // Same behaviour as MS vsdbg and MSVS C# debugger have - step only for code with PDB loaded (no matter JMC enabled or not by user).\r
-    ULONG32 ipOffset;\r
-    ULONG32 ilNextUserCodeOffset;\r
+    ULONG32 ipOffset = 0;\r
+    ULONG32 ilNextUserCodeOffset = 0;\r
     bool noUserCodeFound = false; // Must be initialized with `false`, since GetFrameILAndNextUserCodeILOffset call could be failed before delegate call.\r
     if (SUCCEEDED(Status = m_sharedModules->GetFrameILAndNextUserCodeILOffset(iCorFrame, ipOffset, ilNextUserCodeOffset, &noUserCodeFound)))\r
     {\r
index 8a0d06df9fbcfb5c40f96aaea0fd189a83a5a563..675820431010cad90d37fe44a72478f50be8fe1b 100644 (file)
@@ -17,7 +17,7 @@
 #include "utils/torelease.h"
 #include "utils/utf.h"
 #include "managed/interop.h"
-#include "metadata/modules.h" // HasAttribute()
+#include "metadata/attributes.h"
 
 namespace netcoredbg
 {
diff --git a/src/metadata/async_info.cpp b/src/metadata/async_info.cpp
new file mode 100644 (file)
index 0000000..f71b60a
--- /dev/null
@@ -0,0 +1,104 @@
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "metadata/async_info.h"
+#include "metadata/modules.h"
+#include "managed/interop.h"
+
+
+namespace netcoredbg
+{
+
+// Caller must care about m_asyncMethodSteppingInfoMutex.
+HRESULT AsyncInfo::GetAsyncMethodSteppingInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion)
+{
+    if (asyncMethodSteppingInfo.modAddress == modAddress &&
+        asyncMethodSteppingInfo.methodToken == methodToken &&
+        asyncMethodSteppingInfo.methodVersion == methodVersion)
+        return S_OK;
+
+    if (!asyncMethodSteppingInfo.awaits.empty())
+        asyncMethodSteppingInfo.awaits.clear();
+
+    return m_sharedModules->GetModuleInfo(modAddress, [&](Modules::ModuleInfo &mdInfo) -> HRESULT
+    {
+        if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
+            return E_FAIL;
+
+        HRESULT Status;
+        std::vector<Interop::AsyncAwaitInfoBlock> AsyncAwaitInfo;
+        IfFailRet(Interop::GetAsyncMethodSteppingInfo(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, AsyncAwaitInfo, &asyncMethodSteppingInfo.lastIlOffset));
+
+        for (const auto &entry : AsyncAwaitInfo)
+        {
+            asyncMethodSteppingInfo.awaits.emplace_back(entry.yield_offset, entry.resume_offset);
+        }
+
+        asyncMethodSteppingInfo.modAddress = modAddress;
+        asyncMethodSteppingInfo.methodToken = methodToken;
+        asyncMethodSteppingInfo.methodVersion = methodVersion;
+
+        return S_OK;
+    });
+}
+
+// Check if method have await block. In this way we detect async method with awaits.
+// [in] modAddress - module address;
+// [in] methodToken - method token (from module with address modAddress).
+bool AsyncInfo::IsMethodHaveAwait(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion)
+{
+    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
+
+    return SUCCEEDED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion));
+}
+
+// Find await block after IL offset in particular async method and return await info, if present.
+// In case of async stepping, we need await info from PDB in order to setup breakpoints in proper places (yield and resume offsets).
+// [in] modAddress - module address;
+// [in] methodToken - method token (from module with address modAddress).
+// [in] ipOffset - IL offset;
+// [out] awaitInfo - result, next await info.
+bool AsyncInfo::FindNextAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 ipOffset, AwaitInfo **awaitInfo)
+{
+    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
+
+    if (FAILED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion)))
+        return false;
+
+    for (auto &await : asyncMethodSteppingInfo.awaits)
+    {
+        if (ipOffset <= await.yield_offset)
+        {
+            if (awaitInfo)
+                *awaitInfo = &await;
+            return true;
+        }
+        // Stop search, if IP inside 'await' routine.
+        else if (ipOffset < await.resume_offset)
+        {
+            break;
+        }
+    }
+
+    return false;
+}
+
+// Find last IL offset for user code in async method, if present.
+// In case of step-in and step-over we must detect last user code line in order to "emulate"
+// step-out (DebuggerOfWaitCompletion magic) instead.
+// [in] modAddress - module address;
+// [in] methodToken - method token (from module with address modAddress).
+// [out] lastIlOffset - result, IL offset for last user code line in async method.
+bool AsyncInfo::FindLastIlOffsetAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 &lastIlOffset)
+{
+    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
+
+    if (FAILED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion)))
+        return false;
+
+    lastIlOffset = asyncMethodSteppingInfo.lastIlOffset;
+    return true;
+}
+
+} // namespace netcoredbg
diff --git a/src/metadata/async_info.h b/src/metadata/async_info.h
new file mode 100644 (file)
index 0000000..16f9525
--- /dev/null
@@ -0,0 +1,67 @@
+// 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 <memory>
+
+#include "metadata/modules.h"
+
+namespace netcoredbg
+{
+
+class Modules;
+
+class AsyncInfo
+{
+public:
+
+    AsyncInfo(std::shared_ptr<Modules> &sharedModules) :
+        m_sharedModules(sharedModules)
+    {}
+
+    struct AwaitInfo
+    {
+        uint32_t yield_offset;
+        uint32_t resume_offset;
+
+        AwaitInfo() :
+            yield_offset(0), resume_offset(0)
+        {};
+        AwaitInfo(uint32_t offset1, uint32_t offset2) :
+            yield_offset(offset1), resume_offset(offset2)
+        {};
+    };
+
+    bool IsMethodHaveAwait(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion);
+    bool FindNextAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 ipOffset, AwaitInfo **awaitInfo);
+    bool FindLastIlOffsetAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 &lastIlOffset);
+
+private:
+
+    std::shared_ptr<Modules> m_sharedModules;
+
+    struct AsyncMethodInfo
+    {
+        CORDB_ADDRESS modAddress;
+        mdMethodDef methodToken;
+        ULONG32 methodVersion;
+
+        std::vector<AwaitInfo> awaits;
+        // Part of NotifyDebuggerOfWaitCompletion magic, see ManagedDebugger::SetupAsyncStep().
+        ULONG32 lastIlOffset;
+
+        AsyncMethodInfo() :
+            modAddress(0), methodToken(mdMethodDefNil), methodVersion(0), awaits(), lastIlOffset(0)
+        {};
+    };
+
+    AsyncMethodInfo asyncMethodSteppingInfo;
+    std::mutex m_asyncMethodSteppingInfoMutex;
+    // Note, result stored into asyncMethodSteppingInfo.
+    HRESULT GetAsyncMethodSteppingInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion);
+
+};
+
+} // namespace netcoredbg
diff --git a/src/metadata/attributes.cpp b/src/metadata/attributes.cpp
new file mode 100644 (file)
index 0000000..ae7bd4e
--- /dev/null
@@ -0,0 +1,55 @@
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "metadata/attributes.h"
+
+#include <functional>
+#include <algorithm>
+
+#include "metadata/typeprinter.h"
+
+
+namespace netcoredbg
+{
+
+static bool ForEachAttribute(IMetaDataImport *pMD, mdToken tok, std::function<HRESULT(const std::string &AttrName)> cb)
+{
+    bool found = false;
+    ULONG numAttributes = 0;
+    HCORENUM fEnum = NULL;
+    mdCustomAttribute attr;
+    while(SUCCEEDED(pMD->EnumCustomAttributes(&fEnum, tok, 0, &attr, 1, &numAttributes)) && numAttributes != 0)
+    {
+        std::string mdName;
+        mdToken ptkObj = mdTokenNil;
+        mdToken ptkType = mdTokenNil;
+        if (FAILED(pMD->GetCustomAttributeProps(attr, &ptkObj, &ptkType, nullptr, nullptr)) ||
+            FAILED(TypePrinter::NameForToken(ptkType, pMD, mdName, true, nullptr)))
+            continue;
+
+        found = cb(mdName);
+        if (found)
+            break;
+    }
+    pMD->CloseEnum(fEnum);
+    return found;
+}
+
+bool HasAttribute(IMetaDataImport *pMD, mdToken tok, const char *attrName)
+{
+    return ForEachAttribute(pMD, tok, [&attrName](const std::string &AttrName) -> bool
+    {
+        return AttrName == attrName;
+    });
+}
+
+bool HasAttribute(IMetaDataImport *pMD, mdToken tok, std::vector<std::string> &attrNames)
+{
+    return ForEachAttribute(pMD, tok, [&attrNames](const std::string &AttrName) -> bool
+    {
+        return std::find(attrNames.begin(), attrNames.end(), AttrName) != attrNames.end();
+    });
+}
+
+} // namespace netcoredbg
diff --git a/src/metadata/attributes.h b/src/metadata/attributes.h
new file mode 100644 (file)
index 0000000..6663e54
--- /dev/null
@@ -0,0 +1,26 @@
+// 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"
+
+#include <vector>
+#include <string>
+
+namespace netcoredbg
+{
+
+struct DebuggerAttribute
+{
+    static const char NonUserCode[];
+    static const char StepThrough[];
+    static const char Hidden[];
+};
+
+bool HasAttribute(IMetaDataImport *pMD, mdToken tok, const char *attrName);
+bool HasAttribute(IMetaDataImport *pMD, mdToken tok, std::vector<std::string> &attrNames);
+
+} // namespace netcoredbg
index 51e37b9900d80d8c30dbcaa35f960a8ccfaeebae..a6a2881ef1900fda02f956f09ec9b6b8dfaea16c 100644 (file)
@@ -2,16 +2,14 @@
 // Distributed under the MIT License.
 // See the LICENSE file in the project root for more information.
 
-#include "metadata/modules.h"
-
 #include <string>
 #include <vector>
-#include <functional>
-#include <algorithm>
-
-#include "metadata/typeprinter.h"
+#include <iterator>
+#include "metadata/jmc.h"
+#include "metadata/attributes.h"
 #include "utils/platform.h"
 #include "managed/interop.h"
+#include "utils/torelease.h"
 
 namespace netcoredbg
 {
@@ -34,45 +32,6 @@ const char DebuggerAttribute::Hidden[] = "System.Diagnostics.DebuggerHiddenAttri
 static std::vector<std::string> typeAttrNames{DebuggerAttribute::NonUserCode, DebuggerAttribute::StepThrough};
 static std::vector<std::string> methodAttrNames{DebuggerAttribute::NonUserCode, DebuggerAttribute::StepThrough, DebuggerAttribute::Hidden};
 
-bool ForEachAttribute(IMetaDataImport *pMD, mdToken tok, std::function<HRESULT(const std::string &AttrName)> cb)
-{
-    bool found = false;
-    ULONG numAttributes = 0;
-    HCORENUM fEnum = NULL;
-    mdCustomAttribute attr;
-    while(SUCCEEDED(pMD->EnumCustomAttributes(&fEnum, tok, 0, &attr, 1, &numAttributes)) && numAttributes != 0)
-    {
-        std::string mdName;
-        mdToken ptkObj = mdTokenNil;
-        mdToken ptkType = mdTokenNil;
-        if (FAILED(pMD->GetCustomAttributeProps(attr, &ptkObj, &ptkType, nullptr, nullptr)) ||
-            FAILED(TypePrinter::NameForToken(ptkType, pMD, mdName, true, nullptr)))
-            continue;
-
-        found = cb(mdName);
-        if (found)
-            break;
-    }
-    pMD->CloseEnum(fEnum);
-    return found;
-}
-
-bool HasAttribute(IMetaDataImport *pMD, mdToken tok, const char *attrName)
-{
-    return ForEachAttribute(pMD, tok, [&attrName](const std::string &AttrName) -> bool
-    {
-        return AttrName == attrName;
-    });
-}
-
-bool HasAttribute(IMetaDataImport *pMD, mdToken tok, std::vector<std::string> &attrNames)
-{
-    return ForEachAttribute(pMD, tok, [&attrNames](const std::string &AttrName) -> bool
-    {
-        return std::find(attrNames.begin(), attrNames.end(), AttrName) != attrNames.end();
-    });
-}
-
 static HRESULT GetNonJMCMethodsForTypeDef(
     IMetaDataImport *pMD,
     mdTypeDef typeDef,
diff --git a/src/metadata/jmc.h b/src/metadata/jmc.h
new file mode 100644 (file)
index 0000000..76d500c
--- /dev/null
@@ -0,0 +1,18 @@
+// 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"
+
+#include <unordered_set>
+
+namespace netcoredbg
+{
+
+HRESULT DisableJMCByAttributes(ICorDebugModule *pModule);
+HRESULT DisableJMCByAttributes(ICorDebugModule *pModule, const std::unordered_set<mdMethodDef> &methodTokens);
+
+} // namespace netcoredbg
index de9fcf3751818388431690d7f56828da399e5a0c..546bbc1438ec1d4a6fe7d3a58e618e333ea70e73 100644 (file)
 
 #include <sstream>
 #include <vector>
-#include <map>
 #include <iomanip>
-#include <algorithm>
-#include <fstream>
 
 #include "managed/interop.h"
 #include "utils/platform.h"
-#include "utils/utf.h"
 #include "metadata/typeprinter.h"
+#include "metadata/jmc.h"
 #include "utils/filesystem.h"
 
 namespace netcoredbg
 {
 
-namespace
-{
-
-    struct file_methods_data_t
-    {
-        BSTR document;
-        int32_t methodNum;
-        method_data_t *methodsData;
-
-        file_methods_data_t() = delete;
-        file_methods_data_t(const file_methods_data_t&) = delete;
-        file_methods_data_t& operator=(const file_methods_data_t&) = delete;
-        file_methods_data_t(file_methods_data_t&&) = delete;
-        file_methods_data_t& operator=(file_methods_data_t&&) = delete;
-    };
-
-    struct module_methods_data_t
-    {
-        int32_t fileNum;
-        file_methods_data_t *moduleMethodsData;
-
-        module_methods_data_t() = delete;
-        module_methods_data_t(const module_methods_data_t&) = delete;
-        module_methods_data_t& operator=(const module_methods_data_t&) = delete;
-        module_methods_data_t(module_methods_data_t&&) = delete;
-        module_methods_data_t& operator=(module_methods_data_t&&) = delete;
-    };
-
-    struct module_methods_data_t_deleter
-    {
-        void operator()(module_methods_data_t *p) const
-        {
-            if (p->moduleMethodsData)
-            {
-                for (int32_t i = 0; i < p->fileNum; i++)
-                {
-                    if (p->moduleMethodsData[i].document)
-                        Interop::SysFreeString(p->moduleMethodsData[i].document);
-                    if (p->moduleMethodsData[i].methodsData)
-                        Interop::CoTaskMemFree(p->moduleMethodsData[i].methodsData);
-                }
-                Interop::CoTaskMemFree(p->moduleMethodsData);
-            }
-            Interop::CoTaskMemFree(p);
-        }
-    };
-
-    // Note, we use std::map since we need container that will not invalidate iterators on add new elements.
-    void AddMethodData(/*in,out*/ std::map<size_t, std::set<method_data_t>> &methodData,
-                       /*in,out*/ std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> &multiMethodBpData,
-                       const method_data_t &entry,
-                       const size_t nestedLevel)
-    {
-        // if we here, we need at least one nested level for sure
-        if (methodData.empty())
-        {
-            methodData.emplace(std::make_pair(0, std::set<method_data_t>{entry}));
-            return;
-        }
-        assert(nestedLevel <= methodData.size()); // could be increased only at 1 per recursive call
-        if (nestedLevel == methodData.size())
-        {
-            methodData.emplace(std::make_pair(nestedLevel, std::set<method_data_t>{entry}));
-            return;
-        }
-
-        // same data that was already added, but with different method token (constructors case)
-        auto find = methodData[nestedLevel].find(entry);
-        if (find != methodData[nestedLevel].end())
-        {
-            method_data_t key(find->methodDef, entry.startLine, entry.endLine, entry.startColumn, entry.endColumn);
-            auto find_multi = multiMethodBpData.find(key);
-            if (find_multi == multiMethodBpData.end())
-                multiMethodBpData.emplace(std::make_pair(key, std::vector<mdMethodDef>{entry.methodDef}));
-            else
-                find_multi->second.emplace_back(entry.methodDef);
-
-            return;
-        }
-
-        auto it = methodData[nestedLevel].lower_bound(entry);
-        if (it != methodData[nestedLevel].end() && entry.NestedInto(*it))
-        {
-            AddMethodData(methodData, multiMethodBpData, entry, nestedLevel + 1);
-            return;
-        }
-
-        // case with only one element on nested level, NestedInto() was already called and entry checked
-        if (it == methodData[nestedLevel].begin())
-        {
-            methodData[nestedLevel].emplace(entry);
-            return;
-        }
-
-        // move all previously added nested for new entry elements to level above
-        do
-        {
-            it = std::prev(it);
-
-            if ((*it).NestedInto(entry))
-            {
-                method_data_t tmp = *it;
-                it = methodData[nestedLevel].erase(it);
-                AddMethodData(methodData, multiMethodBpData, tmp, nestedLevel + 1);
-            }
-            else
-                break;
-        }
-        while(it != methodData[nestedLevel].begin());
-
-        methodData[nestedLevel].emplace(entry);
-    }
-
-
-    bool GetMethodTokensByLineNumber(const std::vector<std::vector<method_data_t>> &methodBpData,
-                                     const std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> &multiMethodBpData,
-                                     /*in,out*/ int32_t &lineNum,
-                                     /*out*/ std::vector<mdMethodDef> &Tokens,
-                                     /*out*/ mdMethodDef &closestNestedToken)
-    {
-        const method_data_t *result = nullptr;
-        closestNestedToken = 0;
-
-        for (auto it = methodBpData.cbegin(); it != methodBpData.cend(); ++it)
-        {
-            auto lower = std::lower_bound((*it).cbegin(), (*it).cend(), lineNum);
-            if (lower == (*it).cend())
-                break; // point behind last method for this nested level
-
-            // case with first line of method, for example:
-            // void Method(){ 
-            //            void Method(){ void Method(){...  <- breakpoint at this line
-            if (lineNum == (*lower).startLine)
-            {
-                // At this point we can't check this case, let managed part decide (since it see Columns):
-                // void Method() {
-                // ... code ...; void Method() {     <- breakpoint at this line
-                //  };
-                if (result)
-                    closestNestedToken = (*lower).methodDef;
-                else
-                    result = &(*lower);
-
-                break;
-            }
-            else if (lineNum > (*lower).startLine && (*lower).endLine >= lineNum)
-            {
-                result = &(*lower);
-                continue; // need check nested level (if available)
-            }
-            // out of first level methods lines - forced move line to first method below, for example:
-            //  <-- breakpoint at line without code (out of any methods)
-            // void Method() {...}
-            else if (it == methodBpData.cbegin() && lineNum < (*lower).startLine)
-            {
-                lineNum = (*lower).startLine;
-                result = &(*lower);
-                break;
-            }
-            // result was found on previous cycle, check for closest nested method
-            // need it in case of breakpoint setuped at lines without code and before nested method, for example:
-            // {
-            //  <-- breakpoint at line without code (inside method)
-            //     void Method() {...}
-            // }
-            else if (result && lineNum <= (*lower).startLine && (*lower).endLine <= result->endLine)
-            {
-                closestNestedToken = (*lower).methodDef;
-                break;
-            }
-            else
-                break;
-        }
-
-        if (result)
-        {
-            auto find = multiMethodBpData.find(*result);
-            if (find != multiMethodBpData.end()) // only constructors segments could be part of multiple methods
-            {
-                Tokens.resize((*find).second.size());
-                std::copy(find->second.begin(), find->second.end(), Tokens.begin());
-            }
-            Tokens.emplace_back(result->methodDef);
-        }
-        
-        return !!result;
-    }
-
-    std::vector<std::string> split_on_tokens(const std::string &str, const char delim)
-    {
-        std::vector<std::string> res;
-        size_t pos = 0, prev = 0;
-
-        while (true)
-        {
-            pos = str.find(delim, prev);
-            if (pos == std::string::npos)
-            {
-                res.push_back(std::string(str, prev));
-                break;
-            }
-
-            res.push_back(std::string(str, prev, pos - prev));
-            prev = pos + 1;
-        }
-
-        return res;
-    }
-
-} // unnamed namespace
-
 Modules::ModuleInfo::~ModuleInfo() noexcept
 {
     for (auto symbolReaderHandle : m_symbolReaderHandles)
@@ -348,6 +134,27 @@ static HRESULT ForEachMethod(ICorDebugModule *pModule, std::function<bool(const
     return S_OK;
 }
 
+static std::vector<std::string> split_on_tokens(const std::string &str, const char delim)
+{
+    std::vector<std::string> res;
+    size_t pos = 0, prev = 0;
+
+    while (true)
+    {
+        pos = str.find(delim, prev);
+        if (pos == std::string::npos)
+        {
+            res.push_back(std::string(str, prev));
+            break;
+        }
+
+        res.push_back(std::string(str, prev, pos - prev));
+        prev = pos + 1;
+    }
+
+    return res;
+}
+
 static HRESULT ResolveMethodInModule(ICorDebugModule *pModule, const std::string &funcName, ResolveFuncBreakpointCallback cb)
 {
     std::vector<std::string> splittedName = split_on_tokens(funcName, '.');
@@ -373,7 +180,7 @@ void Modules::CleanupAllModules()
 {
     std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
     m_modulesInfo.clear();
-    m_modulesUpdateHandlerTypes.clear();
+    m_modulesAppUpdate.Clear();
 }
 
 std::string GetModuleFileName(ICorDebugModule *pModule)
@@ -430,6 +237,24 @@ HRESULT IsModuleHaveSameName(ICorDebugModule *pModule, const std::string &Name,
     return modName == Name ? S_OK : S_FALSE;
 }
 
+HRESULT Modules::GetModuleInfo(CORDB_ADDRESS modAddress, ModuleInfoCallback cb)
+{
+    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
+    auto info_pair = m_modulesInfo.find(modAddress);
+    return (info_pair == m_modulesInfo.end()) ? E_FAIL : cb(info_pair->second);
+}
+
+// Caller must care about m_modulesInfoMutex.
+HRESULT Modules::GetModuleInfo(CORDB_ADDRESS modAddress, ModuleInfo **ppmdInfo)
+{
+    auto info_pair = m_modulesInfo.find(modAddress);
+    if (info_pair == m_modulesInfo.end())
+        return E_FAIL;
+
+    *ppmdInfo = &info_pair->second;
+    return S_OK;
+}
+
 HRESULT Modules::ResolveFuncBreakpointInAny(const std::string &module,
                                             bool &module_checked,
                                             const std::string &funcname,
@@ -478,56 +303,9 @@ HRESULT Modules::ResolveFuncBreakpointInModule(ICorDebugModule *pModule, const s
         module_checked = true;
     }
 
-    CORDB_ADDRESS modAddress;
-    IfFailRet(pModule->GetBaseAddress(&modAddress));
-
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
-        return E_FAIL;
-
     return ResolveMethodInModule(pModule, funcname, cb);
 }
 
-template <class T>
-static void LineUpdatesForwardCorrection(unsigned fullPathIndex, mdMethodDef methodToken, method_block_updates_t &methodBlockUpdates, T &block)
-{
-    auto findSourceUpdate = methodBlockUpdates.find(methodToken);
-    if (findSourceUpdate == methodBlockUpdates.end())
-        return;
-
-    for (const auto &entry : findSourceUpdate->second)
-    {
-        if (entry.fullPathIndex != fullPathIndex ||
-            entry.oldLine > block.startLine ||
-            entry.oldLine + entry.endLineOffset < block.startLine)
-            continue;
-
-        int32_t offset = entry.newLine - entry.oldLine;
-        block.startLine += offset;
-        block.endLine += offset;
-        break;
-    }
-}
-
-static void LineUpdatesBackwardCorrection(unsigned fullPathIndex, mdMethodDef methodToken, method_block_updates_t &methodBlockUpdates, int32_t &startLine)
-{
-    auto findSourceUpdate = methodBlockUpdates.find(methodToken);
-    if (findSourceUpdate != methodBlockUpdates.end())
-    {
-        for (const auto &entry : findSourceUpdate->second)
-        {
-            if (entry.fullPathIndex != fullPathIndex ||
-                entry.newLine > startLine ||
-                entry.newLine + entry.endLineOffset < startLine)
-                continue;
-
-            startLine += entry.oldLine - entry.newLine;
-            break;
-        }
-    }
-}
-
 HRESULT Modules::GetFrameILAndSequencePoint(
     ICorDebugFrame *pFrame,
     ULONG32 &ilOffset,
@@ -558,25 +336,20 @@ HRESULT Modules::GetFrameILAndSequencePoint(
     CORDB_ADDRESS modAddress;
     IfFailRet(pModule->GetBaseAddress(&modAddress));
 
-    std::lock_guard<std::mutex> lockModulesInfo(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
+    return GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
-        return E_FAIL;
+        if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
+            return E_FAIL;
 
-    IfFailRet(GetSequencePointByILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, &sequencePoint));
+        IfFailRet(GetSequencePointByILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, &sequencePoint));
 
-    // In case Hot Reload we may have line updates that we must take into account.
-    unsigned fullPathIndex;
-    IfFailRet(GetIndexBySourceFullPath(sequencePoint.document, fullPathIndex));
-    LineUpdatesForwardCorrection(fullPathIndex, methodToken, mdInfo.m_methodBlockUpdates, sequencePoint);
+        // In case Hot Reload we may have line updates that we must take into account.
+        unsigned fullPathIndex;
+        IfFailRet(GetIndexBySourceFullPath(sequencePoint.document, fullPathIndex));
+        LineUpdatesForwardCorrection(fullPathIndex, methodToken, mdInfo.m_methodBlockUpdates, sequencePoint);
 
-    return S_OK;
+        return S_OK;
+    });
 }
 
 HRESULT Modules::GetFrameILAndNextUserCodeILOffset(
@@ -645,20 +418,13 @@ HRESULT Modules::GetStepRangeFromCurrentIP(ICorDebugThread *pThread, COR_DEBUG_S
     ULONG32 ilStartOffset;
     ULONG32 ilEndOffset;
 
+    IfFailRet(GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-        auto info_pair = m_modulesInfo.find(modAddress);
-        if (info_pair == m_modulesInfo.end())
-        {
-            return E_FAIL;
-        }
-
-        ModuleInfo &mdInfo = info_pair->second;
         if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
             return E_FAIL;
 
-        IfFailRet(Interop::GetStepRangesFromIP(mdInfo.m_symbolReaderHandles[methodVersion - 1], nOffset, methodToken, &ilStartOffset, &ilEndOffset));
-    }
+        return Interop::GetStepRangesFromIP(mdInfo.m_symbolReaderHandles[methodVersion - 1], nOffset, methodToken, &ilStartOffset, &ilEndOffset);
+    }));
 
     if (ilStartOffset == ilEndOffset)
     {
@@ -702,102 +468,6 @@ HRESULT GetModuleId(ICorDebugModule *pModule, std::string &id)
     return S_OK;
 }
 
-// Caller must care about m_asyncMethodSteppingInfoMutex.
-HRESULT Modules::GetAsyncMethodSteppingInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion)
-{
-    if (asyncMethodSteppingInfo.modAddress == modAddress &&
-        asyncMethodSteppingInfo.methodToken == methodToken &&
-        asyncMethodSteppingInfo.methodVersion == methodVersion)
-        return S_OK;
-
-    if (!asyncMethodSteppingInfo.awaits.empty())
-        asyncMethodSteppingInfo.awaits.clear();
-
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
-    {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
-        return E_FAIL;
-
-    HRESULT Status;
-    std::vector<Interop::AsyncAwaitInfoBlock> AsyncAwaitInfo;
-    IfFailRet(Interop::GetAsyncMethodSteppingInfo(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, AsyncAwaitInfo, &asyncMethodSteppingInfo.lastIlOffset));
-
-    for (const auto &entry : AsyncAwaitInfo)
-    {
-        asyncMethodSteppingInfo.awaits.emplace_back(entry.yield_offset, entry.resume_offset);
-    }
-
-    asyncMethodSteppingInfo.modAddress = modAddress;
-    asyncMethodSteppingInfo.methodToken = methodToken;
-    asyncMethodSteppingInfo.methodVersion = methodVersion;
-
-    return S_OK;
-}
-
-// Check if method have await block. In this way we detect async method with awaits.
-// [in] modAddress - module address;
-// [in] methodToken - method token (from module with address modAddress).
-bool Modules::IsMethodHaveAwait(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion)
-{
-    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
-
-    return SUCCEEDED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion));
-}
-
-// Find await block after IL offset in particular async method and return await info, if present.
-// In case of async stepping, we need await info from PDB in order to setup breakpoints in proper places (yield and resume offsets).
-// [in] modAddress - module address;
-// [in] methodToken - method token (from module with address modAddress).
-// [in] ipOffset - IL offset;
-// [out] awaitInfo - result, next await info.
-bool Modules::FindNextAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 ipOffset, AwaitInfo **awaitInfo)
-{
-    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
-
-    if (FAILED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion)))
-        return false;
-
-    for (auto &await : asyncMethodSteppingInfo.awaits)
-    {
-        if (ipOffset <= await.yield_offset)
-        {
-            if (awaitInfo)
-                *awaitInfo = &await;
-            return true;
-        }
-        // Stop search, if IP inside 'await' routine.
-        else if (ipOffset < await.resume_offset)
-        {
-            break;
-        }
-    }
-
-    return false;
-}
-
-// Find last IL offset for user code in async method, if present.
-// In case of step-in and step-over we must detect last user code line in order to "emulate"
-// step-out (DebuggerOfWaitCompletion magic) instead.
-// [in] modAddress - module address;
-// [in] methodToken - method token (from module with address modAddress).
-// [out] lastIlOffset - result, IL offset for last user code line in async method.
-bool Modules::FindLastIlOffsetAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 &lastIlOffset)
-{
-    const std::lock_guard<std::mutex> lock(m_asyncMethodSteppingInfoMutex);
-
-    if (FAILED(GetAsyncMethodSteppingInfo(modAddress, methodToken, methodVersion)))
-        return false;
-
-    lastIlOffset = asyncMethodSteppingInfo.lastIlOffset;
-    return true;
-}
-
 static HRESULT LoadSymbols(IMetaDataImport *pMD, ICorDebugModule *pModule, VOID **ppSymbolReaderHandle)
 {
     HRESULT Status = S_OK;
@@ -826,133 +496,6 @@ static HRESULT LoadSymbols(IMetaDataImport *pMD, ICorDebugModule *pModule, VOID
     );
 }
 
-// Get data from 'MetadataUpdateHandler' attribute.
-// https://docs.microsoft.com/en-us/dotnet/api/system.reflection.metadata.metadataupdatehandlerattribute?view=net-6.0
-// Note, provided by attribute type have same format as Type.GetType(String) argument
-// https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype?view=net-6.0#system-type-gettype
-static HRESULT GetUpdateHandlerTypesForModule(IMetaDataImport *pMD, std::vector<std::string> &updateHandlerTypes)
-{
-    static const std::string metadataUpdateHandlerAttribute = "System.Reflection.Metadata.MetadataUpdateHandlerAttribute..ctor";
-
-    ULONG numAttributes = 0;
-    HCORENUM fEnum = NULL;
-    mdCustomAttribute attr;
-    while(SUCCEEDED(pMD->EnumCustomAttributes(&fEnum, 0, 0, &attr, 1, &numAttributes)) && numAttributes != 0)
-    {
-        std::string mdName;
-        mdToken tkObj = mdTokenNil;
-        mdToken tkType = mdTokenNil;
-        void const *pBlob = 0;
-        ULONG cbSize = 0;
-        if (FAILED(pMD->GetCustomAttributeProps(attr, &tkObj, &tkType, &pBlob, &cbSize)) ||
-            FAILED(TypePrinter::NameForToken(tkType, pMD, mdName, true, nullptr)))
-            continue;
-
-        if (mdName != metadataUpdateHandlerAttribute)
-            continue;
-
-        const unsigned char *pCBlob = (const unsigned char *)pBlob;
-        ULONG elementSize;
-
-        // 2 bytes - blob prolog 0x0001
-        pCBlob += 2;
-
-        ULONG stringSize;
-        elementSize = CorSigUncompressData(pCBlob, &stringSize);
-        pCBlob += elementSize;
-        updateHandlerTypes.emplace_back(pCBlob, pCBlob + stringSize);
-    }
-    pMD->CloseEnum(fEnum);
-
-    return S_OK;
-}
-
-// Parse type name. More info:
-// https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names
-// Note, in MetadataUpdateHandler attribute case, type name will not have assembly relative parts.
-// Backtick (`)      Precedes one or more digits representing the number of type parameters, located at the end of the name of a generic type.
-// Brackets ([])     Enclose a generic type argument list, for a constructed generic type; within a type argument list, enclose an assembly-qualified type.
-// Comma (,)         Precedes the Assembly name.
-// Period (.)        Denotes namespace identifiers.
-// Plus sign (+)     Precedes a nested class.
-static void ParceTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
-{
-    std::string::size_type genericDelimiterPos = fullName.find("`");
-    std::string fullTypeName;
-    std::string genericTypes;
-    if (genericDelimiterPos == std::string::npos)
-    {
-        fullTypeName = fullName;
-    }
-    else
-    {
-        fullTypeName = fullName.substr(0, genericDelimiterPos);
-        genericTypes = fullName.substr(genericDelimiterPos);
-    }
-
-    // TODO implement generic support (genericTypes) for update handler types
-
-    std::string::size_type nestedClassDelimiterPos = fullTypeName.find("+");
-    if (nestedClassDelimiterPos == std::string::npos)
-    {
-        mainTypeName = fullTypeName;
-    }
-    else
-    {
-        mainTypeName = fullTypeName.substr(0, nestedClassDelimiterPos);
-        while (nestedClassDelimiterPos != std::string::npos)
-        {
-            std::string::size_type startPos = nestedClassDelimiterPos + 1;
-            nestedClassDelimiterPos = fullTypeName.find("+", startPos);
-            if (nestedClassDelimiterPos == std::string::npos)
-                nestedClasses.emplace_back(fullName.substr(startPos, fullTypeName.size() - startPos));
-            else
-                nestedClasses.emplace_back(fullName.substr(startPos, nestedClassDelimiterPos - startPos));
-        }
-    }
-}
-
-// Scan module for 'MetadataUpdateHandler' attributes and store related to UpdateHandlerTypes ICorDebugType objects.
-// Note, 'MetadataUpdateHandler' attributes can't be changed/removed/added at Hot Reload.
-static HRESULT AddUpdateHandlerTypesForModule(ICorDebugModule *pModule, IMetaDataImport *pMD, std::list<ToRelease<ICorDebugType>> &modulesUpdateHandlerTypes)
-{
-    HRESULT Status;
-    std::vector<std::string> updateHandlerTypeNames;
-    IfFailRet(GetUpdateHandlerTypesForModule(pMD, updateHandlerTypeNames));
-
-    for (const auto &entry : updateHandlerTypeNames)
-    {
-        std::string mainTypeName;
-        std::vector<std::string> nestedClasses;
-        ParceTypeName(entry, mainTypeName, nestedClasses);
-
-        // Resolve main type part.
-        mdTypeDef typeToken = mdTypeDefNil;
-        IfFailRet(pMD->FindTypeDefByName(reinterpret_cast<LPCWSTR>(to_utf16(mainTypeName).c_str()), mdTypeDefNil, &typeToken));
-        if (typeToken == mdTypeDefNil)
-            return E_FAIL;
-
-        // Resolve nested type part.
-        for (const auto &nestedClassName : nestedClasses)
-        {
-            mdTypeDef classToken = mdTypeDefNil;
-            IfFailRet(pMD->FindTypeDefByName(reinterpret_cast<LPCWSTR>(to_utf16(nestedClassName).c_str()), typeToken, &classToken));
-            if (classToken == mdTypeDefNil)
-                return E_FAIL;
-            typeToken = classToken;
-        }
-
-        ToRelease<ICorDebugClass> pClass;
-        IfFailRet(pModule->GetClassFromToken(typeToken, &pClass));
-        ToRelease<ICorDebugClass2> pClass2;
-        IfFailRet(pClass->QueryInterface(IID_ICorDebugClass2, (LPVOID*) &pClass2));
-        modulesUpdateHandlerTypes.emplace_back();
-        IfFailRet(pClass2->GetParameterizedType(ELEMENT_TYPE_CLASS, 0, nullptr, &(modulesUpdateHandlerTypes.back())));
-    }
-
-    return S_OK;
-}
-
 HRESULT Modules::TryLoadModuleSymbols(ICorDebugModule *pModule, Module &module, bool needJMC, bool needHotReload, std::string &outputText)
 {
     HRESULT Status;
@@ -1004,7 +547,7 @@ HRESULT Modules::TryLoadModuleSymbols(ICorDebugModule *pModule, Module &module,
             }
         }
 
-        if (FAILED(FillSourcesCodeLinesForModule(pModule, pMDImport, pSymbolReaderHandle)))
+        if (FAILED(m_modulesSources.FillSourcesCodeLinesForModule(pModule, pMDImport, pSymbolReaderHandle)))
             LOGE("Could not load source lines related info from PDB file. Could produce failures during breakpoint's source path resolve in future.");
     }
 
@@ -1023,7 +566,7 @@ HRESULT Modules::TryLoadModuleSymbols(ICorDebugModule *pModule, Module &module,
     m_modulesInfo.insert(std::make_pair(baseAddress, std::move(mdInfo)));
 
     if (needHotReload)
-        IfFailRet(AddUpdateHandlerTypesForModule(pModule, pMDImport, m_modulesUpdateHandlerTypes));
+        IfFailRet(m_modulesAppUpdate.AddUpdateHandlerTypesForModule(pModule, pMDImport));
 
     return S_OK;
 }
@@ -1044,20 +587,13 @@ HRESULT Modules::GetFrameNamedLocalVariable(
 
     WCHAR wLocalName[mdNameLen] = W("\0");
 
+    IfFailRet(GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-        auto info_pair = m_modulesInfo.find(modAddress);
-        if (info_pair == m_modulesInfo.end())
-        {
-            return E_FAIL;
-        }
-
-        ModuleInfo &mdInfo = info_pair->second;
         if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
             return E_FAIL;
 
-        IfFailRet(Interop::GetNamedLocalVariableAndScope(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, localIndex, wLocalName, _countof(wLocalName), pIlStart, pIlEnd));
-    }
+        return Interop::GetNamedLocalVariableAndScope(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, localIndex, wLocalName, _countof(wLocalName), pIlStart, pIlEnd);
+    }));
 
     localName = wLocalName;
 
@@ -1075,19 +611,13 @@ HRESULT Modules::GetHoistedLocalScopes(
     CORDB_ADDRESS modAddress;
     IfFailRet(pModule->GetBaseAddress(&modAddress));
 
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
+    return GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
-        return E_FAIL;
+        if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
+            return E_FAIL;
 
-    IfFailRet(Interop::GetHoistedLocalScopes(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, data, hoistedLocalScopesCount));
-    return S_OK;
+        return Interop::GetHoistedLocalScopes(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, data, hoistedLocalScopesCount);
+    });
 }
 
 HRESULT Modules::GetModuleWithName(const std::string &name, ICorDebugModule **ppModule, bool onlyWithPDB)
@@ -1125,18 +655,13 @@ HRESULT Modules::GetNextUserCodeILOffsetInMethod(
     CORDB_ADDRESS modAddress;
     IfFailRet(pModule->GetBaseAddress(&modAddress));
 
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
+    return GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
-        return E_FAIL;
+        if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
+            return E_FAIL;
 
-    return Interop::GetNextUserCodeILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, ilNextOffset, noUserCodeFound);
+        return Interop::GetNextUserCodeILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, ilNextOffset, noUserCodeFound);
+    });
 }
 
 HRESULT Modules::GetSequencePointByILOffset(
@@ -1168,18 +693,13 @@ HRESULT Modules::GetSequencePointByILOffset(
     ULONG32 ilOffset,
     Modules::SequencePoint &sequencePoint)
 {
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
+    return GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
-        return E_FAIL;
+        if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
+            return E_FAIL;
 
-    return GetSequencePointByILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, &sequencePoint);
+        return GetSequencePointByILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, &sequencePoint);
+    });
 }
 
 HRESULT Modules::ForEachModule(std::function<HRESULT(ICorDebugModule *pModule)> cb)
@@ -1195,747 +715,41 @@ HRESULT Modules::ForEachModule(std::function<HRESULT(ICorDebugModule *pModule)>
     return S_OK;
 }
 
-static HRESULT GetPdbMethodsRanges(IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle, std::unordered_set<mdMethodDef> *methodTokens,
-                                   std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> &inputData)
-{
-    HRESULT Status;
-    // Note, we need 2 arrays of tokens - for normal methods and constructors (.ctor/.cctor, that could have segmented code).
-    std::vector<int32_t> constrTokens;
-    std::vector<int32_t> normalTokens;
-
-    ULONG numTypedefs = 0;
-    HCORENUM hEnum = NULL;
-    mdTypeDef typeDef;
-    while(SUCCEEDED(pMDImport->EnumTypeDefs(&hEnum, &typeDef, 1, &numTypedefs)) && numTypedefs != 0)
-    {
-        ULONG numMethods = 0;
-        HCORENUM fEnum = NULL;
-        mdMethodDef methodDef;
-        while(SUCCEEDED(pMDImport->EnumMethods(&fEnum, typeDef, &methodDef, 1, &numMethods)) && numMethods != 0)
-        {
-            if (methodTokens && methodTokens->find(methodDef) == methodTokens->end())
-                continue;
-
-            WCHAR funcName[mdNameLen];
-            ULONG funcNameLen;
-            if (FAILED(pMDImport->GetMethodProps(methodDef, nullptr, funcName, _countof(funcName), &funcNameLen,
-                                                 nullptr, nullptr, nullptr, nullptr, nullptr)))
-            {
-                continue;
-            }
-
-            if (str_equal(funcName, W(".ctor")) || str_equal(funcName, W(".cctor")))
-                constrTokens.emplace_back(methodDef);
-            else
-                normalTokens.emplace_back(methodDef);
-        }
-        pMDImport->CloseEnum(fEnum);
-    }
-    pMDImport->CloseEnum(hEnum);
-
-    if (sizeof(std::size_t) > sizeof(std::uint32_t) &&
-        (constrTokens.size() > std::numeric_limits<uint32_t>::max() || normalTokens.size() > std::numeric_limits<uint32_t>::max()))
-    {
-        LOGE("Too big token arrays.");
-        return E_FAIL;
-    }
-
-    PVOID data = nullptr;
-    IfFailRet(Interop::GetModuleMethodsRanges(pSymbolReaderHandle, (uint32_t)constrTokens.size(), constrTokens.data(), (uint32_t)normalTokens.size(), normalTokens.data(), &data));
-    if (data == nullptr)
-        return S_OK;
-
-    inputData.reset((module_methods_data_t*)data);
-    return S_OK;
-}
-
-// Caller must care about m_sourcesInfoMutex.
-HRESULT Modules::GetFullPathIndex(BSTR document, unsigned &fullPathIndex)
-{
-    std::string fullPath = to_utf8(document);
-#ifdef WIN32
-    HRESULT Status;
-    std::string initialFullPath = fullPath;
-    IfFailRet(Interop::StringToUpper(fullPath));
-#endif
-    auto findPathIndex = m_sourcePathToIndex.find(fullPath);
-    if (findPathIndex == m_sourcePathToIndex.end())
-    {
-        fullPathIndex = (unsigned)m_sourceIndexToPath.size();
-        m_sourcePathToIndex.emplace(std::make_pair(fullPath, fullPathIndex));
-        m_sourceIndexToPath.emplace_back(fullPath);
-#ifdef WIN32
-        m_sourceIndexToInitialFullPath.emplace_back(initialFullPath);
-#endif
-        m_sourceNameToFullPathsIndexes[GetFileName(fullPath)].emplace(fullPathIndex);
-        m_sourcesMethodsData.emplace_back(std::vector<FileMethodsData>{});
-    }
-    else
-        fullPathIndex = findPathIndex->second;
-
-    return S_OK;
-}
-
-HRESULT Modules::FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle)
-{
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
-
-    HRESULT Status;
-    std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> inputData;
-    IfFailRet(GetPdbMethodsRanges(pMDImport, pSymbolReaderHandle, nullptr, inputData));
-    if (inputData == nullptr)
-        return S_OK;
-
-    // Usually, modules provide files with unique full paths for sources.
-    m_sourceIndexToPath.reserve(m_sourceIndexToPath.size() + inputData->fileNum);
-    m_sourcesMethodsData.reserve(m_sourcesMethodsData.size() + inputData->fileNum);
-#ifdef WIN32
-    m_sourceIndexToInitialFullPath.reserve(m_sourceIndexToInitialFullPath.size() + inputData->fileNum);
-#endif
-
-    CORDB_ADDRESS modAddress;
-    IfFailRet(pModule->GetBaseAddress(&modAddress));
-
-    for (int i = 0; i < inputData->fileNum; i++)
-    {
-        unsigned fullPathIndex;
-        IfFailRet(GetFullPathIndex(inputData->moduleMethodsData[i].document, fullPathIndex));
-
-        m_sourcesMethodsData[fullPathIndex].emplace_back(FileMethodsData{});
-        auto &fileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
-        fileMethodsData.modAddress = modAddress;
-
-        // Note, don't reorder input data, since it have almost ideal order for us.
-        // For example, for Private.CoreLib (about 22000 methods) only 8 relocations were made.
-        // In case default methods ordering will be dramatically changed, we could use data reordering,
-        // for example based on this solution:
-        //    struct compare {
-        //        bool operator()(const method_data_t &lhs, const method_data_t &rhs) const
-        //        { return lhs.endLine > rhs.endLine || (lhs.endLine == rhs.endLine && lhs.endColumn > rhs.endColumn); }
-        //    };
-        //    std::multiset<method_data_t, compare> orderedInputData;
-        std::map<size_t, std::set<method_data_t>> inputMethodsData;
-        for (int j = 0; j < inputData->moduleMethodsData[i].methodNum; j++)
-        {
-            AddMethodData(inputMethodsData, fileMethodsData.multiMethodsData, inputData->moduleMethodsData[i].methodsData[j], 0);
-        }
-
-        fileMethodsData.methodsData.resize(inputMethodsData.size());
-        for (size_t i =  0; i < inputMethodsData.size(); i++)
-        {
-            fileMethodsData.methodsData[i].resize(inputMethodsData[i].size());
-            std::copy(inputMethodsData[i].begin(), inputMethodsData[i].end(), fileMethodsData.methodsData[i].begin());
-        }
-        for (auto &data : fileMethodsData.multiMethodsData)
-        {
-            data.second.shrink_to_fit();
-        }
-    }
-
-    m_sourcesMethodsData.shrink_to_fit();
-    m_sourceIndexToPath.shrink_to_fit();
-#ifdef WIN32
-    m_sourceIndexToInitialFullPath.shrink_to_fit();
-#endif
-
-    return S_OK;
-}
-
-static HRESULT LineUpdatesForMethodData(unsigned fullPathIndex, method_data_t &methodData, const std::vector<block_update_t> &blockUpdate, method_block_updates_t &methodBlockUpdates)
-{
-    for (const auto &block : blockUpdate)
-    {
-        if (block.oldLine < 0 || block.endLineOffset < 0 || methodData.endLine < 0)
-            return E_INVALIDARG;
-
-        // Note, endLineOffset could be std::numeric_limits<int32_t>::max() (max line number in C# source), so, we forced to cast it first.
-        // Also, this is why we test that method within range and after that negate result.
-        if (!(methodData.startLine >= block.oldLine &&
-              (uint32_t)methodData.endLine <= (uint32_t)block.oldLine + (uint32_t)block.endLineOffset))
-            continue;
-
-        const int32_t codeLineOffset = block.newLine - block.oldLine;
-
-        auto updateCashe = [&]() -> bool
-        {
-            auto findMethod = methodBlockUpdates.find(methodData.methodDef);
-            if (findMethod == methodBlockUpdates.end())
-                return false;
-
-            for (auto &entry : findMethod->second)
-            {
-                if (fullPathIndex == entry.fullPathIndex &&
-                    methodData.startLine == entry.newLine &&
-                    methodData.endLine == entry.newLine + entry.endLineOffset)
-                {
-                    // All we need for previous stored data is change newLine, since oldLine will be the same (PDB for this method version was not changed).
-                    entry.newLine += codeLineOffset;
-                    return true;
-                }
-            }
-            return false;
-        };
-        if (!updateCashe())
-            methodBlockUpdates[methodData.methodDef].emplace_back(fullPathIndex, methodData.startLine + codeLineOffset, methodData.startLine, methodData.endLine - methodData.startLine);
-
-        methodData.startLine += codeLineOffset;
-        methodData.endLine += codeLineOffset;
-
-        break;
-    }
-
-    return S_OK;
-}
-
-HRESULT Modules::UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, std::unordered_set<mdMethodDef> methodTokens,
-                                                 src_block_updates_t &srcBlockUpdates, PVOID pSymbolReaderHandle, method_block_updates_t &methodBlockUpdates)
-{
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
-
-    HRESULT Status;
-    std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> inputData;
-    IfFailRet(GetPdbMethodsRanges(pMDImport, pSymbolReaderHandle, &methodTokens, inputData));
-
-    struct src_update_data_t
-    {
-        std::vector<block_update_t> blockUpdate;
-        int32_t methodNum = 0;
-        method_data_t *methodsData = nullptr;
-    };
-    std::unordered_map<unsigned, src_update_data_t> srcUpdateData;
-
-    if (inputData)
-    {
-        for (int i = 0; i < inputData->fileNum; i++)
-        {
-            unsigned fullPathIndex;
-            IfFailRet(GetFullPathIndex(inputData->moduleMethodsData[i].document, fullPathIndex));
-
-            srcUpdateData[fullPathIndex].methodNum = inputData->moduleMethodsData[i].methodNum;
-            srcUpdateData[fullPathIndex].methodsData = inputData->moduleMethodsData[i].methodsData;
-        }
-    }
-    for (const auto &entry : srcBlockUpdates)
-    {
-        srcUpdateData[entry.first].blockUpdate = entry.second;
-    }
-
-    if (srcUpdateData.empty())
-        return S_OK;
-
-    CORDB_ADDRESS modAddress;
-    IfFailRet(pModule->GetBaseAddress(&modAddress));
-
-    for (const auto &updateData : srcUpdateData)
-    {
-        const unsigned fullPathIndex = updateData.first;
-
-        std::map<size_t, std::set<method_data_t>> inputMethodsData;
-        if (m_sourcesMethodsData[fullPathIndex].empty())
-        { // New source file added.
-            m_sourcesMethodsData[fullPathIndex].emplace_back(FileMethodsData{});
-            auto &tmpFileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
-            tmpFileMethodsData.modAddress = modAddress;
-
-            for (int j = 0; j < updateData.second.methodNum; j++)
-            {
-                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
-            }
-        }
-        else
-        {
-            std::unordered_set<mdMethodDef> inputMetodDefSet;
-            for (int j = 0; j < updateData.second.methodNum; j++)
-            {
-                inputMetodDefSet.insert(updateData.second.methodsData[j].methodDef);
-                // All sequence points related to this method were updated and provide proper lines from PDB directly.
-                methodBlockUpdates.erase(updateData.second.methodsData[j].methodDef);
-            }
-
-            // Move multiMethodsData first (since this is constructors and all will be on level 0 for sure).
-            // Use std::unordered_set here instead array for fast search.
-            auto &tmpFileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
-            std::vector<method_data_t> tmpMultiMethodsData;
-            for (const auto &entryData : tmpFileMethodsData.multiMethodsData)
-            {
-                auto findData = inputMetodDefSet.find(entryData.first.methodDef);
-                if (findData == inputMetodDefSet.end())
-                    tmpMultiMethodsData.emplace_back(entryData.first);
-
-                for (const auto &entryMethodDef : entryData.second)
-                {
-                    findData = inputMetodDefSet.find(entryMethodDef);
-                    if (findData == inputMetodDefSet.end())
-                        tmpMultiMethodsData.emplace_back(entryMethodDef, entryData.first.startLine, entryData.first.endLine,
-                                                         entryData.first.startColumn, entryData.first.endColumn);
-                }
-            }
-
-            tmpFileMethodsData.multiMethodsData.clear();
-            for (auto &methodData : tmpMultiMethodsData)
-            {
-                IfFailRet(LineUpdatesForMethodData(fullPathIndex, methodData, updateData.second.blockUpdate, methodBlockUpdates));
-                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, methodData, 0);
-            }
-
-            // Move normal methods.
-            for (auto &methodsData : tmpFileMethodsData.methodsData)
-            {
-                for (auto &methodData : methodsData)
-                {
-                    auto findData = inputMetodDefSet.find(methodData.methodDef);
-                    if (findData == inputMetodDefSet.end())
-                    {
-                        IfFailRet(LineUpdatesForMethodData(fullPathIndex, methodData, updateData.second.blockUpdate, methodBlockUpdates));
-                        AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, methodData, 0);
-                    }
-                }
-            }
-            tmpFileMethodsData.methodsData.clear();
-
-            // Move new and modified methods.
-            for (int j = 0; j < updateData.second.methodNum; j++)
-            {
-                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
-            }
-        }
-
-        auto &fileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
-        fileMethodsData.methodsData.resize(inputMethodsData.size());
-        for (size_t i =  0; i < inputMethodsData.size(); i++)
-        {
-            fileMethodsData.methodsData[i].resize(inputMethodsData[i].size());
-            std::copy(inputMethodsData[i].begin(), inputMethodsData[i].end(), fileMethodsData.methodsData[i].begin());
-        }
-        for (auto &data : fileMethodsData.multiMethodsData)
-        {
-            data.second.shrink_to_fit();
-        }
-    }
-
-    return S_OK;
-}
-
-HRESULT Modules::ResolveRelativeSourceFileName(std::string &filename)
-{
-    // IMPORTANT! Caller should care about m_sourcesInfoMutex.
-    auto findIndexesByFileName = m_sourceNameToFullPathsIndexes.find(GetFileName(filename));
-    if (findIndexesByFileName == m_sourceNameToFullPathsIndexes.end())
-        return E_FAIL;
-
-    auto const &possiblePathsIndexes = findIndexesByFileName->second;
-    std::string result = filename;
-
-    // Care about all "./" and "../" first.
-    std::list<std::string> pathDirs;
-    std::size_t i;
-    while ((i = result.find_first_of("/\\")) != std::string::npos)
-    {
-        std::string pathElement = result.substr(0, i);
-        if (pathElement == "..")
-        {
-            if (!pathDirs.empty())
-                pathDirs.pop_front();
-        }
-        else if (pathElement != ".")
-            pathDirs.push_front(pathElement);
-
-        result = result.substr(i + 1);
-    }
-    for (const auto &dir : pathDirs)
-    {
-        result = dir + '/' + result;
-    }
-
-    // The problem is - we could have several assemblies that could have same source file name with different path's root.
-    // We don't really have a lot of options here, so, we assume, that all possible sources paths have same root and just find the shortest.
-    if (result == GetFileName(result))
-    {
-        auto it = std::min_element(possiblePathsIndexes.begin(), possiblePathsIndexes.end(),
-                        [&](const unsigned a, const unsigned b){ return m_sourceIndexToPath[a].size() < m_sourceIndexToPath[b].size(); } );
-
-        filename = it == possiblePathsIndexes.end() ? result : m_sourceIndexToPath[*it];
-        return S_OK;
-    }
-
-    std::list<std::string> possibleResults;
-    for (const auto pathIndex : possiblePathsIndexes)
-    {
-        if (result.size() > m_sourceIndexToPath[pathIndex].size())
-            continue;
-
-        // Note, since assemblies could be built in different OSes, we could have different delimiters in source files paths.
-        auto BinaryPredicate = [](const char& a, const char& b)
-        {
-            if ((a == '/' || a == '\\') && (b == '/' || b == '\\')) return true;
-            return a == b;
-        };
-
-        // since C++17
-        //if (std::equal(result.begin(), result.end(), path.end() - result.size(), BinaryPredicate))
-        //    possibleResults.push_back(path);
-        auto first1 = result.begin();
-        auto last1 = result.end();
-        auto first2 = m_sourceIndexToPath[pathIndex].end() - result.size();
-        auto equal = [&]()
-        {
-            for (; first1 != last1; ++first1, ++first2)
-            {
-                if (!BinaryPredicate(*first1, *first2))
-                    return false;
-            }
-            return true;
-        };
-        if (equal())
-            possibleResults.push_back(m_sourceIndexToPath[pathIndex]);
-    }
-    // The problem is - we could have several assemblies that could have sources with same relative paths with different path's root.
-    // We don't really have a lot of options here, so, we assume, that all possible sources paths have same root and just find the shortest.
-    if (!possibleResults.empty())
-    {
-        filename = possibleResults.front();
-        for (const auto& path : possibleResults)
-        {
-            if (filename.length() > path.length())
-                filename = path;
-        }
-        return S_OK;
-    }
-
-    return E_FAIL;
-}
-
 HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::string filename, /*out*/ unsigned &fullname_index,
-                                   /*in*/ int sourceLine, /*out*/ std::vector<resolved_bp_t> &resolvedPoints)
+                                   /*in*/ int sourceLine, /*out*/ std::vector<ModulesSources::resolved_bp_t> &resolvedPoints)
 {
-    HRESULT Status;
 #ifdef WIN32
+    HRESULT Status;
     IfFailRet(Interop::StringToUpper(filename));
 #endif
 
+    // Note, in all code we use m_modulesInfoMutex > m_sourcesInfoMutex lock sequence.
     std::lock_guard<std::mutex> lockModulesInfo(m_modulesInfoMutex);
-    std::lock_guard<std::mutex> lockSourcesInfo(m_sourcesInfoMutex);
-
-    auto findIndex = m_sourcePathToIndex.find(filename);
-    if (findIndex == m_sourcePathToIndex.end())
-    {
-        // Check for absolute path.
-#ifdef WIN32
-        // Check, if start from drive letter, for example "D:\" or "D:/".
-        if (filename.size() > 2 && filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\'))
-#else
-        if (filename[0] == '/')
-#endif
-        {
-            return E_FAIL;
-        }
-
-        IfFailRet(ResolveRelativeSourceFileName(filename));
-
-        findIndex = m_sourcePathToIndex.find(filename);
-        if (findIndex == m_sourcePathToIndex.end())
-            return E_FAIL;
-    }
-
-    fullname_index = findIndex->second;
-
-    struct resolved_input_bp_t
-    {
-        int32_t startLine;
-        int32_t endLine;
-        uint32_t ilOffset;
-        uint32_t methodToken;
-    };
-
-    struct resolved_input_bp_t_deleter
-    {
-        void operator()(resolved_input_bp_t *p) const
-        {
-            Interop::CoTaskMemFree(p);
-        }
-    };
-
-    for (const auto &sourceData : m_sourcesMethodsData[findIndex->second])
-    {
-        if (modAddress && modAddress != sourceData.modAddress)
-            continue;
-
-        std::vector<mdMethodDef> Tokens;
-        int32_t correctedStartLine = sourceLine;
-        mdMethodDef closestNestedToken = 0;
-        if (!GetMethodTokensByLineNumber(sourceData.methodsData, sourceData.multiMethodsData, correctedStartLine, Tokens, closestNestedToken))
-            continue;
-        // correctedStartLine - in case line not belong any methods, if possible, will be "moved" to first line of method below sourceLine.
-
-        auto info_pair = m_modulesInfo.find(sourceData.modAddress);
-        if (info_pair == m_modulesInfo.end())
-            return E_FAIL; // we must have it, since we loaded data from it
-
-        if ((int32_t)Tokens.size() > std::numeric_limits<int32_t>::max())
-        {
-            LOGE("Too big token arrays.");
-            return E_FAIL;
-        }
-
-        ModuleInfo &mdInfo = info_pair->second;
-        if (mdInfo.m_symbolReaderHandles.empty())
-            continue;
-
-        // In case one source line (field/property initialization) compiled into all constructors, after Hot Reload, constructors may have different
-        // code version numbers, that mean debug info located in different symbol readers.
-        std::vector<PVOID> symbolReaderHandles;
-        symbolReaderHandles.reserve(Tokens.size());
-        for (auto methodToken : Tokens)
-        {
-            // Note, new breakpoints could be setup for last code version only, since protocols (MI, VSCode, ...) provide source:line data only.
-            ULONG32 currentVersion;
-            ToRelease<ICorDebugFunction> pFunction;
-            if (FAILED(info_pair->second.m_iCorModule->GetFunctionFromToken(methodToken, &pFunction)) ||
-                FAILED(pFunction->GetCurrentVersionNumber(&currentVersion)))
-            {
-                symbolReaderHandles.emplace_back(mdInfo.m_symbolReaderHandles[0]);
-                continue;
-            }
-
-            assert(mdInfo.m_symbolReaderHandles.size() >= currentVersion);
-            symbolReaderHandles.emplace_back(mdInfo.m_symbolReaderHandles[currentVersion - 1]);
-        }
-
-        // In case Hot Reload we may have line updates that we must take into account.
-        LineUpdatesBackwardCorrection(findIndex->second, Tokens[0], mdInfo.m_methodBlockUpdates, correctedStartLine);
-
-        PVOID data = nullptr;
-        int32_t Count = 0;
-#ifndef _WIN32
-        std::string fullName = m_sourceIndexToPath[findIndex->second];
-#else
-        std::string fullName = m_sourceIndexToInitialFullPath[findIndex->second];
-#endif
-        if (FAILED(Interop::ResolveBreakPoints(symbolReaderHandles.data(), (int32_t)Tokens.size(), Tokens.data(),
-                                               correctedStartLine, closestNestedToken, Count, fullName, &data))
-            || data == nullptr)
-        {
-            continue;
-        }
-        std::unique_ptr<resolved_input_bp_t, resolved_input_bp_t_deleter> inputData((resolved_input_bp_t*)data);
-
-        for (int32_t i = 0; i < Count; i++)
-        {
-            info_pair->second.m_iCorModule->AddRef();
-
-            // In case Hot Reload we may have line updates that we must take into account.
-            LineUpdatesForwardCorrection(findIndex->second, inputData.get()[i].methodToken, mdInfo.m_methodBlockUpdates, inputData.get()[i]);
-
-            resolvedPoints.emplace_back(resolved_bp_t(inputData.get()[i].startLine, inputData.get()[i].endLine, inputData.get()[i].ilOffset,
-                                                      inputData.get()[i].methodToken, info_pair->second.m_iCorModule.GetPtr()));
-        }
-    }
-
-    return S_OK;
-}
-
-static HRESULT LoadLineUpdatesFile(Modules *pModules, const std::string &lineUpdates, src_block_updates_t &srcBlockUpdates)
-{
-    std::ifstream lineUpdatesFileStream(lineUpdates, std::ios::in | std::ios::binary);
-    if (!lineUpdatesFileStream.is_open())
-        return COR_E_FILENOTFOUND;
-
-    HRESULT Status;
-    uint32_t sourcesCount = 0;
-    lineUpdatesFileStream.read((char*)&sourcesCount, 4);
-    if (lineUpdatesFileStream.fail())
-        return E_FAIL;
-
-    struct line_update_t
-    {
-        int32_t newLine;
-        int32_t oldLine;
-    };
-    std::unordered_map<unsigned /*source fullPathIndex*/, std::vector<line_update_t>> lineUpdatesData;
-
-    for (uint32_t i = 0; i < sourcesCount; i++)
-    {
-        uint32_t stringSize = 0;
-        lineUpdatesFileStream.read((char*)&stringSize, 4);
-        if (lineUpdatesFileStream.fail())
-            return E_FAIL;
-
-        std::string fullPath;
-        fullPath.resize(stringSize);
-        lineUpdatesFileStream.read((char*)fullPath.data(), stringSize);
-        if (lineUpdatesFileStream.fail())
-            return E_FAIL;
-
-        unsigned fullPathIndex;
-        IfFailRet(pModules->GetIndexBySourceFullPath(fullPath, fullPathIndex));
-
-        uint32_t updatesCount = 0;
-        lineUpdatesFileStream.read((char*)&updatesCount, 4);
-        if (lineUpdatesFileStream.fail())
-            return E_FAIL;
-
-        if (updatesCount == 0)
-            continue;
-
-        std::vector<line_update_t> &lineUpdates = lineUpdatesData[fullPathIndex];
-        lineUpdates.resize(updatesCount);
-
-        lineUpdatesFileStream.read((char*)lineUpdates.data(), updatesCount * sizeof(line_update_t));
-        if (lineUpdatesFileStream.fail())
-            return E_FAIL;
-    }
-
-    line_update_t startBlock;
-    const int32_t empty = -1;
-    for (const auto &updateFileData : lineUpdatesData)
-    {
-        startBlock.newLine = empty;
-        for (const auto &lineUpdates : updateFileData.second)
-        {
-            if (lineUpdates.newLine != lineUpdates.oldLine)
-            {
-                assert(startBlock.newLine == empty);
-
-                startBlock.newLine = lineUpdates.newLine;
-                startBlock.oldLine = lineUpdates.oldLine;
-            }
-            else
-            {
-                // We use (newLine == oldLine) entry in LineUpdates as "end line" marker for moved region.
-                if (startBlock.newLine != empty)
-                    srcBlockUpdates[updateFileData.first].emplace_back(startBlock.newLine + 1, startBlock.oldLine + 1, lineUpdates.oldLine - 1 - startBlock.oldLine);
-
-                startBlock.newLine = empty;
-            }
-        }
-        // In case this is last method in source file, Roslyn don't provide "end line" in LineUpdates, use max source line as "end line".
-        if (startBlock.newLine != empty)
-            srcBlockUpdates[updateFileData.first].emplace_back(startBlock.newLine + 1, startBlock.oldLine + 1, std::numeric_limits<int32_t>::max());
-    }
-
-    return S_OK;
+    return m_modulesSources.ResolveBreakpoint(this, modAddress, filename, fullname_index, sourceLine, resolvedPoints);
 }
 
 HRESULT Modules::ApplyPdbDeltaAndLineUpdates(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB,
                                              const std::string &lineUpdates, std::unordered_set<mdMethodDef> &methodTokens)
 {
-    HRESULT Status;
-    CORDB_ADDRESS modAddress;
-    IfFailRet(pModule->GetBaseAddress(&modAddress));
-
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
-    {
-        return E_FAIL; // Deltas could be applied for already loaded modules with PDB only.
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.empty())
-        return E_FAIL; // Deltas could be applied for already loaded modules with PDB only.
-
-    PVOID pSymbolReaderHandle = nullptr;
-    IfFailRet(Interop::LoadDeltaPdb(deltaPDB, &pSymbolReaderHandle, methodTokens));
-    // Note, even if methodTokens is empty, pSymbolReaderHandle must be added into vector (we use indexes that correspond to il/metadata apply number + will care about release it in proper way).
-    mdInfo.m_symbolReaderHandles.emplace_back(pSymbolReaderHandle);
-
-    src_block_updates_t srcBlockUpdates;
-    IfFailRet(LoadLineUpdatesFile(this, lineUpdates, srcBlockUpdates));
-
-    if (methodTokens.empty() && srcBlockUpdates.empty())
-        return S_OK;
-
-    if (needJMC && !methodTokens.empty())
-        DisableJMCByAttributes(pModule, methodTokens);
-
-    ToRelease<IUnknown> pMDUnknown;
-    IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
-    ToRelease<IMetaDataImport> pMDImport;
-    IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMDImport));
-
-    return UpdateSourcesCodeLinesForModule(pModule, pMDImport, methodTokens, srcBlockUpdates, pSymbolReaderHandle, mdInfo.m_methodBlockUpdates);
+    return m_modulesSources.ApplyPdbDeltaAndLineUpdates(this, pModule, needJMC, deltaPDB, lineUpdates, methodTokens);
 }
 
 HRESULT Modules::GetSourceFullPathByIndex(unsigned index, std::string &fullPath)
 {
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
-
-    if (m_sourceIndexToPath.size() <= index)
-        return E_FAIL;
-
-#ifndef _WIN32
-    fullPath = m_sourceIndexToPath[index];
-#else
-    fullPath = m_sourceIndexToInitialFullPath[index];
-#endif
-
-    return S_OK;
+    return m_modulesSources.GetSourceFullPathByIndex(index, fullPath);
 }
 
 HRESULT Modules::GetIndexBySourceFullPath(std::string fullPath, unsigned &index)
 {
-#ifdef WIN32
-    HRESULT Status;
-    IfFailRet(Interop::StringToUpper(fullPath));
-#endif
-
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
-
-    auto findIndex = m_sourcePathToIndex.find(fullPath);
-    if (findIndex == m_sourcePathToIndex.end())
-        return E_FAIL;
-
-    index = findIndex->second;
-    return S_OK;
+    return m_modulesSources.GetIndexBySourceFullPath(fullPath, index);
 }
 
 void Modules::FindFileNames(string_view pattern, unsigned limit, std::function<void(const char *)> cb)
 {
-#ifdef WIN32
-    std::string uppercase {pattern};
-    if (FAILED(Interop::StringToUpper(uppercase)))
-        return;
-    pattern = uppercase;
-#endif
-
-    auto check = [&](const std::string& str)
-    {
-        if (limit == 0)
-            return false;
-
-        auto pos = str.find(pattern);
-        if (pos != std::string::npos && (pos == 0 || str[pos-1] == '/' || str[pos-1] == '\\'))
-        {
-            limit--;
-#ifndef _WIN32
-            cb(str.c_str());
-#else
-            auto it = m_sourcePathToIndex.find(str);
-            cb (it != m_sourcePathToIndex.end() ? m_sourceIndexToInitialFullPath[it->second].c_str() : str.c_str());
-#endif
-        }
-
-        return true;
-    };
-
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
-    for (const auto &pair : m_sourceNameToFullPathsIndexes)
-    {
-        LOGD("first '%s'", pair.first.c_str());
-        if (!check(pair.first))
-            return;
-
-        for (const unsigned fileIndex : pair.second)
-        {
-            LOGD("second '%s'", m_sourceIndexToPath[fileIndex].c_str());
-            if (!check(m_sourceIndexToPath[fileIndex]))
-                return;
-        }
-    }
+    m_modulesSources.FindFileNames(pattern, limit, cb);
 }
 
-void Modules::FindFunctions(string_view pattern, unsigned limit, std::function<void(const char *)> cb)
+void Modules::FindFunctions(Utility::string_view pattern, unsigned limit, std::function<void(const char *)> cb)
 {
     auto functor = [&](const std::string& fullName, mdMethodDef &)
     {
@@ -1967,34 +781,24 @@ HRESULT Modules::GetSource(ICorDebugModule *pModule, const std::string &sourcePa
     CORDB_ADDRESS modAddress;
     IfFailRet(pModule->GetBaseAddress(&modAddress));
 
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    auto info_pair = m_modulesInfo.find(modAddress);
-    if (info_pair == m_modulesInfo.end())
+    return GetModuleInfo(modAddress, [&](ModuleInfo &mdInfo) -> HRESULT
     {
-        return E_FAIL;
-    }
-
-    ModuleInfo &mdInfo = info_pair->second;
-    if (mdInfo.m_symbolReaderHandles.size() > 1)
-    {
-        LOGE("This feature not support simultaneous work with Hot Reload.");
-        return E_FAIL;
-    }
+        if (mdInfo.m_symbolReaderHandles.size() > 1)
+        {
+            LOGE("This feature not support simultaneous work with Hot Reload.");
+            return E_FAIL;
+        }
 
-    return mdInfo.m_symbolReaderHandles.empty() ?
-           E_FAIL :
-           Interop::GetSource(mdInfo.m_symbolReaderHandles[0], sourcePath, (PVOID*)fileBuf, fileLen);
+        return mdInfo.m_symbolReaderHandles.empty() ?
+            E_FAIL :
+            Interop::GetSource(mdInfo.m_symbolReaderHandles[0], sourcePath, (PVOID*)fileBuf, fileLen);
+    });
 }
 
 void Modules::CopyModulesUpdateHandlerTypes(std::vector<ToRelease<ICorDebugType>> &modulesUpdateHandlerTypes)
 {
     std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
-    modulesUpdateHandlerTypes.reserve(m_modulesUpdateHandlerTypes.size());
-    for (ToRelease<ICorDebugType> &updateHandlerType : m_modulesUpdateHandlerTypes)
-    {
-        updateHandlerType->AddRef();
-        modulesUpdateHandlerTypes.emplace_back(updateHandlerType.GetPtr());
-    }
+    m_modulesAppUpdate.CopyModulesUpdateHandlerTypes(modulesUpdateHandlerTypes);
 }
 
 } // namespace netcoredbg
index d8cfcacc323709580235bf9b00cf4f66add9d369..b9d74b490b345956626e560f73d015bd7c16eb26 100644 (file)
 
 #include <functional>
 #include <unordered_map>
-#include <set>
-#include <list>
 #include <mutex>
 #include <memory>
 #include "interfaces/types.h"
+#include "metadata/modules_app_update.h"
+#include "metadata/modules_sources.h"
 #include "utils/string_view.h"
 #include "utils/torelease.h"
 #include "utils/utf.h"
 
 namespace netcoredbg
 {
-using Utility::string_view;
-
-typedef std::function<HRESULT(ICorDebugModule *, mdMethodDef &)> ResolveFuncBreakpointCallback;
-
-struct DebuggerAttribute
-{
-    static const char NonUserCode[];
-    static const char StepThrough[];
-    static const char Hidden[];
-};
-
-bool HasAttribute(IMetaDataImport *pMD, mdToken tok, const char *attrName);
-bool HasAttribute(IMetaDataImport *pMD, mdToken tok, std::vector<std::string> &attrNames);
-HRESULT DisableJMCByAttributes(ICorDebugModule *pModule);
-HRESULT DisableJMCByAttributes(ICorDebugModule *pModule, const std::unordered_set<mdMethodDef> &methodTokens);
-
-struct method_data_t
-{
-    mdMethodDef methodDef;
-    int32_t startLine; // first segment/method SequencePoint's startLine
-    int32_t endLine; // last segment/method SequencePoint's endLine
-    int32_t startColumn; // first segment/method SequencePoint's startColumn
-    int32_t endColumn; // last segment/method SequencePoint's endColumn
-
-    method_data_t() :
-        methodDef(0),
-        startLine(0),
-        endLine(0),
-        startColumn(0),
-        endColumn(0)
-    {}
-
-    method_data_t(mdMethodDef methodDef_, int32_t startLine_, int32_t endLine_, int32_t startColumn_, int32_t endColumn_) :
-        methodDef(methodDef_),
-        startLine(startLine_),
-        endLine(endLine_),
-        startColumn(startColumn_),
-        endColumn(endColumn_)
-    {}
-
-    bool operator < (const method_data_t &other) const
-    {
-        return endLine < other.endLine || (endLine == other.endLine && endColumn < other.endColumn);
-    }
-
-    bool operator < (const int32_t lineNum) const
-    {
-        return endLine < lineNum;
-    }
-
-    bool operator == (const method_data_t &other) const
-    {
-        return methodDef == other.methodDef &&
-               startLine == other.startLine && endLine == other.endLine &&
-               startColumn == other.startColumn && endColumn == other.endColumn;
-    }
-
-    bool NestedInto(const method_data_t &other) const
-    {
-        assert(startLine != other.startLine || startColumn != other.startColumn);
-        assert(endLine != other.endLine || endColumn != other.endColumn);
-
-        return (startLine > other.startLine || (startLine == other.startLine && startColumn > other.startColumn)) &&
-            (endLine < other.endLine || (endLine == other.endLine && endColumn < other.endColumn));
-    }
-};
-
-struct method_data_t_hash
-{
-    size_t operator()(const method_data_t &p) const
-    {
-        return p.methodDef + (uint32_t)p.startLine * 100 + (uint32_t)p.endLine * 1000;
-    }
-};
-
 
 HRESULT GetModuleId(ICorDebugModule *pModule, std::string &id);
 std::string GetModuleFileName(ICorDebugModule *pModule);
 HRESULT IsModuleHaveSameName(ICorDebugModule *pModule, const std::string &Name, bool isFullPath);
 
-struct block_update_t
-{
-    int32_t newLine;
-    int32_t oldLine;
-    int32_t endLineOffset;
-    block_update_t(int32_t newLine_, int32_t oldLine_, int32_t endLineOffset_) :
-        newLine(newLine_), oldLine(oldLine_), endLineOffset(endLineOffset_)
-    {}
-};
-typedef std::unordered_map<unsigned /*source fullPathIndex*/, std::vector<block_update_t>> src_block_updates_t;
-
-struct file_block_update_t
-{
-    unsigned fullPathIndex;
-    int32_t newLine;
-    int32_t oldLine;
-    int32_t endLineOffset;
-    file_block_update_t(unsigned fullPathIndex_, int32_t newLine_, int32_t oldLine_, int32_t endLineOffset_) :
-        fullPathIndex(fullPathIndex_), newLine(newLine_), oldLine(oldLine_), endLineOffset(endLineOffset_)
-    {}
-};
-typedef std::unordered_map<mdMethodDef, std::vector<file_block_update_t>> method_block_updates_t;
-
 class Modules
 {
+public:
+
     struct ModuleInfo
     {
         std::vector<PVOID> m_symbolReaderHandles;
@@ -153,46 +57,6 @@ class Modules
         ~ModuleInfo() noexcept;
     };
 
-    std::mutex m_modulesInfoMutex;
-    std::unordered_map<CORDB_ADDRESS, ModuleInfo> m_modulesInfo;
-    // Must care about topological sort during ClearCache() and UpdateApplication() methods calls at Hot Reload.
-    std::list<ToRelease<ICorDebugType>> m_modulesUpdateHandlerTypes;
-
-    struct FileMethodsData
-    {
-        CORDB_ADDRESS modAddress = 0;
-        // properly ordered on each nested level arrays of methods data
-        std::vector<std::vector<method_data_t>> methodsData;
-        // mapping method's data to array of tokens, that also represent same code
-        // aimed to resolve all methods token for constructor's segment, since it could be part of multiple constructors
-        std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> multiMethodsData;
-    };
-
-    // Note, breakpoints setup and ran debuggee's process could be in the same time.
-    std::mutex m_sourcesInfoMutex;
-    // Note, we only add to m_sourceIndexToPath/m_sourcePathToIndex/m_sourceIndexToInitialFullPath, "size()" used as index in map at new element add.
-    // m_sourceIndexToPath - mapping index to full path
-    std::vector<std::string> m_sourceIndexToPath;
-    // m_sourcePathToIndex - mapping full path to index
-    std::unordered_map<std::string, unsigned> m_sourcePathToIndex;
-    // m_sourceNameToFullPathsIndexes - mapping file name to set of paths with this file name
-    std::unordered_map<std::string, std::set<unsigned>> m_sourceNameToFullPathsIndexes;
-    // m_sourcesMethodsData - all methods data indexed by full path, second vector hold data with same full path for different modules,
-    //                        since we may have modules with same source full path
-    std::vector<std::vector<FileMethodsData>> m_sourcesMethodsData;
-
-    HRESULT GetFullPathIndex(BSTR document, unsigned &fullPathIndex);
-    HRESULT FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle);
-    HRESULT UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, std::unordered_set<mdMethodDef> methodTokens,
-                                            src_block_updates_t &blockUpdates, PVOID pSymbolReaderHandle, method_block_updates_t &methodBlockUpdates);
-    HRESULT ResolveRelativeSourceFileName(std::string &filename);
-
-#ifdef WIN32
-    // on Windows OS, all files names converted to uppercase in containers above, but this vector hold initial full path names
-    std::vector<std::string> m_sourceIndexToInitialFullPath;
-#endif
-
-public:
     struct SequencePoint {
         int32_t startLine;
         int32_t startColumn;
@@ -202,29 +66,12 @@ public:
         std::string document;
     };
 
-    struct resolved_bp_t
-    {
-        int32_t startLine;
-        int32_t endLine;
-        uint32_t ilOffset;
-        uint32_t methodToken;
-        ToRelease<ICorDebugModule> iCorModule;
-
-        resolved_bp_t(int32_t startLine_, int32_t endLine_, uint32_t ilOffset_, uint32_t methodToken_, ICorDebugModule *pModule) :
-            startLine(startLine_),
-            endLine(endLine_),
-            ilOffset(ilOffset_),
-            methodToken(methodToken_),
-            iCorModule(pModule)
-        {}
-    };
-
     HRESULT ResolveBreakpoint(
         /*in*/ CORDB_ADDRESS modAddress,
         /*in*/ std::string filename,
         /*out*/ unsigned &fullname_index,
         /*in*/ int sourceLine,
-        /*out*/ std::vector<resolved_bp_t> &resolvedPoints);
+        /*out*/ std::vector<ModulesSources::resolved_bp_t> &resolvedPoints);
 
     HRESULT GetSourceFullPathByIndex(unsigned index, std::string &fullPath);
     HRESULT GetIndexBySourceFullPath(std::string fullPath, unsigned &index);
@@ -235,6 +82,10 @@ public:
 
     void CopyModulesUpdateHandlerTypes(std::vector<ToRelease<ICorDebugType>> &modulesUpdateHandlerTypes);
 
+    typedef std::function<HRESULT(ModuleInfo &)> ModuleInfoCallback;
+    HRESULT GetModuleInfo(CORDB_ADDRESS modAddress, ModuleInfoCallback cb);
+    HRESULT GetModuleInfo(CORDB_ADDRESS modAddress, ModuleInfo **ppmdInfo);
+
     HRESULT GetFrameILAndSequencePoint(
         ICorDebugFrame *pFrame,
         ULONG32 &ilOffset,
@@ -305,28 +156,18 @@ public:
 
     HRESULT ForEachModule(std::function<HRESULT(ICorDebugModule *pModule)> cb);
 
-    void FindFileNames(string_view pattern, unsigned limit, std::function<void(const char *)> cb);
-    void FindFunctions(string_view pattern, unsigned limit, std::function<void(const char *)> cb);
+    void FindFileNames(Utility::string_view pattern, unsigned limit, std::function<void(const char *)> cb);
+    void FindFunctions(Utility::string_view pattern, unsigned limit, std::function<void(const char *)> cb);
     HRESULT GetSource(ICorDebugModule *pModule, const std::string &sourcePath, char** fileBuf, int* fileLen);
 
-    struct AwaitInfo
-    {
-        uint32_t yield_offset;
-        uint32_t resume_offset;
-
-        AwaitInfo() :
-            yield_offset(0), resume_offset(0)
-        {};
-        AwaitInfo(uint32_t offset1, uint32_t offset2) :
-            yield_offset(offset1), resume_offset(offset2)
-        {};
-    };
+private:
 
-    bool IsMethodHaveAwait(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion);
-    bool FindNextAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 ipOffset, AwaitInfo **awaitInfo);
-    bool FindLastIlOffsetAwaitInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion, ULONG32 &lastIlOffset);
+    std::mutex m_modulesInfoMutex;
+    std::unordered_map<CORDB_ADDRESS, ModuleInfo> m_modulesInfo;
+    ModulesAppUpdate m_modulesAppUpdate;
 
-private:
+    // Note, m_modulesSources have its own mutex for private data state sync.
+    ModulesSources m_modulesSources;
 
     HRESULT GetSequencePointByILOffset(
         PVOID pSymbolReaderHandle,
@@ -334,25 +175,6 @@ private:
         ULONG32 ilOffset,
         SequencePoint *sequencePoint);
 
-    struct AsyncMethodInfo
-    {
-        CORDB_ADDRESS modAddress;
-        mdMethodDef methodToken;
-        ULONG32 methodVersion;
-
-        std::vector<AwaitInfo> awaits;
-        // Part of NotifyDebuggerOfWaitCompletion magic, see ManagedDebugger::SetupAsyncStep().
-        ULONG32 lastIlOffset;
-
-        AsyncMethodInfo() :
-            modAddress(0), methodToken(mdMethodDefNil), methodVersion(0), awaits(), lastIlOffset(0)
-        {};
-    };
-
-    AsyncMethodInfo asyncMethodSteppingInfo;
-    std::mutex m_asyncMethodSteppingInfoMutex;
-    // Note, result stored into asyncMethodSteppingInfo.
-    HRESULT GetAsyncMethodSteppingInfo(CORDB_ADDRESS modAddress, mdMethodDef methodToken, ULONG32 methodVersion);
 };
 
 } // namespace netcoredbg
diff --git a/src/metadata/modules_app_update.cpp b/src/metadata/modules_app_update.cpp
new file mode 100644 (file)
index 0000000..4a8528f
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "metadata/modules_app_update.h"
+
+#include <string>
+#include "metadata/typeprinter.h"
+#include "utils/utf.h"
+
+namespace netcoredbg
+{
+
+// Get data from 'MetadataUpdateHandler' attribute.
+// https://docs.microsoft.com/en-us/dotnet/api/system.reflection.metadata.metadataupdatehandlerattribute?view=net-6.0
+// Note, provided by attribute type have same format as Type.GetType(String) argument
+// https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype?view=net-6.0#system-type-gettype
+static HRESULT GetUpdateHandlerTypesForModule(IMetaDataImport *pMD, std::vector<std::string> &updateHandlerTypes)
+{
+    static const std::string metadataUpdateHandlerAttribute = "System.Reflection.Metadata.MetadataUpdateHandlerAttribute..ctor";
+
+    ULONG numAttributes = 0;
+    HCORENUM fEnum = NULL;
+    mdCustomAttribute attr;
+    while(SUCCEEDED(pMD->EnumCustomAttributes(&fEnum, 0, 0, &attr, 1, &numAttributes)) && numAttributes != 0)
+    {
+        std::string mdName;
+        mdToken tkObj = mdTokenNil;
+        mdToken tkType = mdTokenNil;
+        void const *pBlob = 0;
+        ULONG cbSize = 0;
+        if (FAILED(pMD->GetCustomAttributeProps(attr, &tkObj, &tkType, &pBlob, &cbSize)) ||
+            FAILED(TypePrinter::NameForToken(tkType, pMD, mdName, true, nullptr)))
+            continue;
+
+        if (mdName != metadataUpdateHandlerAttribute)
+            continue;
+
+        const unsigned char *pCBlob = (const unsigned char *)pBlob;
+        ULONG elementSize;
+
+        // 2 bytes - blob prolog 0x0001
+        pCBlob += 2;
+
+        ULONG stringSize;
+        elementSize = CorSigUncompressData(pCBlob, &stringSize);
+        pCBlob += elementSize;
+        updateHandlerTypes.emplace_back(pCBlob, pCBlob + stringSize);
+    }
+    pMD->CloseEnum(fEnum);
+
+    return S_OK;
+}
+
+// Parse type name. More info:
+// https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/specifying-fully-qualified-type-names
+// Note, in MetadataUpdateHandler attribute case, type name will not have assembly relative parts.
+// Backtick (`)      Precedes one or more digits representing the number of type parameters, located at the end of the name of a generic type.
+// Brackets ([])     Enclose a generic type argument list, for a constructed generic type; within a type argument list, enclose an assembly-qualified type.
+// Comma (,)         Precedes the Assembly name.
+// Period (.)        Denotes namespace identifiers.
+// Plus sign (+)     Precedes a nested class.
+static void ParceTypeName(const std::string &fullName, std::string &mainTypeName, std::vector<std::string> &nestedClasses)
+{
+    std::string::size_type genericDelimiterPos = fullName.find("`");
+    std::string fullTypeName;
+    std::string genericTypes;
+    if (genericDelimiterPos == std::string::npos)
+    {
+        fullTypeName = fullName;
+    }
+    else
+    {
+        fullTypeName = fullName.substr(0, genericDelimiterPos);
+        genericTypes = fullName.substr(genericDelimiterPos);
+    }
+
+    // TODO implement generic support (genericTypes) for update handler types
+
+    std::string::size_type nestedClassDelimiterPos = fullTypeName.find("+");
+    if (nestedClassDelimiterPos == std::string::npos)
+    {
+        mainTypeName = fullTypeName;
+    }
+    else
+    {
+        mainTypeName = fullTypeName.substr(0, nestedClassDelimiterPos);
+        while (nestedClassDelimiterPos != std::string::npos)
+        {
+            std::string::size_type startPos = nestedClassDelimiterPos + 1;
+            nestedClassDelimiterPos = fullTypeName.find("+", startPos);
+            if (nestedClassDelimiterPos == std::string::npos)
+                nestedClasses.emplace_back(fullName.substr(startPos, fullTypeName.size() - startPos));
+            else
+                nestedClasses.emplace_back(fullName.substr(startPos, nestedClassDelimiterPos - startPos));
+        }
+    }
+}
+
+// Scan module for 'MetadataUpdateHandler' attributes and store related to UpdateHandlerTypes ICorDebugType objects.
+// Note, 'MetadataUpdateHandler' attributes can't be changed/removed/added at Hot Reload.
+HRESULT ModulesAppUpdate::AddUpdateHandlerTypesForModule(ICorDebugModule *pModule, IMetaDataImport *pMD)
+{
+    HRESULT Status;
+    std::vector<std::string> updateHandlerTypeNames;
+    IfFailRet(GetUpdateHandlerTypesForModule(pMD, updateHandlerTypeNames));
+
+    for (const auto &entry : updateHandlerTypeNames)
+    {
+        std::string mainTypeName;
+        std::vector<std::string> nestedClasses;
+        ParceTypeName(entry, mainTypeName, nestedClasses);
+
+        // Resolve main type part.
+        mdTypeDef typeToken = mdTypeDefNil;
+        IfFailRet(pMD->FindTypeDefByName(reinterpret_cast<LPCWSTR>(to_utf16(mainTypeName).c_str()), mdTypeDefNil, &typeToken));
+        if (typeToken == mdTypeDefNil)
+            return E_FAIL;
+
+        // Resolve nested type part.
+        for (const auto &nestedClassName : nestedClasses)
+        {
+            mdTypeDef classToken = mdTypeDefNil;
+            IfFailRet(pMD->FindTypeDefByName(reinterpret_cast<LPCWSTR>(to_utf16(nestedClassName).c_str()), typeToken, &classToken));
+            if (classToken == mdTypeDefNil)
+                return E_FAIL;
+            typeToken = classToken;
+        }
+
+        ToRelease<ICorDebugClass> pClass;
+        IfFailRet(pModule->GetClassFromToken(typeToken, &pClass));
+        ToRelease<ICorDebugClass2> pClass2;
+        IfFailRet(pClass->QueryInterface(IID_ICorDebugClass2, (LPVOID*) &pClass2));
+        m_modulesUpdateHandlerTypes.emplace_back();
+        IfFailRet(pClass2->GetParameterizedType(ELEMENT_TYPE_CLASS, 0, nullptr, &(m_modulesUpdateHandlerTypes.back())));
+    }
+
+    return S_OK;
+}
+
+void ModulesAppUpdate::CopyModulesUpdateHandlerTypes(std::vector<ToRelease<ICorDebugType>> &modulesUpdateHandlerTypes)
+{
+    modulesUpdateHandlerTypes.reserve(m_modulesUpdateHandlerTypes.size());
+    for (ToRelease<ICorDebugType> &updateHandlerType : m_modulesUpdateHandlerTypes)
+    {
+        updateHandlerType->AddRef();
+        modulesUpdateHandlerTypes.emplace_back(updateHandlerType.GetPtr());
+    }
+}
+
+} // namespace netcoredbg
diff --git a/src/metadata/modules_app_update.h b/src/metadata/modules_app_update.h
new file mode 100644 (file)
index 0000000..456f60e
--- /dev/null
@@ -0,0 +1,36 @@
+// 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"
+
+#include <list>
+#include <vector>
+#include "utils/torelease.h"
+
+namespace netcoredbg
+{
+
+class ModulesAppUpdate
+{
+public:
+
+    HRESULT AddUpdateHandlerTypesForModule(ICorDebugModule *pModule, IMetaDataImport *pMD);
+    void CopyModulesUpdateHandlerTypes(std::vector<ToRelease<ICorDebugType>> &modulesUpdateHandlerTypes);
+
+    void Clear()
+    {
+        m_modulesUpdateHandlerTypes.clear();
+    }
+
+private:
+
+    // Must care about topological sort during ClearCache() and UpdateApplication() methods calls at Hot Reload.
+    std::list<ToRelease<ICorDebugType>> m_modulesUpdateHandlerTypes;
+
+};
+
+} // namespace netcoredbg
diff --git a/src/metadata/modules_sources.cpp b/src/metadata/modules_sources.cpp
new file mode 100644 (file)
index 0000000..4ef8ae6
--- /dev/null
@@ -0,0 +1,960 @@
+// 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 <map>
+#include <memory>
+#include <algorithm>
+#include <fstream>
+
+#include "metadata/modules_sources.h"
+#include "metadata/modules.h"
+#include "metadata/jmc.h"
+#include "managed/interop.h"
+#include "utils/utf.h"
+
+namespace netcoredbg
+{
+
+namespace
+{
+
+    struct file_methods_data_t
+    {
+        BSTR document;
+        int32_t methodNum;
+        method_data_t *methodsData;
+
+        file_methods_data_t() = delete;
+        file_methods_data_t(const file_methods_data_t&) = delete;
+        file_methods_data_t& operator=(const file_methods_data_t&) = delete;
+        file_methods_data_t(file_methods_data_t&&) = delete;
+        file_methods_data_t& operator=(file_methods_data_t&&) = delete;
+    };
+
+    struct module_methods_data_t
+    {
+        int32_t fileNum;
+        file_methods_data_t *moduleMethodsData;
+
+        module_methods_data_t() = delete;
+        module_methods_data_t(const module_methods_data_t&) = delete;
+        module_methods_data_t& operator=(const module_methods_data_t&) = delete;
+        module_methods_data_t(module_methods_data_t&&) = delete;
+        module_methods_data_t& operator=(module_methods_data_t&&) = delete;
+    };
+
+    struct module_methods_data_t_deleter
+    {
+        void operator()(module_methods_data_t *p) const
+        {
+            if (p->moduleMethodsData)
+            {
+                for (int32_t i = 0; i < p->fileNum; i++)
+                {
+                    if (p->moduleMethodsData[i].document)
+                        Interop::SysFreeString(p->moduleMethodsData[i].document);
+                    if (p->moduleMethodsData[i].methodsData)
+                        Interop::CoTaskMemFree(p->moduleMethodsData[i].methodsData);
+                }
+                Interop::CoTaskMemFree(p->moduleMethodsData);
+            }
+            Interop::CoTaskMemFree(p);
+        }
+    };
+
+    // Note, we use std::map since we need container that will not invalidate iterators on add new elements.
+    void AddMethodData(/*in,out*/ std::map<size_t, std::set<method_data_t>> &methodData,
+                       /*in,out*/ std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> &multiMethodBpData,
+                       const method_data_t &entry,
+                       const size_t nestedLevel)
+    {
+        // if we here, we need at least one nested level for sure
+        if (methodData.empty())
+        {
+            methodData.emplace(std::make_pair(0, std::set<method_data_t>{entry}));
+            return;
+        }
+        assert(nestedLevel <= methodData.size()); // could be increased only at 1 per recursive call
+        if (nestedLevel == methodData.size())
+        {
+            methodData.emplace(std::make_pair(nestedLevel, std::set<method_data_t>{entry}));
+            return;
+        }
+
+        // same data that was already added, but with different method token (constructors case)
+        auto find = methodData[nestedLevel].find(entry);
+        if (find != methodData[nestedLevel].end())
+        {
+            method_data_t key(find->methodDef, entry.startLine, entry.endLine, entry.startColumn, entry.endColumn);
+            auto find_multi = multiMethodBpData.find(key);
+            if (find_multi == multiMethodBpData.end())
+                multiMethodBpData.emplace(std::make_pair(key, std::vector<mdMethodDef>{entry.methodDef}));
+            else
+                find_multi->second.emplace_back(entry.methodDef);
+
+            return;
+        }
+
+        auto it = methodData[nestedLevel].lower_bound(entry);
+        if (it != methodData[nestedLevel].end() && entry.NestedInto(*it))
+        {
+            AddMethodData(methodData, multiMethodBpData, entry, nestedLevel + 1);
+            return;
+        }
+
+        // case with only one element on nested level, NestedInto() was already called and entry checked
+        if (it == methodData[nestedLevel].begin())
+        {
+            methodData[nestedLevel].emplace(entry);
+            return;
+        }
+
+        // move all previously added nested for new entry elements to level above
+        do
+        {
+            it = std::prev(it);
+
+            if ((*it).NestedInto(entry))
+            {
+                method_data_t tmp = *it;
+                it = methodData[nestedLevel].erase(it);
+                AddMethodData(methodData, multiMethodBpData, tmp, nestedLevel + 1);
+            }
+            else
+                break;
+        }
+        while(it != methodData[nestedLevel].begin());
+
+        methodData[nestedLevel].emplace(entry);
+    }
+
+
+    bool GetMethodTokensByLineNumber(const std::vector<std::vector<method_data_t>> &methodBpData,
+                                     const std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> &multiMethodBpData,
+                                     /*in,out*/ int32_t &lineNum,
+                                     /*out*/ std::vector<mdMethodDef> &Tokens,
+                                     /*out*/ mdMethodDef &closestNestedToken)
+    {
+        const method_data_t *result = nullptr;
+        closestNestedToken = 0;
+
+        for (auto it = methodBpData.cbegin(); it != methodBpData.cend(); ++it)
+        {
+            auto lower = std::lower_bound((*it).cbegin(), (*it).cend(), lineNum);
+            if (lower == (*it).cend())
+                break; // point behind last method for this nested level
+
+            // case with first line of method, for example:
+            // void Method(){ 
+            //            void Method(){ void Method(){...  <- breakpoint at this line
+            if (lineNum == (*lower).startLine)
+            {
+                // At this point we can't check this case, let managed part decide (since it see Columns):
+                // void Method() {
+                // ... code ...; void Method() {     <- breakpoint at this line
+                //  };
+                if (result)
+                    closestNestedToken = (*lower).methodDef;
+                else
+                    result = &(*lower);
+
+                break;
+            }
+            else if (lineNum > (*lower).startLine && (*lower).endLine >= lineNum)
+            {
+                result = &(*lower);
+                continue; // need check nested level (if available)
+            }
+            // out of first level methods lines - forced move line to first method below, for example:
+            //  <-- breakpoint at line without code (out of any methods)
+            // void Method() {...}
+            else if (it == methodBpData.cbegin() && lineNum < (*lower).startLine)
+            {
+                lineNum = (*lower).startLine;
+                result = &(*lower);
+                break;
+            }
+            // result was found on previous cycle, check for closest nested method
+            // need it in case of breakpoint setuped at lines without code and before nested method, for example:
+            // {
+            //  <-- breakpoint at line without code (inside method)
+            //     void Method() {...}
+            // }
+            else if (result && lineNum <= (*lower).startLine && (*lower).endLine <= result->endLine)
+            {
+                closestNestedToken = (*lower).methodDef;
+                break;
+            }
+            else
+                break;
+        }
+
+        if (result)
+        {
+            auto find = multiMethodBpData.find(*result);
+            if (find != multiMethodBpData.end()) // only constructors segments could be part of multiple methods
+            {
+                Tokens.resize((*find).second.size());
+                std::copy(find->second.begin(), find->second.end(), Tokens.begin());
+            }
+            Tokens.emplace_back(result->methodDef);
+        }
+        
+        return !!result;
+    }
+
+} // unnamed namespace
+
+static HRESULT GetPdbMethodsRanges(IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle, std::unordered_set<mdMethodDef> *methodTokens,
+                                   std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> &inputData)
+{
+    HRESULT Status;
+    // Note, we need 2 arrays of tokens - for normal methods and constructors (.ctor/.cctor, that could have segmented code).
+    std::vector<int32_t> constrTokens;
+    std::vector<int32_t> normalTokens;
+
+    ULONG numTypedefs = 0;
+    HCORENUM hEnum = NULL;
+    mdTypeDef typeDef;
+    while(SUCCEEDED(pMDImport->EnumTypeDefs(&hEnum, &typeDef, 1, &numTypedefs)) && numTypedefs != 0)
+    {
+        ULONG numMethods = 0;
+        HCORENUM fEnum = NULL;
+        mdMethodDef methodDef;
+        while(SUCCEEDED(pMDImport->EnumMethods(&fEnum, typeDef, &methodDef, 1, &numMethods)) && numMethods != 0)
+        {
+            if (methodTokens && methodTokens->find(methodDef) == methodTokens->end())
+                continue;
+
+            WCHAR funcName[mdNameLen];
+            ULONG funcNameLen;
+            if (FAILED(pMDImport->GetMethodProps(methodDef, nullptr, funcName, _countof(funcName), &funcNameLen,
+                                                 nullptr, nullptr, nullptr, nullptr, nullptr)))
+            {
+                continue;
+            }
+
+            if (str_equal(funcName, W(".ctor")) || str_equal(funcName, W(".cctor")))
+                constrTokens.emplace_back(methodDef);
+            else
+                normalTokens.emplace_back(methodDef);
+        }
+        pMDImport->CloseEnum(fEnum);
+    }
+    pMDImport->CloseEnum(hEnum);
+
+    if (sizeof(std::size_t) > sizeof(std::uint32_t) &&
+        (constrTokens.size() > std::numeric_limits<uint32_t>::max() || normalTokens.size() > std::numeric_limits<uint32_t>::max()))
+    {
+        LOGE("Too big token arrays.");
+        return E_FAIL;
+    }
+
+    PVOID data = nullptr;
+    IfFailRet(Interop::GetModuleMethodsRanges(pSymbolReaderHandle, (uint32_t)constrTokens.size(), constrTokens.data(), (uint32_t)normalTokens.size(), normalTokens.data(), &data));
+    if (data == nullptr)
+        return S_OK;
+
+    inputData.reset((module_methods_data_t*)data);
+    return S_OK;
+}
+
+static std::string GetFileName(const std::string &path)
+{
+    std::size_t i = path.find_last_of("/\\");
+    return i == std::string::npos ? path : path.substr(i + 1);
+}
+
+// Caller must care about m_sourcesInfoMutex.
+HRESULT ModulesSources::GetFullPathIndex(BSTR document, unsigned &fullPathIndex)
+{
+    std::string fullPath = to_utf8(document);
+#ifdef WIN32
+    HRESULT Status;
+    std::string initialFullPath = fullPath;
+    IfFailRet(Interop::StringToUpper(fullPath));
+#endif
+    auto findPathIndex = m_sourcePathToIndex.find(fullPath);
+    if (findPathIndex == m_sourcePathToIndex.end())
+    {
+        fullPathIndex = (unsigned)m_sourceIndexToPath.size();
+        m_sourcePathToIndex.emplace(std::make_pair(fullPath, fullPathIndex));
+        m_sourceIndexToPath.emplace_back(fullPath);
+#ifdef WIN32
+        m_sourceIndexToInitialFullPath.emplace_back(initialFullPath);
+#endif
+        m_sourceNameToFullPathsIndexes[GetFileName(fullPath)].emplace(fullPathIndex);
+        m_sourcesMethodsData.emplace_back(std::vector<FileMethodsData>{});
+    }
+    else
+        fullPathIndex = findPathIndex->second;
+
+    return S_OK;
+}
+
+HRESULT ModulesSources::FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle)
+{
+    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+
+    HRESULT Status;
+    std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> inputData;
+    IfFailRet(GetPdbMethodsRanges(pMDImport, pSymbolReaderHandle, nullptr, inputData));
+    if (inputData == nullptr)
+        return S_OK;
+
+    // Usually, modules provide files with unique full paths for sources.
+    m_sourceIndexToPath.reserve(m_sourceIndexToPath.size() + inputData->fileNum);
+    m_sourcesMethodsData.reserve(m_sourcesMethodsData.size() + inputData->fileNum);
+#ifdef WIN32
+    m_sourceIndexToInitialFullPath.reserve(m_sourceIndexToInitialFullPath.size() + inputData->fileNum);
+#endif
+
+    CORDB_ADDRESS modAddress;
+    IfFailRet(pModule->GetBaseAddress(&modAddress));
+
+    for (int i = 0; i < inputData->fileNum; i++)
+    {
+        unsigned fullPathIndex;
+        IfFailRet(GetFullPathIndex(inputData->moduleMethodsData[i].document, fullPathIndex));
+
+        m_sourcesMethodsData[fullPathIndex].emplace_back(FileMethodsData{});
+        auto &fileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
+        fileMethodsData.modAddress = modAddress;
+
+        // Note, don't reorder input data, since it have almost ideal order for us.
+        // For example, for Private.CoreLib (about 22000 methods) only 8 relocations were made.
+        // In case default methods ordering will be dramatically changed, we could use data reordering,
+        // for example based on this solution:
+        //    struct compare {
+        //        bool operator()(const method_data_t &lhs, const method_data_t &rhs) const
+        //        { return lhs.endLine > rhs.endLine || (lhs.endLine == rhs.endLine && lhs.endColumn > rhs.endColumn); }
+        //    };
+        //    std::multiset<method_data_t, compare> orderedInputData;
+        std::map<size_t, std::set<method_data_t>> inputMethodsData;
+        for (int j = 0; j < inputData->moduleMethodsData[i].methodNum; j++)
+        {
+            AddMethodData(inputMethodsData, fileMethodsData.multiMethodsData, inputData->moduleMethodsData[i].methodsData[j], 0);
+        }
+
+        fileMethodsData.methodsData.resize(inputMethodsData.size());
+        for (size_t i =  0; i < inputMethodsData.size(); i++)
+        {
+            fileMethodsData.methodsData[i].resize(inputMethodsData[i].size());
+            std::copy(inputMethodsData[i].begin(), inputMethodsData[i].end(), fileMethodsData.methodsData[i].begin());
+        }
+        for (auto &data : fileMethodsData.multiMethodsData)
+        {
+            data.second.shrink_to_fit();
+        }
+    }
+
+    m_sourcesMethodsData.shrink_to_fit();
+    m_sourceIndexToPath.shrink_to_fit();
+#ifdef WIN32
+    m_sourceIndexToInitialFullPath.shrink_to_fit();
+#endif
+
+    return S_OK;
+}
+
+static HRESULT LineUpdatesForMethodData(unsigned fullPathIndex, method_data_t &methodData, const std::vector<block_update_t> &blockUpdate, method_block_updates_t &methodBlockUpdates)
+{
+    for (const auto &block : blockUpdate)
+    {
+        if (block.oldLine < 0 || block.endLineOffset < 0 || methodData.endLine < 0)
+            return E_INVALIDARG;
+
+        // Note, endLineOffset could be std::numeric_limits<int32_t>::max() (max line number in C# source), so, we forced to cast it first.
+        // Also, this is why we test that method within range and after that negate result.
+        if (!(methodData.startLine >= block.oldLine &&
+              (uint32_t)methodData.endLine <= (uint32_t)block.oldLine + (uint32_t)block.endLineOffset))
+            continue;
+
+        const int32_t codeLineOffset = block.newLine - block.oldLine;
+
+        auto updateCashe = [&]() -> bool
+        {
+            auto findMethod = methodBlockUpdates.find(methodData.methodDef);
+            if (findMethod == methodBlockUpdates.end())
+                return false;
+
+            for (auto &entry : findMethod->second)
+            {
+                if (fullPathIndex == entry.fullPathIndex &&
+                    methodData.startLine == entry.newLine &&
+                    methodData.endLine == entry.newLine + entry.endLineOffset)
+                {
+                    // All we need for previous stored data is change newLine, since oldLine will be the same (PDB for this method version was not changed).
+                    entry.newLine += codeLineOffset;
+                    return true;
+                }
+            }
+            return false;
+        };
+        if (!updateCashe())
+            methodBlockUpdates[methodData.methodDef].emplace_back(fullPathIndex, methodData.startLine + codeLineOffset, methodData.startLine, methodData.endLine - methodData.startLine);
+
+        methodData.startLine += codeLineOffset;
+        methodData.endLine += codeLineOffset;
+
+        break;
+    }
+
+    return S_OK;
+}
+
+HRESULT ModulesSources::UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, std::unordered_set<mdMethodDef> methodTokens,
+                                                        src_block_updates_t &srcBlockUpdates, PVOID pSymbolReaderHandle, method_block_updates_t &methodBlockUpdates)
+{
+    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+
+    HRESULT Status;
+    std::unique_ptr<module_methods_data_t, module_methods_data_t_deleter> inputData;
+    IfFailRet(GetPdbMethodsRanges(pMDImport, pSymbolReaderHandle, &methodTokens, inputData));
+
+    struct src_update_data_t
+    {
+        std::vector<block_update_t> blockUpdate;
+        int32_t methodNum = 0;
+        method_data_t *methodsData = nullptr;
+    };
+    std::unordered_map<unsigned, src_update_data_t> srcUpdateData;
+
+    if (inputData)
+    {
+        for (int i = 0; i < inputData->fileNum; i++)
+        {
+            unsigned fullPathIndex;
+            IfFailRet(GetFullPathIndex(inputData->moduleMethodsData[i].document, fullPathIndex));
+
+            srcUpdateData[fullPathIndex].methodNum = inputData->moduleMethodsData[i].methodNum;
+            srcUpdateData[fullPathIndex].methodsData = inputData->moduleMethodsData[i].methodsData;
+        }
+    }
+    for (const auto &entry : srcBlockUpdates)
+    {
+        srcUpdateData[entry.first].blockUpdate = entry.second;
+    }
+
+    if (srcUpdateData.empty())
+        return S_OK;
+
+    CORDB_ADDRESS modAddress;
+    IfFailRet(pModule->GetBaseAddress(&modAddress));
+
+    for (const auto &updateData : srcUpdateData)
+    {
+        const unsigned fullPathIndex = updateData.first;
+
+        std::map<size_t, std::set<method_data_t>> inputMethodsData;
+        if (m_sourcesMethodsData[fullPathIndex].empty())
+        { // New source file added.
+            m_sourcesMethodsData[fullPathIndex].emplace_back(FileMethodsData{});
+            auto &tmpFileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
+            tmpFileMethodsData.modAddress = modAddress;
+
+            for (int j = 0; j < updateData.second.methodNum; j++)
+            {
+                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
+            }
+        }
+        else
+        {
+            std::unordered_set<mdMethodDef> inputMetodDefSet;
+            for (int j = 0; j < updateData.second.methodNum; j++)
+            {
+                inputMetodDefSet.insert(updateData.second.methodsData[j].methodDef);
+                // All sequence points related to this method were updated and provide proper lines from PDB directly.
+                methodBlockUpdates.erase(updateData.second.methodsData[j].methodDef);
+            }
+
+            // Move multiMethodsData first (since this is constructors and all will be on level 0 for sure).
+            // Use std::unordered_set here instead array for fast search.
+            auto &tmpFileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
+            std::vector<method_data_t> tmpMultiMethodsData;
+            for (const auto &entryData : tmpFileMethodsData.multiMethodsData)
+            {
+                auto findData = inputMetodDefSet.find(entryData.first.methodDef);
+                if (findData == inputMetodDefSet.end())
+                    tmpMultiMethodsData.emplace_back(entryData.first);
+
+                for (const auto &entryMethodDef : entryData.second)
+                {
+                    findData = inputMetodDefSet.find(entryMethodDef);
+                    if (findData == inputMetodDefSet.end())
+                        tmpMultiMethodsData.emplace_back(entryMethodDef, entryData.first.startLine, entryData.first.endLine,
+                                                         entryData.first.startColumn, entryData.first.endColumn);
+                }
+            }
+
+            tmpFileMethodsData.multiMethodsData.clear();
+            for (auto &methodData : tmpMultiMethodsData)
+            {
+                IfFailRet(LineUpdatesForMethodData(fullPathIndex, methodData, updateData.second.blockUpdate, methodBlockUpdates));
+                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, methodData, 0);
+            }
+
+            // Move normal methods.
+            for (auto &methodsData : tmpFileMethodsData.methodsData)
+            {
+                for (auto &methodData : methodsData)
+                {
+                    auto findData = inputMetodDefSet.find(methodData.methodDef);
+                    if (findData == inputMetodDefSet.end())
+                    {
+                        IfFailRet(LineUpdatesForMethodData(fullPathIndex, methodData, updateData.second.blockUpdate, methodBlockUpdates));
+                        AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, methodData, 0);
+                    }
+                }
+            }
+            tmpFileMethodsData.methodsData.clear();
+
+            // Move new and modified methods.
+            for (int j = 0; j < updateData.second.methodNum; j++)
+            {
+                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
+            }
+        }
+
+        auto &fileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
+        fileMethodsData.methodsData.resize(inputMethodsData.size());
+        for (size_t i =  0; i < inputMethodsData.size(); i++)
+        {
+            fileMethodsData.methodsData[i].resize(inputMethodsData[i].size());
+            std::copy(inputMethodsData[i].begin(), inputMethodsData[i].end(), fileMethodsData.methodsData[i].begin());
+        }
+        for (auto &data : fileMethodsData.multiMethodsData)
+        {
+            data.second.shrink_to_fit();
+        }
+    }
+
+    return S_OK;
+}
+
+HRESULT ModulesSources::ResolveRelativeSourceFileName(std::string &filename)
+{
+    // IMPORTANT! Caller should care about m_sourcesInfoMutex.
+    auto findIndexesByFileName = m_sourceNameToFullPathsIndexes.find(GetFileName(filename));
+    if (findIndexesByFileName == m_sourceNameToFullPathsIndexes.end())
+        return E_FAIL;
+
+    auto const &possiblePathsIndexes = findIndexesByFileName->second;
+    std::string result = filename;
+
+    // Care about all "./" and "../" first.
+    std::list<std::string> pathDirs;
+    std::size_t i;
+    while ((i = result.find_first_of("/\\")) != std::string::npos)
+    {
+        std::string pathElement = result.substr(0, i);
+        if (pathElement == "..")
+        {
+            if (!pathDirs.empty())
+                pathDirs.pop_front();
+        }
+        else if (pathElement != ".")
+            pathDirs.push_front(pathElement);
+
+        result = result.substr(i + 1);
+    }
+    for (const auto &dir : pathDirs)
+    {
+        result = dir + '/' + result;
+    }
+
+    // The problem is - we could have several assemblies that could have same source file name with different path's root.
+    // We don't really have a lot of options here, so, we assume, that all possible sources paths have same root and just find the shortest.
+    if (result == GetFileName(result))
+    {
+        auto it = std::min_element(possiblePathsIndexes.begin(), possiblePathsIndexes.end(),
+                        [&](const unsigned a, const unsigned b){ return m_sourceIndexToPath[a].size() < m_sourceIndexToPath[b].size(); } );
+
+        filename = it == possiblePathsIndexes.end() ? result : m_sourceIndexToPath[*it];
+        return S_OK;
+    }
+
+    std::list<std::string> possibleResults;
+    for (const auto pathIndex : possiblePathsIndexes)
+    {
+        if (result.size() > m_sourceIndexToPath[pathIndex].size())
+            continue;
+
+        // Note, since assemblies could be built in different OSes, we could have different delimiters in source files paths.
+        auto BinaryPredicate = [](const char& a, const char& b)
+        {
+            if ((a == '/' || a == '\\') && (b == '/' || b == '\\')) return true;
+            return a == b;
+        };
+
+        // since C++17
+        //if (std::equal(result.begin(), result.end(), path.end() - result.size(), BinaryPredicate))
+        //    possibleResults.push_back(path);
+        auto first1 = result.begin();
+        auto last1 = result.end();
+        auto first2 = m_sourceIndexToPath[pathIndex].end() - result.size();
+        auto equal = [&]()
+        {
+            for (; first1 != last1; ++first1, ++first2)
+            {
+                if (!BinaryPredicate(*first1, *first2))
+                    return false;
+            }
+            return true;
+        };
+        if (equal())
+            possibleResults.push_back(m_sourceIndexToPath[pathIndex]);
+    }
+    // The problem is - we could have several assemblies that could have sources with same relative paths with different path's root.
+    // We don't really have a lot of options here, so, we assume, that all possible sources paths have same root and just find the shortest.
+    if (!possibleResults.empty())
+    {
+        filename = possibleResults.front();
+        for (const auto& path : possibleResults)
+        {
+            if (filename.length() > path.length())
+                filename = path;
+        }
+        return S_OK;
+    }
+
+    return E_FAIL;
+}
+
+static void LineUpdatesBackwardCorrection(unsigned fullPathIndex, mdMethodDef methodToken, method_block_updates_t &methodBlockUpdates, int32_t &startLine)
+{
+    auto findSourceUpdate = methodBlockUpdates.find(methodToken);
+    if (findSourceUpdate != methodBlockUpdates.end())
+    {
+        for (const auto &entry : findSourceUpdate->second)
+        {
+            if (entry.fullPathIndex != fullPathIndex ||
+                entry.newLine > startLine ||
+                entry.newLine + entry.endLineOffset < startLine)
+                continue;
+
+            startLine += entry.oldLine - entry.newLine;
+            break;
+        }
+    }
+}
+
+HRESULT ModulesSources::ResolveBreakpoint(/*in*/ Modules *pModules, /*in*/ CORDB_ADDRESS modAddress, /*in*/ std::string filename, /*out*/ unsigned &fullname_index,
+                                          /*in*/ int sourceLine, /*out*/ std::vector<resolved_bp_t> &resolvedPoints)
+{
+    std::lock_guard<std::mutex> lockSourcesInfo(m_sourcesInfoMutex);
+
+    HRESULT Status;
+    auto findIndex = m_sourcePathToIndex.find(filename);
+    if (findIndex == m_sourcePathToIndex.end())
+    {
+        // Check for absolute path.
+#ifdef WIN32
+        // Check, if start from drive letter, for example "D:\" or "D:/".
+        if (filename.size() > 2 && filename[1] == ':' && (filename[2] == '/' || filename[2] == '\\'))
+#else
+        if (filename[0] == '/')
+#endif
+        {
+            return E_FAIL;
+        }
+
+        IfFailRet(ResolveRelativeSourceFileName(filename));
+
+        findIndex = m_sourcePathToIndex.find(filename);
+        if (findIndex == m_sourcePathToIndex.end())
+            return E_FAIL;
+    }
+
+    fullname_index = findIndex->second;
+
+    struct resolved_input_bp_t
+    {
+        int32_t startLine;
+        int32_t endLine;
+        uint32_t ilOffset;
+        uint32_t methodToken;
+    };
+
+    struct resolved_input_bp_t_deleter
+    {
+        void operator()(resolved_input_bp_t *p) const
+        {
+            Interop::CoTaskMemFree(p);
+        }
+    };
+
+    for (const auto &sourceData : m_sourcesMethodsData[findIndex->second])
+    {
+        if (modAddress && modAddress != sourceData.modAddress)
+            continue;
+
+        std::vector<mdMethodDef> Tokens;
+        int32_t correctedStartLine = sourceLine;
+        mdMethodDef closestNestedToken = 0;
+        if (!GetMethodTokensByLineNumber(sourceData.methodsData, sourceData.multiMethodsData, correctedStartLine, Tokens, closestNestedToken))
+            continue;
+        // correctedStartLine - in case line not belong any methods, if possible, will be "moved" to first line of method below sourceLine.
+
+        if ((int32_t)Tokens.size() > std::numeric_limits<int32_t>::max())
+        {
+            LOGE("Too big token arrays.");
+            return E_FAIL;
+        }
+
+        Modules::ModuleInfo *pmdInfo; // Note, pmdInfo must be covered by m_modulesInfoMutex.
+        IfFailRet(pModules->GetModuleInfo(sourceData.modAddress, &pmdInfo)); // we must have it, since we loaded data from it
+        if (pmdInfo->m_symbolReaderHandles.empty())
+            continue;
+
+        // In case one source line (field/property initialization) compiled into all constructors, after Hot Reload, constructors may have different
+        // code version numbers, that mean debug info located in different symbol readers.
+        std::vector<PVOID> symbolReaderHandles;
+        symbolReaderHandles.reserve(Tokens.size());
+        for (auto methodToken : Tokens)
+        {
+            // Note, new breakpoints could be setup for last code version only, since protocols (MI, VSCode, ...) provide source:line data only.
+            ULONG32 currentVersion;
+            ToRelease<ICorDebugFunction> pFunction;
+            if (FAILED(pmdInfo->m_iCorModule->GetFunctionFromToken(methodToken, &pFunction)) ||
+                FAILED(pFunction->GetCurrentVersionNumber(&currentVersion)))
+            {
+                symbolReaderHandles.emplace_back(pmdInfo->m_symbolReaderHandles[0]);
+                continue;
+            }
+
+            assert(pmdInfo->m_symbolReaderHandles.size() >= currentVersion);
+            symbolReaderHandles.emplace_back(pmdInfo->m_symbolReaderHandles[currentVersion - 1]);
+        }
+
+        // In case Hot Reload we may have line updates that we must take into account.
+        LineUpdatesBackwardCorrection(findIndex->second, Tokens[0], pmdInfo->m_methodBlockUpdates, correctedStartLine);
+
+        PVOID data = nullptr;
+        int32_t Count = 0;
+#ifndef _WIN32
+        std::string fullName = m_sourceIndexToPath[findIndex->second];
+#else
+        std::string fullName = m_sourceIndexToInitialFullPath[findIndex->second];
+#endif
+        if (FAILED(Interop::ResolveBreakPoints(symbolReaderHandles.data(), (int32_t)Tokens.size(), Tokens.data(),
+                                               correctedStartLine, closestNestedToken, Count, fullName, &data))
+            || data == nullptr)
+        {
+            continue;
+        }
+        std::unique_ptr<resolved_input_bp_t, resolved_input_bp_t_deleter> inputData((resolved_input_bp_t*)data);
+
+        for (int32_t i = 0; i < Count; i++)
+        {
+            pmdInfo->m_iCorModule->AddRef();
+
+            // In case Hot Reload we may have line updates that we must take into account.
+            LineUpdatesForwardCorrection(findIndex->second, inputData.get()[i].methodToken, pmdInfo->m_methodBlockUpdates, inputData.get()[i]);
+
+            resolvedPoints.emplace_back(resolved_bp_t(inputData.get()[i].startLine, inputData.get()[i].endLine, inputData.get()[i].ilOffset,
+                                                      inputData.get()[i].methodToken, pmdInfo->m_iCorModule.GetPtr()));
+        }
+    }
+
+    return S_OK;
+}
+
+static HRESULT LoadLineUpdatesFile(ModulesSources *pModulesSources, const std::string &lineUpdates, src_block_updates_t &srcBlockUpdates)
+{
+    std::ifstream lineUpdatesFileStream(lineUpdates, std::ios::in | std::ios::binary);
+    if (!lineUpdatesFileStream.is_open())
+        return COR_E_FILENOTFOUND;
+
+    HRESULT Status;
+    uint32_t sourcesCount = 0;
+    lineUpdatesFileStream.read((char*)&sourcesCount, 4);
+    if (lineUpdatesFileStream.fail())
+        return E_FAIL;
+
+    struct line_update_t
+    {
+        int32_t newLine;
+        int32_t oldLine;
+    };
+    std::unordered_map<unsigned /*source fullPathIndex*/, std::vector<line_update_t>> lineUpdatesData;
+
+    for (uint32_t i = 0; i < sourcesCount; i++)
+    {
+        uint32_t stringSize = 0;
+        lineUpdatesFileStream.read((char*)&stringSize, 4);
+        if (lineUpdatesFileStream.fail())
+            return E_FAIL;
+
+        std::string fullPath;
+        fullPath.resize(stringSize);
+        lineUpdatesFileStream.read((char*)fullPath.data(), stringSize);
+        if (lineUpdatesFileStream.fail())
+            return E_FAIL;
+
+        unsigned fullPathIndex;
+        IfFailRet(pModulesSources->GetIndexBySourceFullPath(fullPath, fullPathIndex));
+
+        uint32_t updatesCount = 0;
+        lineUpdatesFileStream.read((char*)&updatesCount, 4);
+        if (lineUpdatesFileStream.fail())
+            return E_FAIL;
+
+        if (updatesCount == 0)
+            continue;
+
+        std::vector<line_update_t> &lineUpdates = lineUpdatesData[fullPathIndex];
+        lineUpdates.resize(updatesCount);
+
+        lineUpdatesFileStream.read((char*)lineUpdates.data(), updatesCount * sizeof(line_update_t));
+        if (lineUpdatesFileStream.fail())
+            return E_FAIL;
+    }
+
+    line_update_t startBlock;
+    const int32_t empty = -1;
+    for (const auto &updateFileData : lineUpdatesData)
+    {
+        startBlock.newLine = empty;
+        for (const auto &lineUpdates : updateFileData.second)
+        {
+            if (lineUpdates.newLine != lineUpdates.oldLine)
+            {
+                assert(startBlock.newLine == empty);
+
+                startBlock.newLine = lineUpdates.newLine;
+                startBlock.oldLine = lineUpdates.oldLine;
+            }
+            else
+            {
+                // We use (newLine == oldLine) entry in LineUpdates as "end line" marker for moved region.
+                if (startBlock.newLine != empty)
+                    srcBlockUpdates[updateFileData.first].emplace_back(startBlock.newLine + 1, startBlock.oldLine + 1, lineUpdates.oldLine - 1 - startBlock.oldLine);
+
+                startBlock.newLine = empty;
+            }
+        }
+        // In case this is last method in source file, Roslyn don't provide "end line" in LineUpdates, use max source line as "end line".
+        if (startBlock.newLine != empty)
+            srcBlockUpdates[updateFileData.first].emplace_back(startBlock.newLine + 1, startBlock.oldLine + 1, std::numeric_limits<int32_t>::max());
+    }
+
+    return S_OK;
+}
+
+HRESULT ModulesSources::ApplyPdbDeltaAndLineUpdates(Modules *pModules, ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB,
+                                                    const std::string &lineUpdates, std::unordered_set<mdMethodDef> &methodTokens)
+{
+    HRESULT Status;
+    CORDB_ADDRESS modAddress;
+    IfFailRet(pModule->GetBaseAddress(&modAddress));
+
+    return pModules->GetModuleInfo(modAddress, [&](Modules::ModuleInfo &mdInfo) -> HRESULT
+    {
+        if (mdInfo.m_symbolReaderHandles.empty())
+            return E_FAIL; // Deltas could be applied for already loaded modules with PDB only.
+
+        PVOID pSymbolReaderHandle = nullptr;
+        IfFailRet(Interop::LoadDeltaPdb(deltaPDB, &pSymbolReaderHandle, methodTokens));
+        // Note, even if methodTokens is empty, pSymbolReaderHandle must be added into vector (we use indexes that correspond to il/metadata apply number + will care about release it in proper way).
+        mdInfo.m_symbolReaderHandles.emplace_back(pSymbolReaderHandle);
+
+        src_block_updates_t srcBlockUpdates;
+        IfFailRet(LoadLineUpdatesFile(this, lineUpdates, srcBlockUpdates));
+
+        if (methodTokens.empty() && srcBlockUpdates.empty())
+            return S_OK;
+
+        if (needJMC && !methodTokens.empty())
+            DisableJMCByAttributes(pModule, methodTokens);
+
+        ToRelease<IUnknown> pMDUnknown;
+        IfFailRet(pModule->GetMetaDataInterface(IID_IMetaDataImport, &pMDUnknown));
+        ToRelease<IMetaDataImport> pMDImport;
+        IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMDImport));
+
+        return UpdateSourcesCodeLinesForModule(pModule, pMDImport, methodTokens, srcBlockUpdates, pSymbolReaderHandle, mdInfo.m_methodBlockUpdates);
+    });
+}
+
+HRESULT ModulesSources::GetSourceFullPathByIndex(unsigned index, std::string &fullPath)
+{
+    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+
+    if (m_sourceIndexToPath.size() <= index)
+        return E_FAIL;
+
+#ifndef _WIN32
+    fullPath = m_sourceIndexToPath[index];
+#else
+    fullPath = m_sourceIndexToInitialFullPath[index];
+#endif
+
+    return S_OK;
+}
+
+HRESULT ModulesSources::GetIndexBySourceFullPath(std::string fullPath, unsigned &index)
+{
+#ifdef WIN32
+    HRESULT Status;
+    IfFailRet(Interop::StringToUpper(fullPath));
+#endif
+
+    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+
+    auto findIndex = m_sourcePathToIndex.find(fullPath);
+    if (findIndex == m_sourcePathToIndex.end())
+        return E_FAIL;
+
+    index = findIndex->second;
+    return S_OK;
+}
+
+void ModulesSources::FindFileNames(Utility::string_view pattern, unsigned limit, std::function<void(const char *)> cb)
+{
+#ifdef WIN32
+    std::string uppercase {pattern};
+    if (FAILED(Interop::StringToUpper(uppercase)))
+        return;
+    pattern = uppercase;
+#endif
+
+    auto check = [&](const std::string& str)
+    {
+        if (limit == 0)
+            return false;
+
+        auto pos = str.find(pattern);
+        if (pos != std::string::npos && (pos == 0 || str[pos-1] == '/' || str[pos-1] == '\\'))
+        {
+            limit--;
+#ifndef _WIN32
+            cb(str.c_str());
+#else
+            auto it = m_sourcePathToIndex.find(str);
+            cb (it != m_sourcePathToIndex.end() ? m_sourceIndexToInitialFullPath[it->second].c_str() : str.c_str());
+#endif
+        }
+
+        return true;
+    };
+
+    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+    for (const auto &pair : m_sourceNameToFullPathsIndexes)
+    {
+        LOGD("first '%s'", pair.first.c_str());
+        if (!check(pair.first))
+            return;
+
+        for (const unsigned fileIndex : pair.second)
+        {
+            LOGD("second '%s'", m_sourceIndexToPath[fileIndex].c_str());
+            if (!check(m_sourceIndexToPath[fileIndex]))
+                return;
+        }
+    }
+}
+
+} // namespace netcoredbg
diff --git a/src/metadata/modules_sources.h b/src/metadata/modules_sources.h
new file mode 100644 (file)
index 0000000..f92448f
--- /dev/null
@@ -0,0 +1,204 @@
+// 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"
+
+#include <set>
+#include <mutex>
+#include <functional>
+#include <unordered_set>
+#include <unordered_map>
+#include <vector>
+#include "utils/string_view.h"
+#include "utils/torelease.h"
+
+
+namespace netcoredbg
+{
+
+typedef std::function<HRESULT(ICorDebugModule *, mdMethodDef &)> ResolveFuncBreakpointCallback;
+
+struct method_data_t
+{
+    mdMethodDef methodDef;
+    int32_t startLine; // first segment/method SequencePoint's startLine
+    int32_t endLine; // last segment/method SequencePoint's endLine
+    int32_t startColumn; // first segment/method SequencePoint's startColumn
+    int32_t endColumn; // last segment/method SequencePoint's endColumn
+
+    method_data_t() :
+        methodDef(0),
+        startLine(0),
+        endLine(0),
+        startColumn(0),
+        endColumn(0)
+    {}
+
+    method_data_t(mdMethodDef methodDef_, int32_t startLine_, int32_t endLine_, int32_t startColumn_, int32_t endColumn_) :
+        methodDef(methodDef_),
+        startLine(startLine_),
+        endLine(endLine_),
+        startColumn(startColumn_),
+        endColumn(endColumn_)
+    {}
+
+    bool operator < (const method_data_t &other) const
+    {
+        return endLine < other.endLine || (endLine == other.endLine && endColumn < other.endColumn);
+    }
+
+    bool operator < (const int32_t lineNum) const
+    {
+        return endLine < lineNum;
+    }
+
+    bool operator == (const method_data_t &other) const
+    {
+        return methodDef == other.methodDef &&
+               startLine == other.startLine && endLine == other.endLine &&
+               startColumn == other.startColumn && endColumn == other.endColumn;
+    }
+
+    bool NestedInto(const method_data_t &other) const
+    {
+        assert(startLine != other.startLine || startColumn != other.startColumn);
+        assert(endLine != other.endLine || endColumn != other.endColumn);
+
+        return (startLine > other.startLine || (startLine == other.startLine && startColumn > other.startColumn)) &&
+            (endLine < other.endLine || (endLine == other.endLine && endColumn < other.endColumn));
+    }
+};
+
+struct method_data_t_hash
+{
+    size_t operator()(const method_data_t &p) const
+    {
+        return p.methodDef + (uint32_t)p.startLine * 100 + (uint32_t)p.endLine * 1000;
+    }
+};
+
+struct block_update_t
+{
+    int32_t newLine;
+    int32_t oldLine;
+    int32_t endLineOffset;
+    block_update_t(int32_t newLine_, int32_t oldLine_, int32_t endLineOffset_) :
+        newLine(newLine_), oldLine(oldLine_), endLineOffset(endLineOffset_)
+    {}
+};
+typedef std::unordered_map<unsigned /*source fullPathIndex*/, std::vector<block_update_t>> src_block_updates_t;
+
+struct file_block_update_t
+{
+    unsigned fullPathIndex;
+    int32_t newLine;
+    int32_t oldLine;
+    int32_t endLineOffset;
+    file_block_update_t(unsigned fullPathIndex_, int32_t newLine_, int32_t oldLine_, int32_t endLineOffset_) :
+        fullPathIndex(fullPathIndex_), newLine(newLine_), oldLine(oldLine_), endLineOffset(endLineOffset_)
+    {}
+};
+typedef std::unordered_map<mdMethodDef, std::vector<file_block_update_t>> method_block_updates_t;
+
+template <class T>
+void LineUpdatesForwardCorrection(unsigned fullPathIndex, mdMethodDef methodToken, method_block_updates_t &methodBlockUpdates, T &block)
+{
+    auto findSourceUpdate = methodBlockUpdates.find(methodToken);
+    if (findSourceUpdate == methodBlockUpdates.end())
+        return;
+
+    for (const auto &entry : findSourceUpdate->second)
+    {
+        if (entry.fullPathIndex != fullPathIndex ||
+            entry.oldLine > block.startLine ||
+            entry.oldLine + entry.endLineOffset < block.startLine)
+            continue;
+
+        int32_t offset = entry.newLine - entry.oldLine;
+        block.startLine += offset;
+        block.endLine += offset;
+        break;
+    }
+}
+
+class Modules;
+
+class ModulesSources
+{
+public:
+
+    struct resolved_bp_t
+    {
+        int32_t startLine;
+        int32_t endLine;
+        uint32_t ilOffset;
+        uint32_t methodToken;
+        ToRelease<ICorDebugModule> iCorModule;
+
+        resolved_bp_t(int32_t startLine_, int32_t endLine_, uint32_t ilOffset_, uint32_t methodToken_, ICorDebugModule *pModule) :
+            startLine(startLine_),
+            endLine(endLine_),
+            ilOffset(ilOffset_),
+            methodToken(methodToken_),
+            iCorModule(pModule)
+        {}
+    };
+
+    HRESULT ResolveBreakpoint(
+        /*in*/ Modules *pModules,
+        /*in*/ CORDB_ADDRESS modAddress,
+        /*in*/ std::string filename,
+        /*out*/ unsigned &fullname_index,
+        /*in*/ int sourceLine,
+        /*out*/ std::vector<resolved_bp_t> &resolvedPoints);
+
+    HRESULT FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, PVOID pSymbolReaderHandle);
+    HRESULT GetSourceFullPathByIndex(unsigned index, std::string &fullPath);
+    HRESULT GetIndexBySourceFullPath(std::string fullPath, unsigned &index);
+    HRESULT ApplyPdbDeltaAndLineUpdates(Modules *pModules, ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB,
+                                        const std::string &lineUpdates, std::unordered_set<mdMethodDef> &methodTokens);
+
+    void FindFileNames(Utility::string_view pattern, unsigned limit, std::function<void(const char *)> cb);
+
+private:
+
+    struct FileMethodsData
+    {
+        CORDB_ADDRESS modAddress = 0;
+        // properly ordered on each nested level arrays of methods data
+        std::vector<std::vector<method_data_t>> methodsData;
+        // mapping method's data to array of tokens, that also represent same code
+        // aimed to resolve all methods token for constructor's segment, since it could be part of multiple constructors
+        std::unordered_map<method_data_t, std::vector<mdMethodDef>, method_data_t_hash> multiMethodsData;
+    };
+
+    // Note, breakpoints setup and ran debuggee's process could be in the same time.
+    std::mutex m_sourcesInfoMutex;
+    // Note, we only add to m_sourceIndexToPath/m_sourcePathToIndex/m_sourceIndexToInitialFullPath, "size()" used as index in map at new element add.
+    // m_sourceIndexToPath - mapping index to full path
+    std::vector<std::string> m_sourceIndexToPath;
+    // m_sourcePathToIndex - mapping full path to index
+    std::unordered_map<std::string, unsigned> m_sourcePathToIndex;
+    // m_sourceNameToFullPathsIndexes - mapping file name to set of paths with this file name
+    std::unordered_map<std::string, std::set<unsigned>> m_sourceNameToFullPathsIndexes;
+    // m_sourcesMethodsData - all methods data indexed by full path, second vector hold data with same full path for different modules,
+    //                        since we may have modules with same source full path
+    std::vector<std::vector<FileMethodsData>> m_sourcesMethodsData;
+
+    HRESULT GetFullPathIndex(BSTR document, unsigned &fullPathIndex);
+    HRESULT UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, std::unordered_set<mdMethodDef> methodTokens,
+                                            src_block_updates_t &blockUpdates, PVOID pSymbolReaderHandle, method_block_updates_t &methodBlockUpdates);
+    HRESULT ResolveRelativeSourceFileName(std::string &filename);
+
+#ifdef WIN32
+    // on Windows OS, all files names converted to uppercase in containers above, but this vector hold initial full path names
+    std::vector<std::string> m_sourceIndexToInitialFullPath;
+#endif
+
+};
+
+} // namespace netcoredbg