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
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
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(¤tVersion));\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
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
\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
#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
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
// 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
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
#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
\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
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
#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
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
#include "utils/torelease.h"
#include "utils/utf.h"
#include "managed/interop.h"
-#include "metadata/modules.h" // HasAttribute()
+#include "metadata/attributes.h"
namespace netcoredbg
{
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include <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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "cor.h"
+#include "cordebug.h"
+
+#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
// 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
{
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,
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "cor.h"
+#include "cordebug.h"
+
+#include <unordered_set>
+
+namespace netcoredbg
+{
+
+HRESULT DisableJMCByAttributes(ICorDebugModule *pModule);
+HRESULT DisableJMCByAttributes(ICorDebugModule *pModule, const std::unordered_set<mdMethodDef> &methodTokens);
+
+} // namespace netcoredbg
#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)
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, '.');
{
std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
m_modulesInfo.clear();
- m_modulesUpdateHandlerTypes.clear();
+ m_modulesAppUpdate.Clear();
}
std::string GetModuleFileName(ICorDebugModule *pModule)
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,
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,
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(
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)
{
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;
);
}
-// 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;
}
}
- 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.");
}
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;
}
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;
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)
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(
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)
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(¤tVersion)))
- {
- 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 &)
{
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
#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;
~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;
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);
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,
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,
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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include "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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "cor.h"
+#include "cordebug.h"
+
+#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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#include <list>
+#include <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(¤tVersion)))
+ {
+ 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
--- /dev/null
+// Copyright (c) 2022 Samsung Electronics Co., LTD
+// Distributed under the MIT License.
+// See the LICENSE file in the project root for more information.
+
+#pragma once
+
+#include "cor.h"
+#include "cordebug.h"
+
+#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