From: Igor Kulaychuk Date: Fri, 5 Oct 2018 17:21:47 +0000 (+0300) Subject: Conditional breakpoints: add implementation and test X-Git-Tag: submit/tizen/20181014.073209~6 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=ad8e4ca49a531fd53425160073de1728485d2579;p=sdk%2Ftools%2Fnetcoredbg.git Conditional breakpoints: add implementation and test Signed-off-by: Alexander Aksenov --- diff --git a/src/debug/netcoredbg/breakpoints.cpp b/src/debug/netcoredbg/breakpoints.cpp index fe0c2e0..fae365d 100644 --- a/src/debug/netcoredbg/breakpoints.cpp +++ b/src/debug/netcoredbg/breakpoints.cpp @@ -23,12 +23,14 @@ void Breakpoints::ManagedBreakpoint::ToBreakpoint(Breakpoint &breakpoint) { breakpoint.id = this->id; breakpoint.verified = this->IsResolved(); + breakpoint.condition = this->condition; breakpoint.source = Source(this->fullname); breakpoint.line = this->linenum; breakpoint.hitCount = this->times; } HRESULT Breakpoints::HitBreakpoint( + Debugger *debugger, ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint, Breakpoint &breakpoint, @@ -74,6 +76,19 @@ HRESULT Breakpoints::HitBreakpoint( b.methodToken == methodToken && b.enabled) { + if (!b.condition.empty()) + { + DWORD threadId = 0; + IfFailRet(pThread->GetID(&threadId)); + uint64_t frameId = StackFrame(threadId, 0, "").id; + + Variable variable; + std::string output; + IfFailRet(debugger->Evaluate(frameId, b.condition, variable, output)); + + if (variable.type != "bool" || variable.value != "true") + return E_FAIL; + } ++b.times; b.ToBreakpoint(breakpoint); return S_OK; @@ -354,21 +369,21 @@ HRESULT Breakpoints::ResolveBreakpoint(ManagedBreakpoint &bp) HRESULT ManagedDebugger::SetBreakpoints( std::string filename, - const std::vector &lines, + const std::vector &srcBreakpoints, std::vector &breakpoints) { - return m_breakpoints.SetBreakpoints(m_pProcess, filename, lines, breakpoints); + return m_breakpoints.SetBreakpoints(m_pProcess, filename, srcBreakpoints, breakpoints); } HRESULT Breakpoints::SetBreakpoints( ICorDebugProcess *pProcess, std::string filename, - const std::vector &lines, + const std::vector &srcBreakpoints, std::vector &breakpoints) { std::lock_guard lock(m_breakpointsMutex); - if (lines.empty()) + if (srcBreakpoints.empty()) { auto it = m_breakpoints.find(filename); if (it != m_breakpoints.end()) @@ -380,8 +395,9 @@ HRESULT Breakpoints::SetBreakpoints( // Remove old breakpoints std::unordered_set unchangedLines; - for (int line : lines) + for (const auto &sb : srcBreakpoints) { + int line = sb.line; if (breakpointsInSource.find(line) != breakpointsInSource.end()) unchangedLines.insert(line); } @@ -396,8 +412,9 @@ HRESULT Breakpoints::SetBreakpoints( // Export breakpoints - for (int line : lines) + for (const auto &sb : srcBreakpoints) { + int line = sb.line; Breakpoint breakpoint; auto b = breakpointsInSource.find(line); @@ -408,6 +425,7 @@ HRESULT Breakpoints::SetBreakpoints( bp.id = m_nextBreakpointId++; bp.fullname = filename; bp.linenum = line; + bp.condition = sb.condition; if (pProcess) ResolveBreakpoint(bp); @@ -418,7 +436,9 @@ HRESULT Breakpoints::SetBreakpoints( else { // Existing breakpoint - b->second.ToBreakpoint(breakpoint); + ManagedBreakpoint &bp = b->second; + bp.condition = sb.condition; + bp.ToBreakpoint(breakpoint); } breakpoints.push_back(breakpoint); diff --git a/src/debug/netcoredbg/debugger.h b/src/debug/netcoredbg/debugger.h index f5a3154..5b41070 100644 --- a/src/debug/netcoredbg/debugger.h +++ b/src/debug/netcoredbg/debugger.h @@ -49,7 +49,7 @@ public: virtual HRESULT Continue() = 0; virtual HRESULT Pause() = 0; virtual HRESULT GetThreads(std::vector &threads) = 0; - virtual HRESULT SetBreakpoints(std::string filename, const std::vector &lines, std::vector &breakpoints) = 0; + virtual HRESULT SetBreakpoints(std::string filename, const std::vector &srcBreakpoints, std::vector &breakpoints) = 0; virtual void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) = 0; virtual HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector &stackFrames, int &totalFrames) = 0; virtual HRESULT StepCommand(int threadId, StepType stepType) = 0; diff --git a/src/debug/netcoredbg/manageddebugger.cpp b/src/debug/netcoredbg/manageddebugger.cpp index e756e09..82e7767 100644 --- a/src/debug/netcoredbg/manageddebugger.cpp +++ b/src/debug/netcoredbg/manageddebugger.cpp @@ -339,26 +339,44 @@ public: return S_OK; } - DWORD threadId = 0; - pThread->GetID(&threadId); - - bool atEntry = false; - StoppedEvent event(StopBreakpoint, threadId); - if (FAILED(m_debugger.m_breakpoints.HitBreakpoint(pThread, pBreakpoint, event.breakpoint, atEntry))) + ToRelease callbackAppDomain(pAppDomain); + pAppDomain->AddRef(); + ToRelease callbackThread(pThread); + pThread->AddRef(); + ToRelease callbackBreakpoint(pBreakpoint); + pBreakpoint->AddRef(); + + std::thread([this]( + ICorDebugAppDomain *pAppDomain, + ICorDebugThread *pThread, + ICorDebugBreakpoint *pBreakpoint) { - pAppDomain->Continue(0); - return S_OK; - } + DWORD threadId = 0; + pThread->GetID(&threadId); + + bool atEntry = false; + StoppedEvent event(StopBreakpoint, threadId); + if (FAILED(m_debugger.m_breakpoints.HitBreakpoint(&m_debugger, pThread, pBreakpoint, event.breakpoint, atEntry))) + { + pAppDomain->Continue(0); + return; + } - if (atEntry) - event.reason = StopEntry; + if (atEntry) + event.reason = StopEntry; - ToRelease pFrame; - if (SUCCEEDED(pThread->GetActiveFrame(&pFrame)) && pFrame != nullptr) - m_debugger.GetFrameLocation(pFrame, threadId, 0, event.frame); + ToRelease pFrame; + if (SUCCEEDED(pThread->GetActiveFrame(&pFrame)) && pFrame != nullptr) + m_debugger.GetFrameLocation(pFrame, threadId, 0, event.frame); + + m_debugger.SetLastStoppedThread(pThread); + m_debugger.m_protocol->EmitStoppedEvent(event); - m_debugger.SetLastStoppedThread(pThread); - m_debugger.m_protocol->EmitStoppedEvent(event); + }, + std::move(callbackAppDomain), + std::move(callbackThread), + std::move(callbackBreakpoint) + ).detach(); return S_OK; } diff --git a/src/debug/netcoredbg/manageddebugger.h b/src/debug/netcoredbg/manageddebugger.h index 88e1689..75b1189 100644 --- a/src/debug/netcoredbg/manageddebugger.h +++ b/src/debug/netcoredbg/manageddebugger.h @@ -184,6 +184,7 @@ class Breakpoints ToRelease breakpoint; bool enabled; ULONG32 times; + std::string condition; bool IsResolved() const { return modAddress != 0; } @@ -216,6 +217,7 @@ public: m_modules(modules), m_nextBreakpointId(1), m_stopAtEntry(false), m_entryPoint(mdMethodDefNil) {} HRESULT HitBreakpoint( + Debugger *debugger, ICorDebugThread *pThread, ICorDebugBreakpoint *pBreakpoint, Breakpoint &breakpoint, @@ -230,7 +232,7 @@ public: HRESULT SetBreakpoints( ICorDebugProcess *pProcess, std::string filename, - const std::vector &lines, + const std::vector &srcBreakpoints, std::vector &breakpoints); void SetStopAtEntry(bool stopAtEntry); @@ -471,7 +473,7 @@ public: HRESULT Continue() override; HRESULT Pause() override; HRESULT GetThreads(std::vector &threads) override; - HRESULT SetBreakpoints(std::string filename, const std::vector &lines, std::vector &breakpoints) override; + HRESULT SetBreakpoints(std::string filename, const std::vector &srcBreakpoints, std::vector &breakpoints) override; void InsertExceptionBreakpoint(const std::string &name, Breakpoint &breakpoint) override; HRESULT GetStackTrace(int threadId, int startFrame, int levels, std::vector &stackFrames, int &totalFrames) override; HRESULT StepCommand(int threadId, StepType stepType) override; diff --git a/src/debug/netcoredbg/miprotocol.cpp b/src/debug/netcoredbg/miprotocol.cpp index 9047b1e..6243f2f 100644 --- a/src/debug/netcoredbg/miprotocol.cpp +++ b/src/debug/netcoredbg/miprotocol.cpp @@ -88,7 +88,11 @@ static bool GetIndices(const std::vector &args, int &index1, int &i return true; } -bool ParseBreakpoint(const std::vector &args_orig, std::string &filename, unsigned int &linenum) +bool ParseBreakpoint( + const std::vector &args_orig, + std::string &filename, + unsigned int &linenum, + std::string &condition) { std::vector args = args_orig; StripArgs(args); @@ -103,6 +107,14 @@ bool ParseBreakpoint(const std::vector &args_orig, std::string &fil return false; } + if (args.at(0) == "-c") + { + if (args.size() < 3) + return false; + condition = args.at(1); + args.erase(args.begin(), args.begin() + 2); + } + std::size_t i = args.at(0).rfind(':'); if (i == std::string::npos) @@ -415,27 +427,31 @@ HRESULT MIProtocol::ListChildren(int threadId, int level, int childStart, int ch return S_OK; } -HRESULT MIProtocol::SetBreakpoint(const std::string &filename, int linenum, Breakpoint &breakpoint) +HRESULT MIProtocol::SetBreakpoint( + const std::string &filename, + int linenum, + const std::string &condition, + Breakpoint &breakpoint) { HRESULT Status; auto &breakpointsInSource = m_breakpoints[filename]; - std::vector lines; + std::vector srcBreakpoints; for (auto it : breakpointsInSource) { - lines.push_back(it.second); + srcBreakpoints.push_back(it.second); } - lines.push_back(linenum); + srcBreakpoints.emplace_back(linenum, condition); std::vector breakpoints; - IfFailRet(m_debugger->SetBreakpoints(filename, lines, breakpoints)); + IfFailRet(m_debugger->SetBreakpoints(filename, srcBreakpoints, breakpoints)); for (const Breakpoint& b : breakpoints) { if (b.line != linenum) continue; - breakpointsInSource.insert(std::make_pair(b.id, b.line)); + breakpointsInSource.insert(std::make_pair(b.id, SourceBreakpoint{ b.line, b.condition })); breakpoint = b; return S_OK; } @@ -443,23 +459,54 @@ HRESULT MIProtocol::SetBreakpoint(const std::string &filename, int linenum, Brea return E_FAIL; } +HRESULT MIProtocol::SetBreakpointCondition(uint32_t id, const std::string &condition) +{ + HRESULT Status; + + // For each file + for (auto &breakpointsIter : m_breakpoints) + { + std::unordered_map &fileBreakpoints = breakpointsIter.second; + + // Find breakpoint with specified id in this file + const auto &sbIter = fileBreakpoints.find(id); + if (sbIter == fileBreakpoints.end()) + continue; + + // Modify breakpoint condition + sbIter->second.condition = condition; + + // Gather all breakpoints in this file + std::vector existingBreakpoints; + for (const auto &it : fileBreakpoints) + existingBreakpoints.emplace_back(it.second); + + // Update breakpoints data for this file + const std::string &filename = breakpointsIter.first; + std::vector tmpBreakpoints; + return m_debugger->SetBreakpoints(filename, existingBreakpoints, tmpBreakpoints); + } + + return E_FAIL; +} + void MIProtocol::DeleteBreakpoints(const std::unordered_set &ids) { for (auto &breakpointsIter : m_breakpoints) { - std::vector remainingLines; + std::vector remainingBreakpoints; for (auto it : breakpointsIter.second) { if (ids.find(it.first) == ids.end()) - remainingLines.push_back(it.second); + remainingBreakpoints.push_back(it.second); } - if (remainingLines.size() == breakpointsIter.second.size()) + if (remainingBreakpoints.size() == breakpointsIter.second.size()) continue; std::string filename = breakpointsIter.first; std::vector tmpBreakpoints; - m_debugger->SetBreakpoints(filename, remainingLines, tmpBreakpoints); + m_debugger->SetBreakpoints(filename, remainingBreakpoints, tmpBreakpoints); } } @@ -613,9 +660,10 @@ HRESULT MIProtocol::HandleCommand(std::string command, { "break-insert", [this](const std::vector &args, std::string &output) -> HRESULT { std::string filename; unsigned int linenum; + std::string condition; Breakpoint breakpoint; - if (ParseBreakpoint(args, filename, linenum) - && SUCCEEDED(SetBreakpoint(filename, linenum, breakpoint))) + if (ParseBreakpoint(args, filename, linenum, condition) + && SUCCEEDED(SetBreakpoint(filename, linenum, condition, breakpoint))) { PrintBreakpoint(breakpoint, output); return S_OK; @@ -636,6 +684,25 @@ HRESULT MIProtocol::HandleCommand(std::string command, DeleteBreakpoints(ids); return S_OK; } }, + { "break-condition", [this](const std::vector &args, std::string &output) -> HRESULT { + HRESULT Status; + + if (args.size() < 2) + { + output = "Command requires at least 2 arguments"; + return E_FAIL; + } + + bool ok; + int id = ParseInt(args.at(0), ok); + if (!ok) + { + output = "Unknown breakpoint id"; + return E_FAIL; + } + + return SetBreakpointCondition(id, args.at(1)); + } }, { "exec-step", [this](const std::vector &args, std::string &output) -> HRESULT { return StepCommand(args, output, Debugger::STEP_IN); }}, @@ -985,8 +1052,15 @@ static bool ParseLine(const std::string &str, if (cmd == "var-assign") { tokenizer.Next(result); - args.push_back(result); - args.push_back(tokenizer.Remain()); + args.push_back(result); // name + args.push_back(tokenizer.Remain()); // expression + return true; + } + else if (cmd == "break-condition") + { + tokenizer.Next(result); + args.push_back(result); // id + args.push_back(tokenizer.Remain()); // expression return true; } diff --git a/src/debug/netcoredbg/miprotocol.h b/src/debug/netcoredbg/miprotocol.h index 684a240..8c99db4 100644 --- a/src/debug/netcoredbg/miprotocol.h +++ b/src/debug/netcoredbg/miprotocol.h @@ -21,7 +21,7 @@ class MIProtocol : public Protocol unsigned int m_varCounter; std::unordered_map m_vars; - std::unordered_map > m_breakpoints; + std::unordered_map > m_breakpoints; static std::string EscapeMIValue(const std::string &str); static HRESULT PrintBreakpoint(const Breakpoint &b, std::string &output); @@ -71,7 +71,8 @@ private: void PrintChildren(std::vector &children, int threadId, int print_values, bool has_more, std::string &output); void PrintNewVar(std::string varobjName, Variable &v, int threadId, int print_values, std::string &output); HRESULT ListChildren(int threadId, int level, int childStart, int childEnd, const std::string &varName, int print_values, std::string &output); - HRESULT SetBreakpoint(const std::string &filename, int linenum, Breakpoint &breakpoints); + HRESULT SetBreakpoint(const std::string &filename, int linenum, const std::string &condition, Breakpoint &breakpoints); + HRESULT SetBreakpointCondition(uint32_t id, const std::string &condition); void DeleteBreakpoints(const std::unordered_set &ids); static HRESULT PrintFrameLocation(const StackFrame &stackFrame, std::string &output); }; diff --git a/src/debug/netcoredbg/protocol.h b/src/debug/netcoredbg/protocol.h index d73c139..608c6dd 100644 --- a/src/debug/netcoredbg/protocol.h +++ b/src/debug/netcoredbg/protocol.h @@ -74,6 +74,7 @@ struct Breakpoint uint32_t id; bool verified; std::string message; + std::string condition; Source source; int line; @@ -241,3 +242,11 @@ enum VariablesFilter VariablesIndexed, VariablesBoth }; + +struct SourceBreakpoint +{ + int line; + std::string condition; + + SourceBreakpoint(int linenum, const std::string &cond = std::string()) : line(linenum), condition(cond) {} +}; diff --git a/src/debug/netcoredbg/vscodeprotocol.cpp b/src/debug/netcoredbg/vscodeprotocol.cpp index 9957348..f126564 100644 --- a/src/debug/netcoredbg/vscodeprotocol.cpp +++ b/src/debug/netcoredbg/vscodeprotocol.cpp @@ -258,6 +258,7 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar m_debugger->Initialize(); body["supportsConfigurationDoneRequest"] = true; body["supportTerminateDebuggee"] = true; + body["supportsConditionalBreakpoints"] = true; return S_OK; } }, { "configurationDone", [this](const json &arguments, json &body){ @@ -266,12 +267,12 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar { "setBreakpoints", [this](const json &arguments, json &body){ HRESULT Status; - std::vector lines; + std::vector srcBreakpoints; for (auto &b : arguments.at("breakpoints")) - lines.push_back(b.at("line")); + srcBreakpoints.emplace_back(b.at("line"), b.value("condition", std::string())); std::vector breakpoints; - IfFailRet(m_debugger->SetBreakpoints(arguments.at("source").at("path"), lines, breakpoints)); + IfFailRet(m_debugger->SetBreakpoints(arguments.at("source").at("path"), srcBreakpoints, breakpoints)); body["breakpoints"] = breakpoints; diff --git a/tests/BreakpointAddRemoveTest/BreakpointAddRemoveTest.cs b/tests/BreakpointAddRemoveTest/BreakpointAddRemoveTest.cs index d09b823..c3ceb87 100644 --- a/tests/BreakpointAddRemoveTest/BreakpointAddRemoveTest.cs +++ b/tests/BreakpointAddRemoveTest/BreakpointAddRemoveTest.cs @@ -13,12 +13,18 @@ Send(String.Format("4-break-insert -f {0}:{1}", filename, Lines["BREAK2"])); r = Expect("4^done"); int id2 = r.Find("bkpt").FindInt("number"); -Send("5-exec-run"); +Send(String.Format("5-break-insert -f -c \"x>20\" {0}:{1}", filename, Lines["BREAK3"])); +r = Expect("5^done"); +int id3 = r.Find("bkpt").FindInt("number"); + +Send("-exec-run"); */ using System; namespace BreakpointAddRemoveTest { + + class Program { static void Main(string[] args) @@ -39,12 +45,29 @@ Assert.Equal(id1, r.FindInt("bkptno")); Send(String.Format("7-break-delete {0}", id2)); Expect("7^done"); - Send("8-exec-continue"); +*/ + TestFunc(10); + TestFunc(21); + TestFunc(9); + + Console.WriteLine("Hello World!"); // //@BREAK2@ + } + + static void TestFunc(int x) + { + x++; // //@BREAK3@ +/* r = Expect("*stopped"); -Assert.Equal("exited", r.FindString("reason")); +Assert.Equal("breakpoint-hit", r.FindString("reason")); +Assert.Equal(Lines["BREAK3"], r.Find("frame").FindInt("line")); +Assert.Equal(id3, r.FindInt("bkptno")); +Send("9-exec-continue"); */ - Console.WriteLine("Hello World!"); // //@BREAK2@ } } +/* +r = Expect("*stopped"); +Assert.Equal("exited", r.FindString("reason")); +*/ }