[Lldb-commits] [lldb] 8c6e138 - Support logpoints in lldb-vscode
Jeffrey Tan via lldb-commits
lldb-commits at lists.llvm.org
Mon Jun 20 16:22:34 PDT 2022
Author: Jeffrey Tan
Date: 2022-06-20T16:22:12-07:00
New Revision: 8c6e138aa893bb88fc3d5d449e42082741f0e2a2
URL: https://github.com/llvm/llvm-project/commit/8c6e138aa893bb88fc3d5d449e42082741f0e2a2
DIFF: https://github.com/llvm/llvm-project/commit/8c6e138aa893bb88fc3d5d449e42082741f0e2a2.diff
LOG: Support logpoints in lldb-vscode
This patch implements VSCode DAP logpoints feature (also called tracepoint
in other VS debugger).
This will provide a convenient way for user to do printf style logging
debugging without pausing debuggee.
Differential Revision: https://reviews.llvm.org/D127702
Added:
lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_logpoints.py
Modified:
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py
lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp
lldb/tools/lldb-vscode/BreakpointBase.cpp
lldb/tools/lldb-vscode/BreakpointBase.h
lldb/tools/lldb-vscode/FunctionBreakpoint.cpp
lldb/tools/lldb-vscode/SourceBreakpoint.cpp
lldb/tools/lldb-vscode/lldb-vscode.cpp
Removed:
################################################################################
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
index b0fb17ffa9719..90d90d959592d 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/lldbvscode_testcase.py
@@ -22,13 +22,15 @@ def build_and_create_debug_adaptor(self, lldbVSCodeEnv=None):
self.build()
self.create_debug_adaptor(lldbVSCodeEnv)
- def set_source_breakpoints(self, source_path, lines, condition=None,
- hitCondition=None):
+ def set_source_breakpoints(self, source_path, lines, data=None):
'''Sets source breakpoints and returns an array of strings containing
the breakpoint IDs ("1", "2") for each breakpoint that was set.
+ Parameter data is array of data objects for breakpoints.
+ Each object in data is 1:1 mapping with the entry in lines.
+ It contains optional location/hitCondition/logMessage parameters.
'''
response = self.vscode.request_setBreakpoints(
- source_path, lines, condition=condition, hitCondition=hitCondition)
+ source_path, lines, data)
if response is None:
return []
breakpoints = response['body']['breakpoints']
diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
index 603b1545cd714..8a871732f7a61 100644
--- a/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
+++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-vscode/vscode.py
@@ -750,8 +750,11 @@ def request_scopes(self, frameId):
}
return self.send_recv(command_dict)
- def request_setBreakpoints(self, file_path, line_array, condition=None,
- hitCondition=None):
+ def request_setBreakpoints(self, file_path, line_array, data=None):
+ ''' data is array of parameters for breakpoints in line_array.
+ Each parameter object is 1:1 mapping with entries in line_entry.
+ It contains optional location/hitCondition/logMessage parameters.
+ '''
(dir, base) = os.path.split(file_path)
source_dict = {
'name': base,
@@ -764,12 +767,18 @@ def request_setBreakpoints(self, file_path, line_array, condition=None,
if line_array is not None:
args_dict['lines'] = '%s' % line_array
breakpoints = []
- for line in line_array:
+ for i, line in enumerate(line_array):
+ breakpoint_data = None
+ if data is not None and i < len(data):
+ breakpoint_data = data[i]
bp = {'line': line}
- if condition is not None:
- bp['condition'] = condition
- if hitCondition is not None:
- bp['hitCondition'] = hitCondition
+ if breakpoint_data is not None:
+ if 'condition' in breakpoint_data and breakpoint_data['condition']:
+ bp['condition'] = breakpoint_data['condition']
+ if 'hitCondition' in breakpoint_data and breakpoint_data['hitCondition']:
+ bp['hitCondition'] = breakpoint_data['hitCondition']
+ if 'logMessage' in breakpoint_data and breakpoint_data['logMessage']:
+ bp['logMessage'] = breakpoint_data['logMessage']
breakpoints.append(bp)
args_dict['breakpoints'] = breakpoints
diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_logpoints.py b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_logpoints.py
new file mode 100644
index 0000000000000..a68ca040c0cf2
--- /dev/null
+++ b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_logpoints.py
@@ -0,0 +1,143 @@
+"""
+Test lldb-vscode logpoints feature.
+"""
+
+
+import unittest2
+import vscode
+import shutil
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+import lldbvscode_testcase
+import os
+
+
+class TestVSCode_logpoints(lldbvscode_testcase.VSCodeTestCaseBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ def setUp(self):
+ lldbvscode_testcase.VSCodeTestCaseBase.setUp(self)
+
+ self.main_basename = 'main-copy.cpp'
+ self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))
+
+ @skipIfWindows
+ @skipIfRemote
+ def test_logmessage_basic(self):
+ '''Tests breakpoint logmessage basic functionality.'''
+ before_loop_line = line_number('main.cpp', '// before loop')
+ loop_line = line_number('main.cpp', '// break loop')
+ after_loop_line = line_number('main.cpp', '// after loop')
+
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program)
+
+ # Set a breakpoint at a line before loop
+ before_loop_breakpoint_ids = self.set_source_breakpoints(
+ self.main_path,
+ [before_loop_line])
+ self.assertEquals(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")
+
+ self.vscode.request_continue()
+
+ # Verify we hit the breakpoint before loop line
+ self.verify_breakpoint_hit(before_loop_breakpoint_ids)
+
+ # Swallow old console output
+ self.get_console()
+
+ # Set two breakpoints:
+ # 1. First at the loop line with logMessage
+ # 2. Second guard breakpoint at a line after loop
+ logMessage_prefix = "This is log message for { -- "
+ # Trailing newline is needed for splitlines()
+ logMessage = logMessage_prefix + "{i + 3}\n"
+ [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
+ self.main_path,
+ [loop_line, after_loop_line],
+ [{'logMessage': logMessage}, {}]
+ )
+
+ # Continue to trigger the breakpoint with log messages
+ self.vscode.request_continue()
+
+ # Verify we hit the breakpoint after loop line
+ self.verify_breakpoint_hit([post_loop_breakpoint_id])
+
+ output = self.get_console()
+ lines = output.splitlines()
+ logMessage_output = []
+ for line in lines:
+ if line.startswith(logMessage_prefix):
+ logMessage_output.append(line)
+
+ # Verify logMessage count
+ loop_count = 10
+ self.assertEqual(len(logMessage_output), loop_count)
+
+ # Verify log message match
+ for idx, logMessage_line in enumerate(logMessage_output):
+ result = idx + 3
+ self.assertEqual(logMessage_line, logMessage_prefix + str(result))
+
+
+ @skipIfWindows
+ @skipIfRemote
+ def test_logmessage_advanced(self):
+ '''Tests breakpoint logmessage functionality for complex expression.'''
+ before_loop_line = line_number('main.cpp', '// before loop')
+ loop_line = line_number('main.cpp', '// break loop')
+ after_loop_line = line_number('main.cpp', '// after loop')
+
+ program = self.getBuildArtifact("a.out")
+ self.build_and_launch(program)
+
+ # Set a breakpoint at a line before loop
+ before_loop_breakpoint_ids = self.set_source_breakpoints(
+ self.main_path,
+ [before_loop_line])
+ self.assertEquals(len(before_loop_breakpoint_ids), 1, "expect one breakpoint")
+
+ self.vscode.request_continue()
+
+ # Verify we hit the breakpoint before loop line
+ self.verify_breakpoint_hit(before_loop_breakpoint_ids)
+
+ # Swallow old console output
+ self.get_console()
+
+ # Set two breakpoints:
+ # 1. First at the loop line with logMessage
+ # 2. Second guard breakpoint at a line after loop
+ logMessage_prefix = "This is log message for { -- "
+ # Trailing newline is needed for splitlines()
+ logMessage = logMessage_prefix + "{int y = 0; if (i % 3 == 0) { y = i + 3;} else {y = i * 3;} y}\n"
+ [loop_breakpoint_id, post_loop_breakpoint_id] = self.set_source_breakpoints(
+ self.main_path,
+ [loop_line, after_loop_line],
+ [{'logMessage': logMessage}, {}]
+ )
+
+ # Continue to trigger the breakpoint with log messages
+ self.vscode.request_continue()
+
+ # Verify we hit the breakpoint after loop line
+ self.verify_breakpoint_hit([post_loop_breakpoint_id])
+
+ output = self.get_console()
+ lines = output.splitlines()
+ logMessage_output = []
+ for line in lines:
+ if line.startswith(logMessage_prefix):
+ logMessage_output.append(line)
+
+ # Verify logMessage count
+ loop_count = 10
+ self.assertEqual(len(logMessage_output), loop_count)
+
+ # Verify log message match
+ for idx, logMessage_line in enumerate(logMessage_output):
+ result = idx + 3 if idx % 3 == 0 else idx * 3
+ self.assertEqual(logMessage_line, logMessage_prefix + str(result))
diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py
index a6269f4840251..1daba44541745 100644
--- a/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py
+++ b/lldb/test/API/tools/lldb-vscode/breakpoint/TestVSCode_setBreakpoints.py
@@ -300,7 +300,7 @@ def test_functionality(self):
# Update the condition on our breakpoint
new_breakpoint_ids = self.set_source_breakpoints(self.main_path,
[loop_line],
- condition="i==4")
+ [{'condition': "i==4"}])
self.assertEquals(breakpoint_ids, new_breakpoint_ids,
"existing breakpoint should have its condition "
"updated")
@@ -312,7 +312,7 @@ def test_functionality(self):
new_breakpoint_ids = self.set_source_breakpoints(self.main_path,
[loop_line],
- hitCondition="2")
+ [{'hitCondition':"2"}])
self.assertEquals(breakpoint_ids, new_breakpoint_ids,
"existing breakpoint should have its condition "
diff --git a/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp b/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp
index 04deb9482c5fb..d4e0ac26dd11a 100644
--- a/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp
+++ b/lldb/test/API/tools/lldb-vscode/breakpoint/main.cpp
@@ -33,7 +33,7 @@ int main(int argc, char const *argv[]) {
fprintf(stderr, "%s\n", dlerror());
exit(2);
}
- foo(12);
+ foo(12); // before loop
for (int i=0; i<10; ++i) {
int x = twelve(i) + thirteen(i) + a::fourteen(i); // break loop
@@ -43,5 +43,5 @@ int main(int argc, char const *argv[]) {
} catch (...) {
puts("caught exception...");
}
- return 0;
+ return 0; // after loop
}
diff --git a/lldb/tools/lldb-vscode/BreakpointBase.cpp b/lldb/tools/lldb-vscode/BreakpointBase.cpp
index 5013fcbb7927c..14d279dfd6601 100644
--- a/lldb/tools/lldb-vscode/BreakpointBase.cpp
+++ b/lldb/tools/lldb-vscode/BreakpointBase.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "BreakpointBase.h"
+#include "VSCode.h"
#include "llvm/ADT/StringExtras.h"
using namespace lldb_vscode;
@@ -24,6 +25,126 @@ void BreakpointBase::SetHitCondition() {
bp.SetIgnoreCount(hitCount - 1);
}
+// logMessage will be divided into array of LogMessagePart as two kinds:
+// 1. raw print text message, and
+// 2. interpolated expression for evaluation which is inside matching curly
+// braces.
+//
+// The function tries to parse logMessage into a list of LogMessageParts
+// for easy later access in BreakpointHitCallback.
+void BreakpointBase::SetLogMessage() {
+ logMessageParts.clear();
+
+ // Contains unmatched open curly braces indices.
+ std::vector<int> unmatched_curly_braces;
+
+ // Contains all matched curly braces in logMessage.
+ // Loop invariant: matched_curly_braces_ranges are sorted by start index in
+ // ascending order without any overlap between them.
+ std::vector<std::pair<int, int>> matched_curly_braces_ranges;
+
+ // Part1 - parse matched_curly_braces_ranges.
+ // locating all curly braced expression ranges in logMessage.
+ // The algorithm takes care of nested and imbalanced curly braces.
+ for (size_t i = 0; i < logMessage.size(); ++i) {
+ if (logMessage[i] == '{') {
+ unmatched_curly_braces.push_back(i);
+ } else if (logMessage[i] == '}') {
+ if (unmatched_curly_braces.empty())
+ // Nothing to match.
+ continue;
+
+ int last_unmatched_index = unmatched_curly_braces.back();
+ unmatched_curly_braces.pop_back();
+
+ // Erase any matched ranges included in the new match.
+ while (!matched_curly_braces_ranges.empty()) {
+ assert(matched_curly_braces_ranges.back().first !=
+ last_unmatched_index &&
+ "How can a curley brace be matched twice?");
+ if (matched_curly_braces_ranges.back().first < last_unmatched_index)
+ break;
+
+ // This is a nested range let's earse it.
+ assert((size_t)matched_curly_braces_ranges.back().second < i);
+ matched_curly_braces_ranges.pop_back();
+ }
+
+ // Assert invariant.
+ assert(matched_curly_braces_ranges.empty() ||
+ matched_curly_braces_ranges.back().first < last_unmatched_index);
+ matched_curly_braces_ranges.emplace_back(last_unmatched_index, i);
+ }
+ }
+
+ // Part2 - parse raw text and expresions parts.
+ // All expression ranges have been parsed in matched_curly_braces_ranges.
+ // The code below uses matched_curly_braces_ranges to divide logMessage
+ // into raw text parts and expression parts.
+ int last_raw_text_start = 0;
+ for (const std::pair<int, int> &curly_braces_range :
+ matched_curly_braces_ranges) {
+ // Raw text before open curly brace.
+ assert(curly_braces_range.first >= last_raw_text_start);
+ size_t raw_text_len = curly_braces_range.first - last_raw_text_start;
+ if (raw_text_len > 0)
+ logMessageParts.emplace_back(
+ llvm::StringRef(logMessage.c_str() + last_raw_text_start,
+ raw_text_len),
+ /*is_expr=*/false);
+
+ // Expression between curly braces.
+ assert(curly_braces_range.second > curly_braces_range.first);
+ size_t expr_len = curly_braces_range.second - curly_braces_range.first - 1;
+ logMessageParts.emplace_back(
+ llvm::StringRef(logMessage.c_str() + curly_braces_range.first + 1,
+ expr_len),
+ /*is_expr=*/true);
+ last_raw_text_start = curly_braces_range.second + 1;
+ }
+ // Trailing raw text after close curly brace.
+ if (logMessage.size() > last_raw_text_start)
+ logMessageParts.emplace_back(
+ llvm::StringRef(logMessage.c_str() + last_raw_text_start,
+ logMessage.size() - last_raw_text_start),
+ /*is_expr=*/false);
+ bp.SetCallback(BreakpointBase::BreakpointHitCallback, this);
+}
+
+/*static*/
+bool BreakpointBase::BreakpointHitCallback(
+ void *baton, lldb::SBProcess &process, lldb::SBThread &thread,
+ lldb::SBBreakpointLocation &location) {
+ if (!baton)
+ return true;
+
+ BreakpointBase *bp = (BreakpointBase *)baton;
+ lldb::SBFrame frame = thread.GetSelectedFrame();
+
+ std::string output;
+ for (const BreakpointBase::LogMessagePart &messagePart :
+ bp->logMessageParts) {
+ if (messagePart.is_expr) {
+ // Try local frame variables first before fall back to expression
+ // evaluation
+ const char *expr = messagePart.text.str().c_str();
+ lldb::SBValue value =
+ frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget);
+ if (value.GetError().Fail())
+ value = frame.EvaluateExpression(expr);
+ const char *expr_val = value.GetValue();
+ if (expr_val)
+ output += expr_val;
+ } else {
+ output += messagePart.text.str();
+ }
+ }
+ g_vsc.SendOutput(OutputType::Console, output.c_str());
+
+ // Do not stop.
+ return false;
+}
+
void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) {
if (condition != request_bp.condition) {
condition = request_bp.condition;
@@ -33,6 +154,10 @@ void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) {
hitCondition = request_bp.hitCondition;
SetHitCondition();
}
+ if (logMessage != request_bp.logMessage) {
+ logMessage = request_bp.logMessage;
+ SetLogMessage();
+ }
}
const char *BreakpointBase::GetBreakpointLabel() {
diff --git a/lldb/tools/lldb-vscode/BreakpointBase.h b/lldb/tools/lldb-vscode/BreakpointBase.h
index eb1e466e2444a..321c8adfbfb99 100644
--- a/lldb/tools/lldb-vscode/BreakpointBase.h
+++ b/lldb/tools/lldb-vscode/BreakpointBase.h
@@ -13,11 +13,16 @@
#include "lldb/API/SBBreakpoint.h"
#include "llvm/Support/JSON.h"
#include <string>
+#include <vector>
namespace lldb_vscode {
struct BreakpointBase {
-
+ // logMessage part can be either a raw text or an expression.
+ struct LogMessagePart {
+ llvm::StringRef text;
+ bool is_expr;
+ };
// An optional expression for conditional breakpoints.
std::string condition;
// An optional expression that controls how many hits of the breakpoint are
@@ -27,6 +32,7 @@ struct BreakpointBase {
// (stop) but log the message instead. Expressions within {} are
// interpolated.
std::string logMessage;
+ std::vector<LogMessagePart> logMessageParts;
// The LLDB breakpoint associated wit this source breakpoint
lldb::SBBreakpoint bp;
@@ -35,8 +41,12 @@ struct BreakpointBase {
void SetCondition();
void SetHitCondition();
+ void SetLogMessage();
void UpdateBreakpoint(const BreakpointBase &request_bp);
static const char *GetBreakpointLabel();
+ static bool BreakpointHitCallback(void *baton, lldb::SBProcess &process,
+ lldb::SBThread &thread,
+ lldb::SBBreakpointLocation &location);
};
} // namespace lldb_vscode
diff --git a/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp
index e7720a1bc849c..b7a8705d854c8 100644
--- a/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp
+++ b/lldb/tools/lldb-vscode/FunctionBreakpoint.cpp
@@ -25,6 +25,8 @@ void FunctionBreakpoint::SetBreakpoint() {
SetCondition();
if (!hitCondition.empty())
SetHitCondition();
+ if (!logMessage.empty())
+ SetLogMessage();
}
} // namespace lldb_vscode
diff --git a/lldb/tools/lldb-vscode/SourceBreakpoint.cpp b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp
index be93c2e21ea43..742d6142d3234 100644
--- a/lldb/tools/lldb-vscode/SourceBreakpoint.cpp
+++ b/lldb/tools/lldb-vscode/SourceBreakpoint.cpp
@@ -24,6 +24,8 @@ void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) {
SetCondition();
if (!hitCondition.empty())
SetHitCondition();
+ if (!logMessage.empty())
+ SetLogMessage();
}
} // namespace lldb_vscode
diff --git a/lldb/tools/lldb-vscode/lldb-vscode.cpp b/lldb/tools/lldb-vscode/lldb-vscode.cpp
index 869c125ec0340..6c7118890f2f7 100644
--- a/lldb/tools/lldb-vscode/lldb-vscode.cpp
+++ b/lldb/tools/lldb-vscode/lldb-vscode.cpp
@@ -1532,6 +1532,8 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsLoadedSourcesRequest", false);
// The debug adapter supports sending progress reporting events.
body.try_emplace("supportsProgressReporting", true);
+ // The debug adapter supports 'logMessage' in breakpoint.
+ body.try_emplace("supportsLogPoints", true);
response.try_emplace("body", std::move(body));
g_vsc.SendJSON(llvm::json::Value(std::move(response)));
@@ -2079,9 +2081,10 @@ void request_setBreakpoints(const llvm::json::Object &request) {
}
}
// At this point the breakpoint is new
- src_bp.SetBreakpoint(path.data());
- AppendBreakpoint(src_bp.bp, response_breakpoints, path, src_bp.line);
- g_vsc.source_breakpoints[path][src_bp.line] = std::move(src_bp);
+ g_vsc.source_breakpoints[path][src_bp.line] = src_bp;
+ SourceBreakpoint &new_bp = g_vsc.source_breakpoints[path][src_bp.line];
+ new_bp.SetBreakpoint(path.data());
+ AppendBreakpoint(new_bp.bp, response_breakpoints, path, new_bp.line);
}
}
}
@@ -2304,10 +2307,11 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) {
// Any breakpoints that are left in "request_bps" are breakpoints that
// need to be set.
for (auto &pair : request_bps) {
- pair.second.SetBreakpoint();
// Add this breakpoint info to the response
- AppendBreakpoint(pair.second.bp, response_breakpoints);
g_vsc.function_breakpoints[pair.first()] = std::move(pair.second);
+ FunctionBreakpoint &new_bp = g_vsc.function_breakpoints[pair.first()];
+ new_bp.SetBreakpoint();
+ AppendBreakpoint(new_bp.bp, response_breakpoints);
}
llvm::json::Object body;
More information about the lldb-commits
mailing list