Add line updates support for Hot Reload.
authorMikhail Kurinnoi <m.kurinnoi@samsung.com>
Fri, 1 Jul 2022 09:08:23 +0000 (12:08 +0300)
committerAlexander Soldatov/Platform Lab /SRR/Staff Engineer/Samsung Electronics <soldatov.a@samsung.com>
Mon, 18 Jul 2022 15:53:34 +0000 (18:53 +0300)
Other changes:
- Change methods and variables names in order to reflect their real meaning.
- Simplify std::ifstream related code in ApplyMetadataAndILDeltas().
- Improve breakpoint resolve for constructor in partial class (in different sources).

24 files changed:
src/debugger/breakpoint_entry.cpp
src/debugger/breakpoints_func.cpp
src/debugger/manageddebugger.cpp
src/debugger/manageddebugger.h
src/debugger/steppers.cpp
src/interfaces/idebugger.h
src/managed/SymbolReader.cs
src/managed/interop.cpp
src/managed/interop.h
src/metadata/modules.cpp
src/metadata/modules.h
src/protocols/miprotocol.cpp
test-suite/MITestHotReloadAsyncStepping/Program.cs
test-suite/MITestHotReloadBreak/Program.cs
test-suite/MITestHotReloadBreakpoint/Program.cs
test-suite/MITestHotReloadEvaluate/Program.cs
test-suite/MITestHotReloadJMC/Program.cs
test-suite/MITestHotReloadPDB/MITestHotReloadPDB.csproj [new file with mode: 0644]
test-suite/MITestHotReloadPDB/Program.cs [new file with mode: 0644]
test-suite/MITestHotReloadStepping/Program.cs
test-suite/MITestHotReloadWithoutBreak/Program.cs
test-suite/MITestSrcBreakpointResolve/Program.cs
test-suite/VSCodeTestSrcBreakpointResolve/Program.cs
test-suite/sdb_run_tests.sh

index 07ea9da027aa8ce47a9449fd563a7ea545aaa2ef..c73904cb5da0cd286aaad2c594802fd48d54ced9 100644 (file)
@@ -153,12 +153,12 @@ static HRESULT TrySetupAsyncEntryBreakpoint(ICorDebugModule *pModule, IMetaDataI
         return E_FAIL;\r
 \r
     // Note, in case of async `MoveNext` method, user code don't start from 0 IL offset.\r
-    ULONG32 ilCloseOffset;\r
+    ULONG32 ilNextOffset;\r
     const ULONG32 currentVersion = 1; // In case entry breakpoint, this can be only base PDB, not delta PDB for sure.\r
-    IfFailRet(pModules->GetNextSequencePointInMethod(pModule, resultToken, currentVersion, 0, ilCloseOffset));\r
+    IfFailRet(pModules->GetNextUserCodeILOffsetInMethod(pModule, resultToken, currentVersion, 0, ilNextOffset));\r
 \r
     entryPointToken = resultToken;\r
-    entryPointOffset = ilCloseOffset;\r
+    entryPointOffset = ilNextOffset;\r
     return S_OK;\r
 }\r
 \r
index a04befa0671a0a57e38da33635cb471ad58ad509..ff568e29213ea3b53842e6aecfce7212c729e75e 100644 (file)
@@ -262,15 +262,15 @@ HRESULT FuncBreakpoints::AddFuncBreakpoint(ManagedFuncBreakpoint &fbp, ResolvedF
         ULONG32 currentVersion; // Note, new breakpoints could be setup for last code version only, since protocols (MI, VSCode, ...) provide method name (sig) only.\r
         IfFailRet(pFunc->GetCurrentVersionNumber(&currentVersion));\r
 \r
-        ULONG32 ilCloseOffset;\r
-        if (FAILED(m_sharedModules->GetNextSequencePointInMethod(entry.first, entry.second, currentVersion, 0, ilCloseOffset)))\r
+        ULONG32 ilNextOffset;\r
+        if (FAILED(m_sharedModules->GetNextUserCodeILOffsetInMethod(entry.first, entry.second, currentVersion, 0, ilNextOffset)))\r
             return S_OK;\r
 \r
         ToRelease<ICorDebugCode> pCode;\r
         IfFailRet(pFunc->GetILCode(&pCode));\r
 \r
         ToRelease<ICorDebugFunctionBreakpoint> iCorFuncBreakpoint;\r
-        IfFailRet(pCode->CreateBreakpoint(ilCloseOffset, &iCorFuncBreakpoint));\r
+        IfFailRet(pCode->CreateBreakpoint(ilNextOffset, &iCorFuncBreakpoint));\r
         IfFailRet(iCorFuncBreakpoint->Activate(fbp.enabled ? TRUE : FALSE));\r
 \r
         CORDB_ADDRESS modAddress;\r
index 1f802631da39ee99c15726e9d38c13112c35718a..164b71c365b4bd21f4714e8fb23a646c6db31619 100644 (file)
@@ -1265,54 +1265,45 @@ HRESULT ManagedDebugger::SetHotReload(bool enable)
 
 static HRESULT ApplyMetadataAndILDeltas(Modules *pModules, const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL)
 {
-    HRESULT Status = S_OK;
+    HRESULT Status;
 
     std::ifstream deltaILFileStream(deltaIL, std::ios::in | std::ios::binary | std::ios::ate);
     std::ifstream deltaMDFileStream(deltaMD, std::ios::in | std::ios::binary | std::ios::ate);
 
-    if (deltaILFileStream.is_open() && deltaMDFileStream.is_open())
-    {
-        auto deltaILSize = deltaILFileStream.tellg();
-        if (deltaILSize < 0)
-            return E_FAIL;
-        BYTE *deltaILMemBlock = new BYTE[(size_t)deltaILSize];
-        deltaILFileStream.seekg(0, std::ios::beg);
-        deltaILFileStream.read((char*)deltaILMemBlock, deltaILSize);
+    if (!deltaILFileStream.is_open() || !deltaMDFileStream.is_open())
+        return COR_E_FILENOTFOUND;
 
-        auto deltaMDSize = deltaMDFileStream.tellg();
-        if (deltaMDSize < 0)
-            return E_FAIL;
-        BYTE *deltaMDMemBlock = new BYTE[(size_t)deltaMDSize];
-        deltaMDFileStream.seekg(0, std::ios::beg);
-        deltaMDFileStream.read((char*)deltaMDMemBlock, deltaMDSize);
-
-        ToRelease<ICorDebugModule> pModule;
-        IfFailRet(pModules->GetModuleWithName(dllFileName, &pModule, true));
-        ToRelease<ICorDebugModule2> pModule2;
-        IfFailRet(pModule->QueryInterface(IID_ICorDebugModule2, (LPVOID *)&pModule2));
-        IfFailRet(pModule2->ApplyChanges((ULONG)deltaMDSize, deltaMDMemBlock, (ULONG)deltaILSize, deltaILMemBlock));
-    }
-    else
-    {
-        Status = COR_E_FILENOTFOUND;
-    }
+    auto deltaILSize = deltaILFileStream.tellg();
+    if (deltaILSize < 0)
+        return E_FAIL;
+    std::unique_ptr<BYTE[]> deltaILMemBlock(new BYTE[(size_t)deltaILSize]);
+    deltaILFileStream.seekg(0, std::ios::beg);
+    deltaILFileStream.read((char*)deltaILMemBlock.get(), deltaILSize);
 
-    if (deltaILFileStream.is_open())
-        deltaILFileStream.close();
-    if (deltaMDFileStream.is_open())
-        deltaMDFileStream.close();
+    auto deltaMDSize = deltaMDFileStream.tellg();
+    if (deltaMDSize < 0)
+        return E_FAIL;
+    std::unique_ptr<BYTE[]> deltaMDMemBlock(new BYTE[(size_t)deltaMDSize]);
+    deltaMDFileStream.seekg(0, std::ios::beg);
+    deltaMDFileStream.read((char*)deltaMDMemBlock.get(), deltaMDSize);
 
-    return Status;
+    ToRelease<ICorDebugModule> pModule;
+    IfFailRet(pModules->GetModuleWithName(dllFileName, &pModule, true));
+    ToRelease<ICorDebugModule2> pModule2;
+    IfFailRet(pModule->QueryInterface(IID_ICorDebugModule2, (LPVOID *)&pModule2));
+    IfFailRet(pModule2->ApplyChanges((ULONG)deltaMDSize, deltaMDMemBlock.get(), (ULONG)deltaILSize, deltaILMemBlock.get()));
+
+    return S_OK;
 }
 
-HRESULT ManagedDebugger::ApplyPdbDelta(const std::string &dllFileName, const std::string &deltaPDB)
+HRESULT ManagedDebugger::ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates)
 {
     HRESULT Status;
     ToRelease<ICorDebugModule> pModule;
     IfFailRet(m_sharedModules->GetModuleWithName(dllFileName, &pModule, true));
 
     std::unordered_set<mdMethodDef> pdbMethodTokens;
-    IfFailRet(m_sharedModules->ApplyPdbDelta(pModule, m_justMyCode, deltaPDB, pdbMethodTokens));
+    IfFailRet(m_sharedModules->ApplyPdbDeltaAndLineUpdates(pModule, m_justMyCode, deltaPDB, lineUpdates, pdbMethodTokens));
 
     // Since we could have new code lines and new methods added, check all breakpoints again.
     std::vector<BreakpointEvent> events;
@@ -1323,7 +1314,8 @@ HRESULT ManagedDebugger::ApplyPdbDelta(const std::string &dllFileName, const std
     return S_OK;
 }
 
-HRESULT ManagedDebugger::HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL, const std::string &deltaPDB)
+HRESULT ManagedDebugger::HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL,
+                                              const std::string &deltaPDB, const std::string &lineUpdates)
 {
     LogFuncEntry();
 
@@ -1340,7 +1332,7 @@ HRESULT ManagedDebugger::HotReloadApplyDeltas(const std::string &dllFileName, co
         IfFailRet(m_iCorProcess->Stop(0));
 
     IfFailRet(ApplyMetadataAndILDeltas(m_sharedModules.get(), dllFileName, deltaMD, deltaIL));
-    IfFailRet(ApplyPdbDelta(dllFileName, deltaPDB));
+    IfFailRet(ApplyPdbDeltaAndLineUpdates(dllFileName, deltaPDB, lineUpdates));
 
     if (procRunning == TRUE)
         IfFailRet(m_iCorProcess->Continue(0));
index ebb208e694d46958808f8b325753821a6dece8a1..e9353c6a955990f6ec4167a1ef4b26ac04f0d151 100644 (file)
@@ -167,8 +167,9 @@ public:
     HRESULT GetExceptionInfo(ThreadId threadId, ExceptionInfo &exceptionInfo) override;
     HRESULT GetSourceFile(const std::string &sourcePath, char** fileBuf, int* fileLen) override;
     void FreeUnmanaged(PVOID mem) override;
-    HRESULT HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL, const std::string &deltaPDB) override;
-    HRESULT ApplyPdbDelta(const std::string &dllFileName, const std::string &deltaPDB) override;
+    HRESULT HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL,
+                                 const std::string &deltaPDB, const std::string &lineUpdates) override;
+    HRESULT ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates) override;
 
     void FindFileNames(string_view pattern, unsigned limit, SearchCallback) override;
     void FindFunctions(string_view pattern, unsigned limit, SearchCallback) override;
index f00134d59597f0ac0c57ac1d25fff2e9c60a4c0c..c2c887767124e42c0f7cbe93e05aa7b4a0cea40b 100644 (file)
@@ -163,12 +163,12 @@ HRESULT Steppers::ManagedCallbackStepComplete(ICorDebugThread *pThread, CorDebug
 \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 ilCloseUserCodeOffset;\r
+    ULONG32 ilNextUserCodeOffset;\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, ilCloseUserCodeOffset, &noUserCodeFound)))\r
+    if (SUCCEEDED(Status = m_sharedModules->GetFrameILAndNextUserCodeILOffset(iCorFrame, ipOffset, ilNextUserCodeOffset, &noUserCodeFound)))\r
     {\r
         // Current IL offset less than IL offset of next close user code line.\r
-        if (ipOffset < ilCloseUserCodeOffset)\r
+        if (ipOffset < ilNextUserCodeOffset)\r
         {\r
             IfFailRet(m_simpleStepper->SetupStep(pThread, IDebugger::StepType::STEP_OVER));\r
             return S_OK;\r
index e6c75c83ae217bc73f93bfd60e7b98188f05d718..5ae51e314f3603050fc547f965d960fffff03bff 100644 (file)
@@ -103,8 +103,9 @@ public:
     virtual HRESULT GetExceptionInfo(ThreadId threadId, ExceptionInfo &exceptionInfo) = 0;
     virtual HRESULT GetSourceFile(const std::string &sourcePath, char** fileBuf, int* fileLen) = 0;
     virtual void FreeUnmanaged(PVOID mem) = 0;
-    virtual HRESULT HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL, const std::string &deltaPDB) = 0;
-    virtual HRESULT ApplyPdbDelta(const std::string &dllFileName, const std::string &deltaPDB) = 0;
+    virtual HRESULT HotReloadApplyDeltas(const std::string &dllFileName, const std::string &deltaMD, const std::string &deltaIL,
+                                         const std::string &deltaPDB, const std::string &lineUpdates) = 0;
+    virtual HRESULT ApplyPdbDeltaAndLineUpdates(const std::string &dllFileName, const std::string &deltaPDB, const std::string &lineUpdates) = 0;
     typedef std::function<void(const char *)> SearchCallback;
     virtual void FindFileNames(string_view pattern, unsigned limit, SearchCallback) = 0;
     virtual void FindFunctions(string_view pattern, unsigned limit, SearchCallback) = 0;
index 0fad911597a40b2549be1f01e4dd9836bb3a13cd..bf7672179140c106e467590ea92d8c64aaa7ca00 100644 (file)
@@ -400,10 +400,10 @@ namespace NetCoreDbg
         /// <param name="sequencePoint">sequence point return</param>
         /// <param name="noUserCodeFound">return 1 in case all sequence points checked and no user code was found, otherwise return 0</param>
         /// <returns>"Ok" if information is available</returns>
-        private static RetCode GetNextSequencePointByILOffset(IntPtr symbolReaderHandle, int methodToken, uint ilOffset, out uint ilCloseOffset, out int noUserCodeFound)
+        private static RetCode GetNextUserCodeILOffset(IntPtr symbolReaderHandle, int methodToken, uint ilOffset, out uint ilNextOffset, out int noUserCodeFound)
         {
             Debug.Assert(symbolReaderHandle != IntPtr.Zero);
-            ilCloseOffset = 0;
+            ilNextOffset = 0;
             noUserCodeFound = 0;
 
             try
@@ -420,7 +420,7 @@ namespace NetCoreDbg
 
                     if (point.Offset >= ilOffset)
                     {
-                        ilCloseOffset = (uint)point.Offset;
+                        ilNextOffset = (uint)point.Offset;
                         return RetCode.OK;
                     }
                 }
@@ -458,11 +458,6 @@ namespace NetCoreDbg
                 startColumn = startColumn_;
                 endColumn = endColumn_;
             }
-            public void SetRangeEnd(int endLine_, int endColumn_)
-            {
-                endLine = endLine_;
-                endColumn = endColumn_;
-            }
             public void ExtendRange(int startLine_, int endLine_, int startColumn_, int endColumn_)
             {
                 if (startLine > startLine_)
@@ -532,42 +527,17 @@ namespace NetCoreDbg
                 {
                     int methodToken = Marshal.ReadInt32(constrTokens, i);
                     method_data_t currentData = new method_data_t(methodToken, 0, 0, 0, 0);
-                    DocumentHandle currentDocHandle = new DocumentHandle();
 
                     foreach (SequencePoint p in GetSequencePointCollection(methodToken, reader))
                     {
                         if (p.StartLine == 0 || p.StartLine == SequencePoint.HiddenLine)
                             continue;
 
-                        if (currentData.startLine == 0)
-                        {
-                            currentData.SetRange(p.StartLine, p.EndLine, p.StartColumn, p.EndColumn);
-                            currentDocHandle = p.Document;
-                        }
-                        // same segment only in case same file and on next line or on same line but on the right
-                        else if ((p.StartLine == currentData.endLine + 1 ||
-                                  (p.StartLine == currentData.endLine && p.StartColumn > currentData.endColumn)) &&
-                                 currentDocHandle == p.Document )
-                        {
-                            currentData.SetRangeEnd(p.EndLine, p.EndColumn);
-                        }
-                        else // SequencePoint from another segment
-                        {
-                            if (!ModuleData.ContainsKey(currentDocHandle))
-                                ModuleData[currentDocHandle] = new List<method_data_t>();
-
-                            ModuleData[currentDocHandle].Add(currentData);
-                            currentData.SetRange(p.StartLine, p.EndLine, p.StartColumn, p.EndColumn);
-                            currentDocHandle = p.Document;
-                        }
-                    }
+                        if (!ModuleData.ContainsKey(p.Document))
+                                ModuleData[p.Document] = new List<method_data_t>();
 
-                    if (currentData.startLine != 0)
-                    {
-                        if (!ModuleData.ContainsKey(currentDocHandle))
-                            ModuleData[currentDocHandle] = new List<method_data_t>();
-
-                        ModuleData[currentDocHandle].Add(currentData);
+                        currentData.SetRange(p.StartLine, p.EndLine, p.StartColumn, p.EndColumn);
+                        ModuleData[p.Document].Add(currentData);
                     }
                 }
 
@@ -681,7 +651,8 @@ namespace NetCoreDbg
         /// <param name="Count">entry's count in data</param>
         /// <param name="data">pointer to memory with result</param>
         /// <returns>"Ok" if information is available</returns>
-        internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int nestedToken, out int Count, out IntPtr data)
+        internal static RetCode ResolveBreakPoints(IntPtr symbolReaderHandles, int tokenNum, IntPtr Tokens, int sourceLine, int nestedToken,
+                                                   out int Count, [MarshalAs(UnmanagedType.LPWStr)] string sourcePath, out IntPtr data)
         {
             Debug.Assert(symbolReaderHandles != IntPtr.Zero);
             Count = 0;
@@ -715,6 +686,11 @@ namespace NetCoreDbg
                         if (p.StartLine == 0 || p.StartLine == SequencePoint.HiddenLine || p.EndLine < sourceLine)
                             continue;
 
+                        // Note, in case of constructors, we must care about source too, since we may have situation when field/property have same line in another source.
+                        var fileName = reader.GetString(reader.GetDocument(p.Document).Name);
+                        if (fileName != sourcePath)
+                            continue;
+
                         // first access, assign to first user code sequence point
                         if (nearestSP.StartLine == 0)
                         {
index 25624ac84573e68f78f5e9d76ea11ca84a13bf94..97c2a94dc525d19aef0f6ad5f7c59885d9016412 100644 (file)
@@ -61,10 +61,10 @@ typedef  void (*DisposeDelegate)(PVOID);
 typedef  RetCode (*GetLocalVariableNameAndScope)(PVOID, int32_t, int32_t, BSTR*, uint32_t*, uint32_t*);
 typedef  RetCode (*GetHoistedLocalScopes)(PVOID, int32_t, PVOID*, int32_t*);
 typedef  RetCode (*GetSequencePointByILOffsetDelegate)(PVOID, mdMethodDef, uint32_t, PVOID);
-typedef  RetCode (*GetNextSequencePointByILOffsetDelegate)(PVOID, mdMethodDef, uint32_t, uint32_t*, int32_t*);
+typedef  RetCode (*GetNextUserCodeILOffsetDelegate)(PVOID, mdMethodDef, uint32_t, uint32_t*, int32_t*);
 typedef  RetCode (*GetStepRangesFromIPDelegate)(PVOID, int32_t, mdMethodDef, uint32_t*, uint32_t*);
 typedef  RetCode (*GetModuleMethodsRangesDelegate)(PVOID, uint32_t, PVOID, uint32_t, PVOID, PVOID*);
-typedef  RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t*, PVOID*);
+typedef  RetCode (*ResolveBreakPointsDelegate)(PVOID[], int32_t, PVOID, int32_t, int32_t, int32_t*, const WCHAR*, PVOID*);
 typedef  RetCode (*GetAsyncMethodSteppingInfoDelegate)(PVOID, mdMethodDef, PVOID*, int32_t*, uint32_t*);
 typedef  RetCode (*GetSourceDelegate)(PVOID, const WCHAR*, int32_t*, PVOID*);
 typedef  PVOID (*LoadDeltaPdbDelegate)(const WCHAR*, PVOID*, int32_t*);
@@ -83,7 +83,7 @@ DisposeDelegate disposeDelegate = nullptr;
 GetLocalVariableNameAndScope getLocalVariableNameAndScopeDelegate = nullptr;
 GetHoistedLocalScopes getHoistedLocalScopesDelegate = nullptr;
 GetSequencePointByILOffsetDelegate getSequencePointByILOffsetDelegate = nullptr;
-GetNextSequencePointByILOffsetDelegate getNextSequencePointByILOffsetDelegate = nullptr;
+GetNextUserCodeILOffsetDelegate getNextUserCodeILOffsetDelegate = nullptr;
 GetStepRangesFromIPDelegate getStepRangesFromIPDelegate = nullptr;
 GetModuleMethodsRangesDelegate getModuleMethodsRangesDelegate = nullptr;
 ResolveBreakPointsDelegate resolveBreakPointsDelegate = nullptr;
@@ -227,7 +227,7 @@ void Init(const std::string &coreClrPath)
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetLocalVariableNameAndScope", (void **)&getLocalVariableNameAndScopeDelegate)) &&
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetHoistedLocalScopes", (void **)&getHoistedLocalScopesDelegate)) &&
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetSequencePointByILOffset", (void **)&getSequencePointByILOffsetDelegate)) &&
-        SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetNextSequencePointByILOffset", (void **)&getNextSequencePointByILOffsetDelegate)) &&
+        SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetNextUserCodeILOffset", (void **)&getNextUserCodeILOffsetDelegate)) &&
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetStepRangesFromIP", (void **)&getStepRangesFromIPDelegate)) &&
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "GetModuleMethodsRanges", (void **)&getModuleMethodsRangesDelegate)) &&
         SUCCEEDED(Status = createDelegate(hostHandle, domainId, ManagedPartDllName, SymbolReaderClassName, "ResolveBreakPoints", (void **)&resolveBreakPointsDelegate)) &&
@@ -252,7 +252,7 @@ void Init(const std::string &coreClrPath)
                               getLocalVariableNameAndScopeDelegate &&
                               getHoistedLocalScopesDelegate &&
                               getSequencePointByILOffsetDelegate &&
-                              getNextSequencePointByILOffsetDelegate &&
+                              getNextUserCodeILOffsetDelegate &&
                               getStepRangesFromIPDelegate &&
                               getModuleMethodsRangesDelegate &&
                               resolveBreakPointsDelegate &&
@@ -291,7 +291,7 @@ void Shutdown()
     getLocalVariableNameAndScopeDelegate = nullptr;
     getHoistedLocalScopesDelegate = nullptr;
     getSequencePointByILOffsetDelegate = nullptr;
-    getNextSequencePointByILOffsetDelegate = nullptr;
+    getNextUserCodeILOffsetDelegate = nullptr;
     getStepRangesFromIPDelegate = nullptr;
     getModuleMethodsRangesDelegate = nullptr;
     resolveBreakPointsDelegate = nullptr;
@@ -318,16 +318,16 @@ HRESULT GetSequencePointByILOffset(PVOID pSymbolReaderHandle, mdMethodDef method
     return retCode == RetCode::OK ? S_OK : E_FAIL;
 }
 
-HRESULT GetNextSequencePointByILOffset(PVOID pSymbolReaderHandle, mdMethodDef methodToken, ULONG32 ilOffset, ULONG32 &ilCloseOffset, bool *noUserCodeFound)
+HRESULT GetNextUserCodeILOffset(PVOID pSymbolReaderHandle, mdMethodDef methodToken, ULONG32 ilOffset, ULONG32 &ilNextOffset, bool *noUserCodeFound)
 {
     std::unique_lock<Utility::RWLock::Reader> read_lock(CLRrwlock.reader);
-    if (!getNextSequencePointByILOffsetDelegate || !pSymbolReaderHandle)
+    if (!getNextUserCodeILOffsetDelegate || !pSymbolReaderHandle)
         return E_FAIL;
 
     int32_t NoUserCodeFound = 0;
 
     // Sequence points with startLine equal to 0xFEEFEE marker are filtered out on the managed side.
-    RetCode retCode = getNextSequencePointByILOffsetDelegate(pSymbolReaderHandle, methodToken, ilOffset, &ilCloseOffset, &NoUserCodeFound);
+    RetCode retCode = getNextUserCodeILOffsetDelegate(pSymbolReaderHandle, methodToken, ilOffset, &ilNextOffset, &NoUserCodeFound);
 
     if (noUserCodeFound)
         *noUserCodeFound = NoUserCodeFound == 1;
@@ -411,13 +411,13 @@ HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensN
     return retCode == RetCode::OK ? S_OK : E_FAIL;
 }
 
-HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, PVOID *data)
+HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data)
 {
     std::unique_lock<Utility::RWLock::Reader> read_lock(CLRrwlock.reader);
     if (!resolveBreakPointsDelegate || !pSymbolReaderHandles || !Tokens || !data)
         return E_FAIL;
 
-    RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, nestedToken, &Count, data);
+    RetCode retCode = resolveBreakPointsDelegate(pSymbolReaderHandles, tokenNum, Tokens, sourceLine, nestedToken, &Count, to_utf16(sourcePath).c_str(), data);
     return retCode == RetCode::OK ? S_OK : E_FAIL;
 }
 
index 879d449420bcad0b489f11e106c930b9dda92c3c..8b3d20b16a532c603390e55222d8160f0e5c7b88 100644 (file)
@@ -88,13 +88,13 @@ namespace Interop
                                       ULONG64 inMemoryPdbAddress, ULONG64 inMemoryPdbSize, VOID **ppSymbolReaderHandle);
     void DisposeSymbols(PVOID pSymbolReaderHandle);
     HRESULT GetSequencePointByILOffset(PVOID pSymbolReaderHandle, mdMethodDef MethodToken, ULONG32 IlOffset, SequencePoint *sequencePoint);
-    HRESULT GetNextSequencePointByILOffset(PVOID pSymbolReaderHandle, mdMethodDef MethodToken, ULONG32 IlOffset, ULONG32 &ilCloseOffset, bool *noUserCodeFound);
+    HRESULT GetNextUserCodeILOffset(PVOID pSymbolReaderHandle, mdMethodDef MethodToken, ULONG32 IlOffset, ULONG32 &ilNextOffset, bool *noUserCodeFound);
     HRESULT GetNamedLocalVariableAndScope(PVOID pSymbolReaderHandle, mdMethodDef methodToken, ULONG localIndex,
                                           WCHAR *localName, ULONG localNameLen, ULONG32 *pIlStart, ULONG32 *pIlEnd);
     HRESULT GetHoistedLocalScopes(PVOID pSymbolReaderHandle, mdMethodDef methodToken, PVOID *data, int32_t &hoistedLocalScopesCount);
     HRESULT GetStepRangesFromIP(PVOID pSymbolReaderHandle, ULONG32 ip, mdMethodDef MethodToken, ULONG32 *ilStartOffset, ULONG32 *ilEndOffset);
     HRESULT GetModuleMethodsRanges(PVOID pSymbolReaderHandle, uint32_t constrTokensNum, PVOID constrTokens, uint32_t normalTokensNum, PVOID normalTokens, PVOID *data);
-    HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, PVOID *data);
+    HRESULT ResolveBreakPoints(PVOID pSymbolReaderHandles[], int32_t tokenNum, PVOID Tokens, int32_t sourceLine, int32_t nestedToken, int32_t &Count, const std::string &sourcePath, PVOID *data);
     HRESULT GetAsyncMethodSteppingInfo(PVOID pSymbolReaderHandle, mdMethodDef methodToken, std::vector<AsyncAwaitInfoBlock> &AsyncAwaitInfo, ULONG32 *ilOffset);
     HRESULT GetSource(PVOID symbolReaderHandle, const std::string fileName, PVOID *data, int32_t *length);
     HRESULT LoadDeltaPdb(const std::string &pdbPath, VOID **ppSymbolReaderHandle, std::unordered_set<mdMethodDef> &methodTokens);
index 7f5a29fd60f762d7da940b29165985201973d7c5..fe2775104e3099b43e4015e057e5c6ca84f8733c 100644 (file)
@@ -9,6 +9,7 @@
 #include <map>
 #include <iomanip>
 #include <algorithm>
+#include <fstream>
 
 #include "managed/interop.h"
 #include "utils/platform.h"
@@ -487,6 +488,45 @@ HRESULT Modules::ResolveFuncBreakpointInModule(ICorDebugModule *pModule, const s
     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,
@@ -517,7 +557,7 @@ HRESULT Modules::GetFrameILAndSequencePoint(
     CORDB_ADDRESS modAddress;
     IfFailRet(pModule->GetBaseAddress(&modAddress));
 
-    std::lock_guard<std::mutex> lock(m_modulesInfoMutex);
+    std::lock_guard<std::mutex> lockModulesInfo(m_modulesInfoMutex);
     auto info_pair = m_modulesInfo.find(modAddress);
     if (info_pair == m_modulesInfo.end())
     {
@@ -528,13 +568,20 @@ HRESULT Modules::GetFrameILAndSequencePoint(
     if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
         return E_FAIL;
 
-    return 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);
+
+    return S_OK;
 }
 
 HRESULT Modules::GetFrameILAndNextUserCodeILOffset(
     ICorDebugFrame *pFrame,
     ULONG32 &ilOffset,
-    ULONG32 &ilCloseOffset,
+    ULONG32 &ilNextOffset,
     bool *noUserCodeFound)
 {
     HRESULT Status;
@@ -559,7 +606,7 @@ HRESULT Modules::GetFrameILAndNextUserCodeILOffset(
     ToRelease<ICorDebugModule> pModule;
     IfFailRet(pFunc->GetModule(&pModule));
 
-    return GetNextSequencePointInMethod(pModule, methodToken, methodVersion, ilOffset, ilCloseOffset, noUserCodeFound);
+    return GetNextUserCodeILOffsetInMethod(pModule, methodToken, methodVersion, ilOffset, ilNextOffset, noUserCodeFound);
 }
 
 HRESULT Modules::GetStepRangeFromCurrentIP(ICorDebugThread *pThread, COR_DEBUG_STEP_RANGE *range)
@@ -935,12 +982,12 @@ HRESULT Modules::GetModuleWithName(const std::string &name, ICorDebugModule **pp
     return E_FAIL;
 }
 
-HRESULT Modules::GetNextSequencePointInMethod(
+HRESULT Modules::GetNextUserCodeILOffsetInMethod(
     ICorDebugModule *pModule,
     mdMethodDef methodToken,
     ULONG32 methodVersion,
     ULONG32 ilOffset,
-    ULONG32 &ilCloseOffset,
+    ULONG32 &ilNextOffset,
     bool *noUserCodeFound)
 {
     HRESULT Status;
@@ -958,7 +1005,7 @@ HRESULT Modules::GetNextSequencePointInMethod(
     if (mdInfo.m_symbolReaderHandles.empty() || mdInfo.m_symbolReaderHandles.size() < methodVersion)
         return E_FAIL;
 
-    return Interop::GetNextSequencePointByILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, ilCloseOffset, noUserCodeFound);
+    return Interop::GetNextUserCodeILOffset(mdInfo.m_symbolReaderHandles[methodVersion - 1], methodToken, ilOffset, ilNextOffset, noUserCodeFound);
 }
 
 HRESULT Modules::GetSequencePointByILOffset(
@@ -1163,23 +1210,94 @@ HRESULT Modules::FillSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDa
     return S_OK;
 }
 
-HRESULT Modules::UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMetaDataImport *pMDImport, std::unordered_set<mdMethodDef> methodTokens, PVOID pSymbolReaderHandle)
+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));
-    if (inputData == nullptr)
+
+    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 (int i = 0; i < inputData->fileNum; i++)
+    for (const auto &updateData : srcUpdateData)
     {
-        unsigned fullPathIndex;
-        IfFailRet(GetFullPathIndex(inputData->moduleMethodsData[i].document, fullPathIndex));
+        const unsigned fullPathIndex = updateData.first;
 
         std::map<size_t, std::set<method_data_t>> inputMethodsData;
         if (m_sourcesMethodsData[fullPathIndex].empty())
@@ -1188,31 +1306,32 @@ HRESULT Modules::UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMeta
             auto &tmpFileMethodsData = m_sourcesMethodsData[fullPathIndex].back();
             tmpFileMethodsData.modAddress = modAddress;
 
-            for (int j = 0; j < inputData->moduleMethodsData[i].methodNum; j++)
+            for (int j = 0; j < updateData.second.methodNum; j++)
             {
-                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, inputData->moduleMethodsData[i].methodsData[j], 0);
+                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
             }
         }
         else
         {
-            // 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.
             std::unordered_set<mdMethodDef> inputMetodDefSet;
-            for (int j = 0; j < inputData->moduleMethodsData[i].methodNum; j++)
+            for (int j = 0; j < updateData.second.methodNum; j++)
             {
-                inputMetodDefSet.insert(inputData->moduleMethodsData[i].methodsData[j].methodDef);
+                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 (auto entryData : tmpFileMethodsData.multiMethodsData)
+            for (const auto &entryData : tmpFileMethodsData.multiMethodsData)
             {
                 auto findData = inputMetodDefSet.find(entryData.first.methodDef);
                 if (findData == inputMetodDefSet.end())
                     tmpMultiMethodsData.emplace_back(entryData.first);
 
-                for (auto entryMethodDef : entryData.second)
+                for (const auto &entryMethodDef : entryData.second)
                 {
                     findData = inputMetodDefSet.find(entryMethodDef);
                     if (findData == inputMetodDefSet.end())
@@ -1222,27 +1341,31 @@ HRESULT Modules::UpdateSourcesCodeLinesForModule(ICorDebugModule *pModule, IMeta
             }
 
             tmpFileMethodsData.multiMethodsData.clear();
-            for (auto methodData : tmpMultiMethodsData)
+            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 &methodsData : tmpFileMethodsData.methodsData)
             {
-                for (auto methodData : 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 < inputData->moduleMethodsData[i].methodNum; j++)
+            for (int j = 0; j < updateData.second.methodNum; j++)
             {
-                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, inputData->moduleMethodsData[i].methodsData[j], 0);
+                AddMethodData(inputMethodsData, tmpFileMethodsData.multiMethodsData, updateData.second.methodsData[j], 0);
             }
         }
 
@@ -1359,7 +1482,8 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::
     IfFailRet(Interop::StringToUpper(filename));
 #endif
 
-    std::lock_guard<std::mutex> lock(m_sourcesInfoMutex);
+    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())
@@ -1446,10 +1570,18 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::
             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, &data))
+                                               correctedStartLine, closestNestedToken, Count, fullName, &data))
             || data == nullptr)
         {
             continue;
@@ -1459,6 +1591,10 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::
         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()));
         }
@@ -1467,7 +1603,90 @@ HRESULT Modules::ResolveBreakpoint(/*in*/ CORDB_ADDRESS modAddress, /*in*/ std::
     return S_OK;
 }
 
-HRESULT Modules::ApplyPdbDelta(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB, std::unordered_set<mdMethodDef> &methodTokens)
+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;
+}
+
+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;
@@ -1490,10 +1709,13 @@ HRESULT Modules::ApplyPdbDelta(ICorDebugModule *pModule, bool needJMC, const std
     // 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);
 
-    if (methodTokens.empty())
+    src_block_updates_t srcBlockUpdates;
+    IfFailRet(LoadLineUpdatesFile(this, lineUpdates, srcBlockUpdates));
+
+    if (methodTokens.empty() && srcBlockUpdates.empty())
         return S_OK;
 
-    if (needJMC)
+    if (needJMC && !methodTokens.empty())
         DisableJMCByAttributes(pModule, methodTokens);
 
     ToRelease<IUnknown> pMDUnknown;
@@ -1501,7 +1723,7 @@ HRESULT Modules::ApplyPdbDelta(ICorDebugModule *pModule, bool needJMC, const std
     ToRelease<IMetaDataImport> pMDImport;
     IfFailRet(pMDUnknown->QueryInterface(IID_IMetaDataImport, (LPVOID*) &pMDImport));
 
-    return UpdateSourcesCodeLinesForModule(pModule, pMDImport, methodTokens, pSymbolReaderHandle);
+    return UpdateSourcesCodeLinesForModule(pModule, pMDImport, methodTokens, srcBlockUpdates, pSymbolReaderHandle, mdInfo.m_methodBlockUpdates);
 }
 
 HRESULT Modules::GetSourceFullPathByIndex(unsigned index, std::string &fullPath)
index ab6b493e6a582a282533af08006b133a15ee0567..5eb8d4d5dd9ab5ec58053876a758080bc91513e7 100644 (file)
@@ -99,12 +99,37 @@ 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
 {
     struct ModuleInfo
     {
         std::vector<PVOID> m_symbolReaderHandles;
         ToRelease<ICorDebugModule> m_iCorModule;
+        // Cache for LineUpdates data for all methods in this module (Hot Reload related).
+        method_block_updates_t m_methodBlockUpdates;
 
         ModuleInfo(PVOID Handle, ICorDebugModule *Module) :
             m_iCorModule(Module)
@@ -155,8 +180,8 @@ class Modules
 
     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, 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
@@ -200,7 +225,8 @@ public:
 
     HRESULT GetSourceFullPathByIndex(unsigned index, std::string &fullPath);
     HRESULT GetIndexBySourceFullPath(std::string fullPath, unsigned &index);
-    HRESULT ApplyPdbDelta(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB, std::unordered_set<mdMethodDef> &methodTokens);
+    HRESULT ApplyPdbDeltaAndLineUpdates(ICorDebugModule *pModule, bool needJMC, const std::string &deltaPDB,
+                                        const std::string &lineUpdates, std::unordered_set<mdMethodDef> &methodTokens);
 
     HRESULT GetModuleWithName(const std::string &name, ICorDebugModule **ppModule, bool onlyWithPDB = false);
 
@@ -212,7 +238,7 @@ public:
     HRESULT GetFrameILAndNextUserCodeILOffset(
         ICorDebugFrame *pFrame,
         ULONG32 &ilOffset,
-        ULONG32 &ilCloseOffset,
+        ULONG32 &ilNextOffset,
         bool *noUserCodeFound);
 
     HRESULT ResolveFuncBreakpointInAny(
@@ -257,12 +283,12 @@ public:
         PVOID *data,
         int32_t &hoistedLocalScopesCount);
 
-    HRESULT GetNextSequencePointInMethod(
+    HRESULT GetNextUserCodeILOffsetInMethod(
         ICorDebugModule *pModule,
         mdMethodDef methodToken,
         ULONG32 methodVersion,
         ULONG32 ilOffset,
-        ULONG32 &ilCloseOffset,
+        ULONG32 &ilNextOffset,
         bool *noUserCodeFound = nullptr);
 
     HRESULT GetSequencePointByILOffset(
index f85ea95ab9dc0cd9e516faf42df3908cab1d116f..5386c249dde046a0ec76423d2bbca42079541fa6 100644 (file)
@@ -1097,9 +1097,9 @@ HRESULT MIProtocol::HandleCommand(const std::string& command, const std::vector<
     { "apply-deltas", [this](const std::vector<std::string> &args, std::string &output) -> HRESULT {
         HRESULT Status;
 
-        if (args.size() != 4)
+        if (args.size() != 5)
         {
-            output = "Command requires 4 arguments";
+            output = "Command requires 5 arguments";
             return E_FAIL;
         }
 
@@ -1107,8 +1107,9 @@ HRESULT MIProtocol::HandleCommand(const std::string& command, const std::vector<
         std::string deltaMD = args.at(1);
         std::string deltaIL = args.at(2);
         std::string deltaPDB = args.at(3);
+        std::string lineUpdates = args.at(4);
 
-        IfFailRet(m_sharedDebugger->HotReloadApplyDeltas(dllFileName, deltaMD, deltaIL, deltaPDB));
+        IfFailRet(m_sharedDebugger->HotReloadApplyDeltas(dllFileName, deltaMD, deltaIL, deltaPDB, lineUpdates));
 
         return S_OK;
     }},
index 3cf069f91bf44aba27d56d5e3c13eeb9125cc4a2..c786b550f245e6519014e999c05a1a3fb616bfdf 100644 (file)
@@ -284,6 +284,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -296,7 +297,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index a835b513cca3ec2037305ca62b124779e7fbfb95..4062b1530d5146355efe5a63bc1287f8a21b8385 100644 (file)
@@ -304,6 +304,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -316,7 +317,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index 1961249340395b1831b29db36f148c080fb87d11..8b5f0d0473bb14a922ac93b4d9a80be263f1a809 100644 (file)
@@ -219,6 +219,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -231,7 +232,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index 2dc1698f3d6d67f604d008fd65b04ea39039734c..4020a55f32d35e18187c8c3fe2919c88042192c1 100644 (file)
@@ -231,6 +231,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -243,7 +244,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index 72469f63cf6d66598ff75e4a07b94659088c3a25..56f50a364bc79c6ce2148f63a56268685a0925fa 100644 (file)
@@ -249,6 +249,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -261,7 +262,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
diff --git a/test-suite/MITestHotReloadPDB/MITestHotReloadPDB.csproj b/test-suite/MITestHotReloadPDB/MITestHotReloadPDB.csproj
new file mode 100644 (file)
index 0000000..b3d4e42
--- /dev/null
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+  <ItemGroup>\r
+    <ProjectReference Include="..\NetcoreDbgTest\NetcoreDbgTest.csproj" />\r
+    <ProjectReference Include="..\TestAppHotReload\TestAppHotReload.csproj" />\r
+  </ItemGroup>\r
+\r
+  <PropertyGroup>\r
+    <OutputType>Exe</OutputType>\r
+    <TargetFramework>netcoreapp3.1</TargetFramework>\r
+  </PropertyGroup>\r
+\r
+</Project>\r
diff --git a/test-suite/MITestHotReloadPDB/Program.cs b/test-suite/MITestHotReloadPDB/Program.cs
new file mode 100644 (file)
index 0000000..7f75201
--- /dev/null
@@ -0,0 +1,481 @@
+using System;\r
+using System.IO;\r
+using System.Runtime.InteropServices;\r
+\r
+using NetcoreDbgTest;\r
+using NetcoreDbgTest.MI;\r
+using NetcoreDbgTest.Script;\r
+using NetcoreDbgTest.GetDeltaApi;\r
+\r
+namespace NetcoreDbgTest.Script\r
+{\r
+    class Context\r
+    {\r
+        public void Prepare(string caller_trace)\r
+        {\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-gdb-set enable-hot-reload 1").Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-file-exec-and-symbols " + ControlInfo.CorerunPath).Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-exec-arguments " + ControlInfo.TargetAssemblyPath).Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+\r
+            Assert.Equal(MIResultClass.Running,\r
+                         MIDebugger.Request("-exec-run").Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        bool IsStoppedEvent(MIOutOfBandRecord record)\r
+        {\r
+            if (record.Type != MIOutOfBandRecordType.Async) {\r
+                return false;\r
+            }\r
+\r
+            var asyncRecord = (MIAsyncRecord)record;\r
+\r
+            if (asyncRecord.Class != MIAsyncRecordClass.Exec ||\r
+                asyncRecord.Output.Class != MIAsyncOutputClass.Stopped) {\r
+                return false;\r
+            }\r
+\r
+            return true;\r
+        }\r
+\r
+        public void WasEntryPointHit(string realNamespace, string caller_trace)\r
+        {\r
+            Func<MIOutOfBandRecord, bool> filter = (record) => {\r
+                if (!IsStoppedEvent(record)) {\r
+                    return false;\r
+                }\r
+\r
+                var output = ((MIAsyncRecord)record).Output;\r
+                var reason = (MIConst)output["reason"];\r
+\r
+                if (reason.CString != "entry-point-hit") {\r
+                    return false;\r
+                }\r
+\r
+                var frame = (MITuple)output["frame"];\r
+                var func = (MIConst)frame["func"];\r
+                if (func.CString == realNamespace + ".Program.Main()") {\r
+                    return true;\r
+                }\r
+\r
+                return false;\r
+            };\r
+\r
+            Assert.True(MIDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void WasBreakpointHit(string caller_trace, string bpFileName, int bpNumLine)\r
+        {\r
+            Func<MIOutOfBandRecord, bool> filter = (record) => {\r
+                if (!IsStoppedEvent(record)) {\r
+                    return false;\r
+                }\r
+\r
+                var output = ((MIAsyncRecord)record).Output;\r
+                var reason = (MIConst)output["reason"];\r
+\r
+                if (reason.CString != "breakpoint-hit") {\r
+                    return false;\r
+                }\r
+\r
+                var frame = (MITuple)output["frame"];\r
+                var fileName = (MIConst)frame["file"];\r
+                var line = ((MIConst)frame["line"]).Int;\r
+\r
+                if (fileName.CString == bpFileName &&\r
+                    line == bpNumLine) {\r
+                    return true;\r
+                }\r
+\r
+                return false;\r
+            };\r
+\r
+            Assert.True(MIDebugger.IsEventReceived(filter),\r
+                        @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void WasExit(string caller_trace)\r
+        {\r
+            Func<MIOutOfBandRecord, bool> filter = (record) => {\r
+                if (!IsStoppedEvent(record)) {\r
+                    return false;\r
+                }\r
+\r
+                var output = ((MIAsyncRecord)record).Output;\r
+                var reason = (MIConst)output["reason"];\r
+\r
+                if (reason.CString != "exited") {\r
+                    return false;\r
+                }\r
+\r
+                var exitCode = (MIConst)output["exit-code"];\r
+\r
+                if (exitCode.CString == "0") {\r
+                    return true;\r
+                }\r
+\r
+                return false;\r
+            };\r
+\r
+            Assert.True(MIDebugger.IsEventReceived(filter), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void DebuggerExit(string caller_trace)\r
+        {\r
+            Assert.Equal(MIResultClass.Exit,\r
+                         MIDebugger.Request("-gdb-exit").Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void EnableBreakpoint(string caller_trace, string bpFileName, int bpNumLine)\r
+        {\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-break-insert -f " + bpFileName + ":" + bpNumLine).Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void EnableFuncBreakpoint(string caller_trace, string funcName)\r
+        {\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-break-insert -f " + funcName).Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void Continue(string caller_trace)\r
+        {\r
+            Assert.Equal(MIResultClass.Running,\r
+                         MIDebugger.Request("-exec-continue").Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void CheckHostRuntimeVersion(string caller_trace)\r
+        {\r
+            Assert.True(GetDeltaApi.CheckRuntimeVersion(), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void CheckHostOS(string caller_trace)\r
+        {\r
+            Assert.True(RuntimeInformation.IsOSPlatform(OSPlatform.Linux), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void CheckTargetRuntimeVersion(string caller_trace)\r
+        {\r
+            var res = MIDebugger.Request("-var-create - * System.Environment.Version.Major>=6");\r
+            Assert.Equal(MIResultClass.Done, res.Class, @"__FILE__:__LINE__"+"\n" + caller_trace);\r
+            Assert.Equal("true", ((MIConst)res["value"]).CString, @"__FILE__:__LINE__"+"\n" + caller_trace);\r
+        }\r
+\r
+        public void StartGenDeltaSession(string caller_trace)\r
+        {\r
+            string projectPath = Path.GetDirectoryName(ControlInfo.SourceFilesPath);\r
+            Assert.True(GetDeltaApi.StartGenDeltaSession(projectPath), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void EndGenDeltaSession(string caller_trace)\r
+        {\r
+            Assert.True(GetDeltaApi.EndGenDeltaSession(), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void SdbPush(string caller_trace, string hostFullPath, string targetPath)\r
+        {\r
+            System.Diagnostics.Process process = new System.Diagnostics.Process();\r
+            process.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;\r
+            process.StartInfo.RedirectStandardInput = true;\r
+            process.StartInfo.RedirectStandardOutput = true;\r
+            process.StartInfo.CreateNoWindow = true;\r
+            process.StartInfo.UseShellExecute = false;\r
+            \r
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))\r
+            {\r
+                process.StartInfo.FileName = "bash";\r
+                process.StartInfo.Arguments = "-c \"sdb push " + hostFullPath + " " + targetPath + "\"";\r
+            }\r
+            else\r
+                throw new Exception("Host OS not supported. " + @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+\r
+            process.Start();\r
+            process.WaitForExit();\r
+            Assert.Equal(0, process.ExitCode, @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void WriteDeltas(string caller_trace, string fileName)\r
+        {\r
+            string hostPath;\r
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))\r
+                hostPath = Path.Combine(@"/tmp", fileName);\r
+            else\r
+                throw new Exception("Host OS not supported. " + @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+\r
+            string targetPath = @"/tmp";\r
+            Assert.True(GetDeltaApi.WriteDeltas(hostPath), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
+        }\r
+\r
+        public void GetDelta(string caller_trace, string source, string sourceFileName)\r
+        {\r
+            Assert.True(GetDeltaApi.GetDeltas(source, sourceFileName, "", false), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void ErrorGetDelta(string caller_trace, string source, string sourceFileName)\r
+        {\r
+            Assert.False(GetDeltaApi.GetDeltas(source, sourceFileName, "", false), @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public void ApplyDeltas(string caller_trace, string fileName)\r
+        {\r
+            string targetPath = Path.Combine(@"/tmp", fileName);\r
+            string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
+            Assert.Equal(MIResultClass.Done,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
+                         @"__FILE__:__LINE__"+"\n"+caller_trace);\r
+        }\r
+\r
+        public Context(ControlInfo controlInfo, NetcoreDbgTestCore.DebuggerClient debuggerClient)\r
+        {\r
+            ControlInfo = controlInfo;\r
+            MIDebugger = new MIDebugger(debuggerClient);\r
+            GetDeltaApi = new GetDeltaApi.GetDeltaApi();\r
+        }\r
+\r
+        ControlInfo ControlInfo;\r
+        MIDebugger MIDebugger;\r
+        GetDeltaApi.GetDeltaApi GetDeltaApi;\r
+    }\r
+}\r
+\r
+namespace MITestHotReloadPDB\r
+{\r
+    class Program\r
+    {\r
+        static void Main(string[] args)\r
+        {\r
+            Label.Checkpoint("init", "pdb_test1", (Object context) => {\r
+                Context Context = (Context)context;\r
+                Context.CheckHostRuntimeVersion(@"__FILE__:__LINE__");\r
+                Context.CheckHostOS(@"__FILE__:__LINE__");\r
+                Context.Prepare(@"__FILE__:__LINE__");\r
+                Context.WasEntryPointHit("TestAppHotReload", @"__FILE__:__LINE__");\r
+                // Note, target Hot Reload check must be after debuggee process start and stop at entry breakpoint.\r
+                Context.CheckTargetRuntimeVersion(@"__FILE__:__LINE__");\r
+                Context.StartGenDeltaSession(@"__FILE__:__LINE__");\r
+\r
+                Context.GetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                        static void HotReloadTest()\r
+                        {\r
+                            Console.WriteLine(""Updated string."");\r
+                            HotReloadBreakpointTest1();\r
+                            HotReloadBreakpointTest1();\r
+                        }\r
+                        static void HotReloadBreakpointTest1()\r
+                        {\r
+                            Console.WriteLine(""Added string."");\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+                Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta1");\r
+                Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta1");\r
+\r
+\r
+                // Test method move without code changes.\r
+\r
+                Context.GetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        static void HotReloadBreakpointTest1()\r
+                        {\r
+                            Console.WriteLine(""Added string."");\r
+                        }\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                        static void HotReloadTest()\r
+                        {\r
+                            Console.WriteLine(""Updated string."");\r
+                            HotReloadBreakpointTest1();\r
+                            HotReloadBreakpointTest1();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+                Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta2");\r
+                Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta2");\r
+\r
+                // Test syntax error in new code (deltas generation routine test).\r
+\r
+                Context.ErrorGetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        static void HotReloadBreakpointTest1()\r
+                        {\r
+                            sdfdsf\r
+                        }\r
+                        static void HotReloadTest()\r
+                        {\r
+                        }\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+\r
+\r
+                // Test methods delete error in new code (deltas generation routine test).\r
+\r
+                Context.ErrorGetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+\r
+\r
+                // Test method move with code changes.\r
+\r
+                Context.GetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        static void HotReloadBreakpointTest1()\r
+                        {\r
+                            Console.WriteLine(""Added string."");\r
+                        }\r
+\r
+                        static void HotReloadTest()\r
+                        {\r
+                            Console.WriteLine(""Updated string. 2"");\r
+                            HotReloadBreakpointTest1();\r
+                        }\r
+\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+                Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta3");\r
+                Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta3");\r
+\r
+                Context.GetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+                        \r
+                        static void HotReloadTest()\r
+                        {\r
+                            Console.WriteLine(""Updated string. 2"");\r
+                            HotReloadBreakpointTest1();\r
+                        }\r
+\r
+\r
+                        static void HotReloadBreakpointTest1()\r
+                        {\r
+                            Console.WriteLine(""Added string."");\r
+                        }\r
+\r
+\r
+\r
+\r
+\r
+\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+                Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta4");\r
+                Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta4");\r
+\r
+                Context.GetDelta(@"__FILE__:__LINE__",\r
+                @"using System;\r
+                namespace TestAppHotReload\r
+                {\r
+                    class Program\r
+                    {\r
+\r
+\r
+                        static void HotReloadBreakpointTest1()\r
+                        {                                                                                    // line 9\r
+                            Console.WriteLine(""Added string."");\r
+                        }\r
+                        static void HotReloadTest()\r
+                        {                                                                                    // line 14\r
+                            Console.WriteLine(""Updated string. 2"");\r
+                            HotReloadBreakpointTest1();\r
+                        }\r
+                        static void Main(string[] args)\r
+                        {\r
+                            Console.WriteLine(""Hello World! Main updated."");\r
+                            HotReloadTest();\r
+                        }\r
+                    }\r
+                }", @"Program.cs");\r
+                Context.WriteDeltas(@"__FILE__:__LINE__", "tmp_delta5");\r
+                Context.ApplyDeltas(@"__FILE__:__LINE__", "tmp_delta5");\r
+\r
+                Context.EnableBreakpoint(@"__FILE__:__LINE__", @"Program.cs", 9);\r
+                Context.EnableBreakpoint(@"__FILE__:__LINE__", @"Program.cs", 14);\r
+                Context.Continue(@"__FILE__:__LINE__");\r
+            });\r
+\r
+            // Test breakpoints in moved methods.\r
+            Label.Checkpoint("pdb_test1", "finish", (Object context) => {\r
+                Context Context = (Context)context;\r
+                Context.WasBreakpointHit(@"__FILE__:__LINE__", @"Program.cs", 14);\r
+                Context.Continue(@"__FILE__:__LINE__");\r
+                Context.WasBreakpointHit(@"__FILE__:__LINE__", @"Program.cs", 9);\r
+                Context.Continue(@"__FILE__:__LINE__");\r
+            });\r
+\r
+            Label.Checkpoint("finish", "", (Object context) => {\r
+                Context Context = (Context)context;\r
+                Context.EndGenDeltaSession(@"__FILE__:__LINE__");\r
+                Context.WasExit(@"__FILE__:__LINE__");\r
+                Context.DebuggerExit(@"__FILE__:__LINE__");\r
+            });\r
+        }\r
+    }\r
+}\r
index e660e2e5d27202041a13377093d788ed8dc669f9..e4b2bc9f98357d47baa31d42adee279a3e1cf78f 100644 (file)
@@ -284,6 +284,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -296,7 +297,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index d6eb3968570c0286d0d69780e5208507c7982ccb..06dd417b8baa3a10b94336f786b897377f638b5c 100644 (file)
@@ -206,6 +206,7 @@ namespace NetcoreDbgTest.Script
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".metadata", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".il", targetPath);\r
             SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".pdb", targetPath);\r
+            SdbPush(@"__FILE__:__LINE__"+"\n"+caller_trace, hostPath + ".bin", targetPath);\r
         }\r
 \r
         public void GetDelta(string caller_trace, string source, string sourceFileName)\r
@@ -223,7 +224,7 @@ namespace NetcoreDbgTest.Script
             string targetPath = Path.Combine(@"/tmp", fileName);\r
             string targetAssemblyName = Path.GetFileName(ControlInfo.TargetAssemblyPath);\r
             Assert.Equal(MIResultClass.Done,\r
-                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb").Class,\r
+                         MIDebugger.Request("-apply-deltas " + targetAssemblyName + " " + targetPath + ".metadata " + targetPath + ".il " + targetPath + ".pdb " + targetPath + ".bin").Class,\r
                          @"__FILE__:__LINE__"+"\n"+caller_trace);\r
         }\r
 \r
index 8944533aa8ee8f8b3a1fc6ed86a48c0ff9925be9..b7ab65246dafe2bcf5c9ec34ad0571467bed27f5 100644 (file)
@@ -224,12 +224,12 @@ namespace MITestSrcBreakpointResolve
 \r
         public test_constructors()\r
         {\r
-\r
+            int i = 5;     // bp here! make sure you correct code (test constructor)!\r
         }\r
 \r
         public test_constructors(int i)\r
         {\r
-            \r
+            int j = 5;\r
         }\r
     }\r
 \r
@@ -450,11 +450,14 @@ Label.Breakpoint("resolved_bp4");       Console.WriteLine(
                 Context Context = (Context)context;\r
                 Context.WasBreakpointHit(@"__FILE__:__LINE__", "bp23");\r
 \r
-                Context.ManualEnableBreakpoint(@"__FILE__:__LINE__", "Program.cs", 223);\r
+                Context.ManualEnableBreakpoint(@"__FILE__:__LINE__", "Program.cs", 223); // line number with "int test_field = 5;" code\r
+                Context.ManualEnableBreakpoint(@"__FILE__:__LINE__", "Program.cs", 227); // line number with "int i = 5;" code\r
+                Context.Continue(@"__FILE__:__LINE__");\r
+                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 223); // line number with "int test_field = 5;" code\r
                 Context.Continue(@"__FILE__:__LINE__");\r
-                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 223);\r
+                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 227); // line number with "int i = 5;" code\r
                 Context.Continue(@"__FILE__:__LINE__");\r
-                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 223);\r
+                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 223); // line number with "int test_field = 5;" code\r
                 Context.Continue(@"__FILE__:__LINE__");\r
             });\r
 \r
index 7e0434d62999f1a822e67205544e4a7aea2bbb4d..db948d7714049552be18ea7344a0ad2f06eb4eda 100644 (file)
@@ -288,10 +288,12 @@ namespace VSCodeTestSrcBreakpointResolve
 \r
         public test_constructors()\r
         {\r
+            int i = 5;     // bp here! make sure you correct code (test constructor)!\r
         }\r
 \r
         public test_constructors(int i)\r
         {\r
+            int j = 5;\r
         }\r
     }\r
 \r
@@ -534,10 +536,13 @@ Label.Breakpoint("resolved_bp4");       Console.WriteLine(
                 Context.WasBreakpointHit(@"__FILE__:__LINE__", "bp23");\r
 \r
                 Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code\r
+                Context.AddManualBreakpoint(@"__FILE__:__LINE__", "Program.cs", 291); // line number with "int i = 5;" code\r
                 Context.SetBreakpoints(@"__FILE__:__LINE__");\r
                 Context.Continue(@"__FILE__:__LINE__");\r
                 Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code\r
                 Context.Continue(@"__FILE__:__LINE__");\r
+                Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 291); // line number with "int i = 5;" code\r
+                Context.Continue(@"__FILE__:__LINE__");\r
                 Context.WasManualBreakpointHit(@"__FILE__:__LINE__", "Program.cs", 287); // line number with "int test_field = 5;" code\r
                 Context.Continue(@"__FILE__:__LINE__");\r
             });\r
index 451d464eeff740745010e4b3ffb74c54082cddc9..9a4f5cecea037054883b1429053016c45386cf3c 100755 (executable)
@@ -65,6 +65,7 @@ ALL_TEST_NAMES=(
     "MITestHotReloadJMC"
     "MITestHotReloadWithoutBreak"
     "MITestGeneric"
+    "MITestHotReloadPDB"
     "VSCodeExampleTest"
     "VSCodeTestBreakpoint"
     "VSCodeTestFuncBreak"