diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py index 63748a71f1122..aaa1c93098b02 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py @@ -612,6 +612,28 @@ def request_attach( command_dict = {"command": "attach", "type": "request", "arguments": args_dict} return self.send_recv(command_dict) + def request_breakpointLocations( + self, file_path, line, end_line=None, column=None, end_column=None + ): + (dir, base) = os.path.split(file_path) + source_dict = {"name": base, "path": file_path} + args_dict = {} + args_dict["source"] = source_dict + if line is not None: + args_dict["line"] = line + if end_line is not None: + args_dict["endLine"] = end_line + if column is not None: + args_dict["column"] = column + if end_column is not None: + args_dict["endColumn"] = end_column + command_dict = { + "command": "breakpointLocations", + "type": "request", + "arguments": args_dict, + } + return self.send_recv(command_dict) + def request_configurationDone(self): command_dict = { "command": "configurationDone", @@ -852,6 +874,8 @@ def request_next(self, threadId, granularity="statement"): def request_stepIn(self, threadId, targetId, granularity="statement"): if self.exit_status is not None: raise ValueError("request_stepIn called after process exited") + if threadId is None: + threadId = self.get_thread_id() args_dict = { "threadId": threadId, "targetId": targetId, @@ -912,18 +936,14 @@ def request_setBreakpoints(self, file_path, line_array, data=None): breakpoint_data = data[i] bp = {"line": line} if breakpoint_data is not None: - if "condition" in breakpoint_data and breakpoint_data["condition"]: + if breakpoint_data.get("condition"): bp["condition"] = breakpoint_data["condition"] - if ( - "hitCondition" in breakpoint_data - and breakpoint_data["hitCondition"] - ): + if breakpoint_data.get("hitCondition"): bp["hitCondition"] = breakpoint_data["hitCondition"] - if ( - "logMessage" in breakpoint_data - and breakpoint_data["logMessage"] - ): + if breakpoint_data.get("logMessage"): bp["logMessage"] = breakpoint_data["logMessage"] + if breakpoint_data.get("column"): + bp["column"] = breakpoint_data["column"] breakpoints.append(bp) args_dict["breakpoints"] = breakpoints diff --git a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py index 7e80912be4464..d9080332483f3 100644 --- a/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py +++ b/lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py @@ -238,9 +238,10 @@ def set_global(self, name, value, id=None): def stepIn( self, threadId=None, targetId=None, waitForStop=True, granularity="statement" ): - self.dap_server.request_stepIn( + response = self.dap_server.request_stepIn( threadId=threadId, targetId=targetId, granularity=granularity ) + self.assertTrue(response["success"]) if waitForStop: return self.dap_server.wait_for_stopped() return None diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/Makefile b/lldb/test/API/tools/lldb-dap/breakpoint/Makefile index 7634f513e8523..06438b3e6e313 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/Makefile +++ b/lldb/test/API/tools/lldb-dap/breakpoint/Makefile @@ -16,4 +16,4 @@ main-copy.cpp: main.cpp # The following shared library will be used to test breakpoints under dynamic loading libother: other-copy.c "$(MAKE)" -f $(MAKEFILE_RULES) \ - DYLIB_ONLY=YES DYLIB_C_SOURCES=other-copy.c DYLIB_NAME=other + DYLIB_ONLY=YES DYLIB_C_SOURCES=other-copy.c DYLIB_NAME=other diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py new file mode 100644 index 0000000000000..1058157e2c668 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_breakpointLocations.py @@ -0,0 +1,88 @@ +""" +Test lldb-dap breakpointLocations request +""" + + +import dap_server +import shutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil +import lldbdap_testcase +import os + + +class TestDAP_breakpointLocations(lldbdap_testcase.DAPTestCaseBase): + def setUp(self): + lldbdap_testcase.DAPTestCaseBase.setUp(self) + + self.main_basename = "main-copy.cpp" + self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename)) + + @skipIfWindows + def test_column_breakpoints(self): + """Test retrieving the available breakpoint locations.""" + program = self.getBuildArtifact("a.out") + self.build_and_launch(program, stopOnEntry=True) + loop_line = line_number(self.main_path, "// break loop") + self.dap_server.request_continue() + + # Ask for the breakpoint locations based only on the line number + response = self.dap_server.request_breakpointLocations( + self.main_path, loop_line + ) + self.assertTrue(response["success"]) + self.assertEqual( + response["body"]["breakpoints"], + [ + {"line": loop_line, "column": 9}, + {"line": loop_line, "column": 13}, + {"line": loop_line, "column": 20}, + {"line": loop_line, "column": 23}, + {"line": loop_line, "column": 25}, + {"line": loop_line, "column": 34}, + {"line": loop_line, "column": 37}, + {"line": loop_line, "column": 39}, + {"line": loop_line, "column": 51}, + ], + ) + + # Ask for the breakpoint locations for a column range + response = self.dap_server.request_breakpointLocations( + self.main_path, + loop_line, + column=24, + end_column=46, + ) + self.assertTrue(response["success"]) + self.assertEqual( + response["body"]["breakpoints"], + [ + {"line": loop_line, "column": 25}, + {"line": loop_line, "column": 34}, + {"line": loop_line, "column": 37}, + {"line": loop_line, "column": 39}, + ], + ) + + # Ask for the breakpoint locations for a range of line numbers + response = self.dap_server.request_breakpointLocations( + self.main_path, + line=loop_line, + end_line=loop_line + 2, + column=39, + ) + self.maxDiff = None + self.assertTrue(response["success"]) + # On some systems, there is an additional breakpoint available + # at line 41, column 3, i.e. at the end of the loop. To make this + # test more portable, only check that all expected breakpoints are + # presented, but also accept additional breakpoints. + expected_breakpoints = [ + {"column": 39, "line": 40}, + {"column": 51, "line": 40}, + {"column": 3, "line": 42}, + {"column": 18, "line": 42}, + ] + for bp in expected_breakpoints: + self.assertIn(bp, response["body"]["breakpoints"]) diff --git a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py index 123fea79c5cda..c62feda64a125 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py @@ -125,20 +125,18 @@ def test_set_and_clear(self): # Set 3 breakpoints and verify that they got set correctly response = self.dap_server.request_setBreakpoints(self.main_path, lines) line_to_id = {} - if response: - breakpoints = response["body"]["breakpoints"] - self.assertEqual( - len(breakpoints), - len(lines), - "expect %u source breakpoints" % (len(lines)), - ) - for breakpoint, index in zip(breakpoints, range(len(lines))): - line = breakpoint["line"] - self.assertTrue(line, lines[index]) - # Store the "id" of the breakpoint that was set for later - line_to_id[line] = breakpoint["id"] - self.assertIn(line, lines, "line expected in lines array") - self.assertTrue(breakpoint["verified"], "expect breakpoint verified") + breakpoints = response["body"]["breakpoints"] + self.assertEqual( + len(breakpoints), + len(lines), + "expect %u source breakpoints" % (len(lines)), + ) + for index, breakpoint in enumerate(breakpoints): + line = breakpoint["line"] + self.assertEqual(line, lines[index]) + # Store the "id" of the breakpoint that was set for later + line_to_id[line] = breakpoint["id"] + self.assertTrue(breakpoint["verified"], "expect breakpoint verified") # There is no breakpoint delete packet, clients just send another # setBreakpoints packet with the same source file with fewer lines. @@ -151,75 +149,66 @@ def test_set_and_clear(self): # Set 2 breakpoints and verify that the previous breakpoints that were # set above are still set. response = self.dap_server.request_setBreakpoints(self.main_path, lines) - if response: - breakpoints = response["body"]["breakpoints"] + breakpoints = response["body"]["breakpoints"] + self.assertEqual( + len(breakpoints), + len(lines), + "expect %u source breakpoints" % (len(lines)), + ) + for index, breakpoint in enumerate(breakpoints): + line = breakpoint["line"] + self.assertEqual(line, lines[index]) + # Verify the same breakpoints are still set within LLDB by + # making sure the breakpoint ID didn't change self.assertEqual( - len(breakpoints), - len(lines), - "expect %u source breakpoints" % (len(lines)), + line_to_id[line], + breakpoint["id"], + "verify previous breakpoints stayed the same", ) - for breakpoint, index in zip(breakpoints, range(len(lines))): - line = breakpoint["line"] - self.assertTrue(line, lines[index]) - # Verify the same breakpoints are still set within LLDB by - # making sure the breakpoint ID didn't change - self.assertEqual( - line_to_id[line], - breakpoint["id"], - "verify previous breakpoints stayed the same", - ) - self.assertIn(line, lines, "line expected in lines array") - self.assertTrue( - breakpoint["verified"], "expect breakpoint still verified" - ) + self.assertTrue(breakpoint["verified"], "expect breakpoint still verified") # Now get the full list of breakpoints set in the target and verify # we have only 2 breakpoints set. The response above could have told # us about 2 breakpoints, but we want to make sure we don't have the # third one still set in the target response = self.dap_server.request_testGetTargetBreakpoints() - if response: - breakpoints = response["body"]["breakpoints"] + breakpoints = response["body"]["breakpoints"] + self.assertEqual( + len(breakpoints), + len(lines), + "expect %u source breakpoints" % (len(lines)), + ) + for breakpoint in breakpoints: + line = breakpoint["line"] + # Verify the same breakpoints are still set within LLDB by + # making sure the breakpoint ID didn't change self.assertEqual( - len(breakpoints), - len(lines), - "expect %u source breakpoints" % (len(lines)), + line_to_id[line], + breakpoint["id"], + "verify previous breakpoints stayed the same", ) - for breakpoint in breakpoints: - line = breakpoint["line"] - # Verify the same breakpoints are still set within LLDB by - # making sure the breakpoint ID didn't change - self.assertEqual( - line_to_id[line], - breakpoint["id"], - "verify previous breakpoints stayed the same", - ) - self.assertIn(line, lines, "line expected in lines array") - self.assertTrue( - breakpoint["verified"], "expect breakpoint still verified" - ) + self.assertIn(line, lines, "line expected in lines array") + self.assertTrue(breakpoint["verified"], "expect breakpoint still verified") # Now clear all breakpoints for the source file by passing down an # empty lines array lines = [] response = self.dap_server.request_setBreakpoints(self.main_path, lines) - if response: - breakpoints = response["body"]["breakpoints"] - self.assertEqual( - len(breakpoints), - len(lines), - "expect %u source breakpoints" % (len(lines)), - ) + breakpoints = response["body"]["breakpoints"] + self.assertEqual( + len(breakpoints), + len(lines), + "expect %u source breakpoints" % (len(lines)), + ) # Verify with the target that all breakpoints have been cleared response = self.dap_server.request_testGetTargetBreakpoints() - if response: - breakpoints = response["body"]["breakpoints"] - self.assertEqual( - len(breakpoints), - len(lines), - "expect %u source breakpoints" % (len(lines)), - ) + breakpoints = response["body"]["breakpoints"] + self.assertEqual( + len(breakpoints), + len(lines), + "expect %u source breakpoints" % (len(lines)), + ) # Now set a breakpoint again in the same source file and verify it # was added. @@ -281,12 +270,11 @@ def test_clear_breakpoints_unset_breakpoints(self): self.assertEqual( len(breakpoints), len(lines), "expect %u source breakpoints" % (len(lines)) ) - for breakpoint, index in zip(breakpoints, range(len(lines))): + for index, breakpoint in enumerate(breakpoints): line = breakpoint["line"] - self.assertTrue(line, lines[index]) + self.assertEqual(line, lines[index]) # Store the "id" of the breakpoint that was set for later line_to_id[line] = breakpoint["id"] - self.assertIn(line, lines, "line expected in lines array") self.assertTrue(breakpoint["verified"], "expect breakpoint verified") # Now clear all breakpoints for the source file by not setting the @@ -356,3 +344,49 @@ def test_functionality(self): self.continue_to_breakpoints(breakpoint_ids) i = int(self.dap_server.get_local_variable_value("i")) self.assertEqual(i, 7, "i != 7 showing post hitCondition hits every time") + + @skipIfWindows + def test_column_breakpoints(self): + """Test setting multiple breakpoints in the same line at different columns.""" + loop_line = line_number("main.cpp", "// break loop") + + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Set two breakpoints on the loop line at different columns. + columns = [13, 39] + response = self.dap_server.request_setBreakpoints( + self.main_path, [loop_line, loop_line], list({"column": c} for c in columns) + ) + + # Verify the breakpoints were set correctly + breakpoints = response["body"]["breakpoints"] + breakpoint_ids = [] + self.assertEqual( + len(breakpoints), + len(columns), + "expect %u source breakpoints" % (len(columns)), + ) + for index, breakpoint in enumerate(breakpoints): + self.assertEqual(breakpoint["line"], loop_line) + self.assertEqual(breakpoint["column"], columns[index]) + self.assertTrue(breakpoint["verified"], "expect breakpoint verified") + breakpoint_ids.append(breakpoint["id"]) + + # Continue to the first breakpoint, + self.continue_to_breakpoints([breakpoint_ids[0]]) + + # We should have stopped right before the call to `twelve`. + # Step into and check we are inside `twelve`. + self.stepIn() + func_name = self.get_stackFrames()[0]["name"] + self.assertEqual(func_name, "twelve(int)") + + # Continue to the second breakpoint. + self.continue_to_breakpoints([breakpoint_ids[1]]) + + # We should have stopped right before the call to `fourteen`. + # Step into and check we are inside `fourteen`. + self.stepIn() + func_name = self.get_stackFrames()[0]["name"] + self.assertEqual(func_name, "a::fourteen(int)") diff --git a/lldb/test/API/tools/lldb-dap/output/TestDAP_output.py b/lldb/test/API/tools/lldb-dap/output/TestDAP_output.py index 02c34ba10321b..eae7629409844 100644 --- a/lldb/test/API/tools/lldb-dap/output/TestDAP_output.py +++ b/lldb/test/API/tools/lldb-dap/output/TestDAP_output.py @@ -10,23 +10,33 @@ class TestDAP_output(lldbdap_testcase.DAPTestCaseBase): @skipIfWindows def test_output(self): + """ + Test output handling for the running process. + """ program = self.getBuildArtifact("a.out") - self.build_and_launch(program) + self.build_and_launch( + program, + exitCommands=[ + # Ensure that output produced by lldb itself is not consumed by the OutputRedirector. + "?script print('out\\0\\0', end='\\r\\n', file=sys.stdout)", + "?script print('err\\0\\0', end='\\r\\n', file=sys.stderr)", + ], + ) source = "main.c" lines = [line_number(source, "// breakpoint 1")] breakpoint_ids = self.set_source_breakpoints(source, lines) self.continue_to_breakpoints(breakpoint_ids) - + # Ensure partial messages are still sent. output = self.collect_stdout(timeout_secs=1.0, pattern="abcdef") - self.assertTrue(output and len(output) > 0, "expect no program output") + self.assertTrue(output and len(output) > 0, "expect program stdout") self.continue_to_exit() - + output += self.get_stdout(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval) - self.assertTrue(output and len(output) > 0, "expect no program output") + self.assertTrue(output and len(output) > 0, "expect program stdout") self.assertIn( - "abcdefghi\r\nhello world\r\n", + "abcdefghi\r\nhello world\r\nfinally\0\0out\0\0\r\nerr\0\0", output, - 'full output not found in: ' + output, + "full stdout not found in: " + repr(output), ) diff --git a/lldb/test/API/tools/lldb-dap/output/main.c b/lldb/test/API/tools/lldb-dap/output/main.c index 0cfcf604aa68f..226cb607a1234 100644 --- a/lldb/test/API/tools/lldb-dap/output/main.c +++ b/lldb/test/API/tools/lldb-dap/output/main.c @@ -8,5 +8,8 @@ int main() { printf("def"); printf("ghi\n"); printf("hello world\n"); // breakpoint 1 + // Ensure the OutputRedirector does not consume the programs \0\0 output. + char buf[] = "finally\0"; + write(STDOUT_FILENO, buf, sizeof(buf)); return 0; } diff --git a/lldb/tools/lldb-dap/Breakpoint.cpp b/lldb/tools/lldb-dap/Breakpoint.cpp index 0c33d4b114d76..b3bfa61595a82 100644 --- a/lldb/tools/lldb-dap/Breakpoint.cpp +++ b/lldb/tools/lldb-dap/Breakpoint.cpp @@ -7,9 +7,15 @@ //===----------------------------------------------------------------------===// #include "Breakpoint.h" -#include "DAP.h" #include "JSONUtils.h" +#include "lldb/API/SBAddress.h" +#include "lldb/API/SBBreakpointLocation.h" +#include "lldb/API/SBLineEntry.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/JSON.h" +#include +#include +#include using namespace lldb_dap; @@ -50,7 +56,7 @@ void Breakpoint::CreateJsonObject(llvm::json::Object &object) { if (bp_addr.IsValid()) { std::string formatted_addr = - "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(g_dap.target)); + "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(bp.GetTarget())); object.try_emplace("instructionReference", formatted_addr); auto line_entry = bp_addr.GetLineEntry(); const auto line = line_entry.GetLine(); diff --git a/lldb/tools/lldb-dap/Breakpoint.h b/lldb/tools/lldb-dap/Breakpoint.h index 47a9d9c59ae2b..a726f27e59ee0 100644 --- a/lldb/tools/lldb-dap/Breakpoint.h +++ b/lldb/tools/lldb-dap/Breakpoint.h @@ -10,6 +10,8 @@ #define LLDB_TOOLS_LLDB_DAP_BREAKPOINT_H #include "BreakpointBase.h" +#include "DAPForward.h" +#include "lldb/API/SBBreakpoint.h" namespace lldb_dap { @@ -17,9 +19,8 @@ struct Breakpoint : public BreakpointBase { // The LLDB breakpoint associated wit this source breakpoint lldb::SBBreakpoint bp; - Breakpoint() = default; - Breakpoint(const llvm::json::Object &obj) : BreakpointBase(obj){}; - Breakpoint(lldb::SBBreakpoint bp) : bp(bp) {} + Breakpoint(DAP &d, const llvm::json::Object &obj) : BreakpointBase(d, obj) {} + Breakpoint(DAP &d, lldb::SBBreakpoint bp) : BreakpointBase(d), bp(bp) {} void SetCondition() override; void SetHitCondition() override; diff --git a/lldb/tools/lldb-dap/BreakpointBase.cpp b/lldb/tools/lldb-dap/BreakpointBase.cpp index 519729f5519ff..1e28c29082a9f 100644 --- a/lldb/tools/lldb-dap/BreakpointBase.cpp +++ b/lldb/tools/lldb-dap/BreakpointBase.cpp @@ -7,13 +7,13 @@ //===----------------------------------------------------------------------===// #include "BreakpointBase.h" -#include "DAP.h" -#include "llvm/ADT/StringExtras.h" +#include "JSONUtils.h" +#include "llvm/ADT/StringRef.h" using namespace lldb_dap; -BreakpointBase::BreakpointBase(const llvm::json::Object &obj) - : condition(std::string(GetString(obj, "condition"))), +BreakpointBase::BreakpointBase(DAP &d, const llvm::json::Object &obj) + : dap(d), condition(std::string(GetString(obj, "condition"))), hitCondition(std::string(GetString(obj, "hitCondition"))) {} void BreakpointBase::UpdateBreakpoint(const BreakpointBase &request_bp) { diff --git a/lldb/tools/lldb-dap/BreakpointBase.h b/lldb/tools/lldb-dap/BreakpointBase.h index 5a04bb201615f..3c248dd1736d0 100644 --- a/lldb/tools/lldb-dap/BreakpointBase.h +++ b/lldb/tools/lldb-dap/BreakpointBase.h @@ -9,14 +9,14 @@ #ifndef LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H #define LLDB_TOOLS_LLDB_DAP_BREAKPOINTBASE_H -#include "lldb/API/SBBreakpoint.h" -#include "llvm/Support/JSON.h" +#include "DAPForward.h" #include -#include namespace lldb_dap { struct BreakpointBase { + // Associated DAP session. + DAP &dap; // An optional expression for conditional breakpoints. std::string condition; @@ -24,8 +24,8 @@ struct BreakpointBase { // ignored. The backend is expected to interpret the expression as needed std::string hitCondition; - BreakpointBase() = default; - BreakpointBase(const llvm::json::Object &obj); + explicit BreakpointBase(DAP &d) : dap(d) {} + BreakpointBase(DAP &d, const llvm::json::Object &obj); virtual ~BreakpointBase() = default; virtual void SetCondition() = 0; diff --git a/lldb/tools/lldb-dap/CMakeLists.txt b/lldb/tools/lldb-dap/CMakeLists.txt index d68098bf7b326..43fc18873feb3 100644 --- a/lldb/tools/lldb-dap/CMakeLists.txt +++ b/lldb/tools/lldb-dap/CMakeLists.txt @@ -1,7 +1,3 @@ -if ( CMAKE_SYSTEM_NAME MATCHES "Windows" OR CMAKE_SYSTEM_NAME MATCHES "NetBSD" ) - list(APPEND extra_libs lldbHost) -endif () - if (HAVE_LIBPTHREAD) list(APPEND extra_libs pthread) endif () @@ -26,9 +22,11 @@ add_lldb_tool(lldb-dap lldb-dap.cpp Breakpoint.cpp BreakpointBase.cpp + DAP.cpp ExceptionBreakpoint.cpp FifoFiles.cpp FunctionBreakpoint.cpp + InstructionBreakpoint.cpp IOStream.cpp JSONUtils.cpp LLDBUtils.cpp @@ -36,12 +34,11 @@ add_lldb_tool(lldb-dap ProgressEvent.cpp RunInTerminal.cpp SourceBreakpoint.cpp - DAP.cpp Watchpoint.cpp - InstructionBreakpoint.cpp LINK_LIBS liblldb + lldbHost ${extra_libs} LINK_COMPONENTS diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 282d01a0ddbfd..f05f52a25a827 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -6,34 +6,64 @@ // //===----------------------------------------------------------------------===// -#include -#include -#include -#include -#include - #include "DAP.h" +#include "JSONUtils.h" #include "LLDBUtils.h" +#include "OutputRedirector.h" +#include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBCommandReturnObject.h" +#include "lldb/API/SBLanguageRuntime.h" +#include "lldb/API/SBListener.h" +#include "lldb/API/SBProcess.h" +#include "lldb/API/SBStream.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Utility/Status.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-enumerations.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include +#include +#include #if defined(_WIN32) #define NOMINMAX #include #include #include +#else +#include #endif using namespace lldb_dap; -namespace lldb_dap { +namespace { +#ifdef _WIN32 +const char DEV_NULL[] = "nul"; +#else +const char DEV_NULL[] = "/dev/null"; +#endif +} // namespace -DAP g_dap; +namespace lldb_dap { -DAP::DAP() - : broadcaster("lldb-dap"), exception_breakpoints(), - focus_tid(LLDB_INVALID_THREAD_ID), stop_at_entry(false), is_attach(false), +DAP::DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode, + StreamDescriptor input, StreamDescriptor output) + : debug_adaptor_path(path), log(log), input(std::move(input)), + output(std::move(output)), broadcaster("lldb-dap"), + exception_breakpoints(), focus_tid(LLDB_INVALID_THREAD_ID), + stop_at_entry(false), is_attach(false), enable_auto_variable_summaries(false), enable_synthetic_child_debugging(false), display_extended_backtrace(false), @@ -41,21 +71,7 @@ DAP::DAP() configuration_done_sent(false), waiting_for_run_in_terminal(false), progress_event_reporter( [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }), - reverse_request_seq(0), repl_mode(ReplMode::Auto) { - const char *log_file_path = getenv("LLDBDAP_LOG"); -#if defined(_WIN32) - // Windows opens stdout and stdin in text mode which converts \n to 13,10 - // while the value is just 10 on Darwin/Linux. Setting the file mode to binary - // fixes this. - int result = _setmode(fileno(stdout), _O_BINARY); - assert(result); - result = _setmode(fileno(stdin), _O_BINARY); - UNUSED_IF_ASSERT_DISABLED(result); - assert(result); -#endif - if (log_file_path) - log.reset(new std::ofstream(log_file_path)); -} + reverse_request_seq(0), repl_mode(repl_mode) {} DAP::~DAP() = default; @@ -71,21 +87,21 @@ void DAP::PopulateExceptionBreakpoints() { exception_breakpoints = std::vector{}; if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) { - exception_breakpoints->emplace_back("cpp_catch", "C++ Catch", + exception_breakpoints->emplace_back(*this, "cpp_catch", "C++ Catch", lldb::eLanguageTypeC_plus_plus); - exception_breakpoints->emplace_back("cpp_throw", "C++ Throw", + exception_breakpoints->emplace_back(*this, "cpp_throw", "C++ Throw", lldb::eLanguageTypeC_plus_plus); } if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) { - exception_breakpoints->emplace_back("objc_catch", "Objective-C Catch", - lldb::eLanguageTypeObjC); - exception_breakpoints->emplace_back("objc_throw", "Objective-C Throw", - lldb::eLanguageTypeObjC); + exception_breakpoints->emplace_back( + *this, "objc_catch", "Objective-C Catch", lldb::eLanguageTypeObjC); + exception_breakpoints->emplace_back( + *this, "objc_throw", "Objective-C Throw", lldb::eLanguageTypeObjC); } if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) { - exception_breakpoints->emplace_back("swift_catch", "Swift Catch", + exception_breakpoints->emplace_back(*this, "swift_catch", "Swift Catch", lldb::eLanguageTypeSwift); - exception_breakpoints->emplace_back("swift_throw", "Swift Throw", + exception_breakpoints->emplace_back(*this, "swift_throw", "Swift Throw", lldb::eLanguageTypeSwift); } // Besides handling the hardcoded list of languages from above, we try to @@ -116,7 +132,7 @@ void DAP::PopulateExceptionBreakpoints() { raw_throw_keyword ? raw_throw_keyword : "throw"; exception_breakpoints->emplace_back( - raw_lang_name + "_" + throw_keyword, + *this, raw_lang_name + "_" + throw_keyword, capitalized_lang_name + " " + capitalize(throw_keyword), lang); } @@ -127,7 +143,7 @@ void DAP::PopulateExceptionBreakpoints() { raw_catch_keyword ? raw_catch_keyword : "catch"; exception_breakpoints->emplace_back( - raw_lang_name + "_" + catch_keyword, + *this, raw_lang_name + "_" + catch_keyword, capitalized_lang_name + " " + capitalize(catch_keyword), lang); } } @@ -171,6 +187,45 @@ ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) { return nullptr; } +llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) { + in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true); + + if (auto Error = out.RedirectTo([this](llvm::StringRef output) { + SendOutput(OutputType::Stdout, output); + })) + return Error; + + if (overrideOut) { + auto fd = out.GetWriteFileDescriptor(); + if (auto Error = fd.takeError()) + return Error; + + if (dup2(*fd, fileno(overrideOut)) == -1) + return llvm::errorCodeToError(llvm::errnoAsErrorCode()); + } + + if (auto Error = err.RedirectTo([this](llvm::StringRef output) { + SendOutput(OutputType::Stderr, output); + })) + return Error; + + if (overrideErr) { + auto fd = err.GetWriteFileDescriptor(); + if (auto Error = fd.takeError()) + return Error; + + if (dup2(*fd, fileno(overrideErr)) == -1) + return llvm::errorCodeToError(llvm::errnoAsErrorCode()); + } + + return llvm::Error::success(); +} + +void DAP::StopIO() { + out.Stop(); + err.Stop(); +} + // Send the JSON in "json_str" to the "out" stream. Correctly send the // "Content-Length:" field followed by the length, followed by the raw // JSON bytes. @@ -207,19 +262,19 @@ std::string DAP::ReadJSON() { std::string json_str; int length; - if (!input.read_expected(log.get(), "Content-Length: ")) + if (!input.read_expected(log, "Content-Length: ")) return json_str; - if (!input.read_line(log.get(), length_str)) + if (!input.read_line(log, length_str)) return json_str; if (!llvm::to_integer(length_str, length)) return json_str; - if (!input.read_expected(log.get(), "\r\n")) + if (!input.read_expected(log, "\r\n")) return json_str; - if (!input.read_full(log.get(), length, json_str)) + if (!input.read_full(log, length, json_str)) return json_str; if (log) { @@ -475,12 +530,12 @@ lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) { llvm::json::Value DAP::CreateTopLevelScopes() { llvm::json::Array scopes; - scopes.emplace_back(CreateScope("Locals", VARREF_LOCALS, - g_dap.variables.locals.GetSize(), false)); + scopes.emplace_back( + CreateScope("Locals", VARREF_LOCALS, variables.locals.GetSize(), false)); scopes.emplace_back(CreateScope("Globals", VARREF_GLOBALS, - g_dap.variables.globals.GetSize(), false)); + variables.globals.GetSize(), false)); scopes.emplace_back(CreateScope("Registers", VARREF_REGS, - g_dap.variables.registers.GetSize(), false)); + variables.registers.GetSize(), false)); return llvm::json::Value(std::move(scopes)); } @@ -488,8 +543,8 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression, bool partial_expression) { // Check for the escape hatch prefix. if (!expression.empty() && - llvm::StringRef(expression).starts_with(g_dap.command_escape_prefix)) { - expression = expression.substr(g_dap.command_escape_prefix.size()); + llvm::StringRef(expression).starts_with(command_escape_prefix)) { + expression = expression.substr(command_escape_prefix.size()); return ReplMode::Command; } @@ -529,7 +584,7 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame frame, std::string &expression, << "Warning: Expression '" << term << "' is both an LLDB command and variable. It will be evaluated as " "a variable. To evaluate the expression as an LLDB command, use '" - << g_dap.command_escape_prefix << "' as a prefix.\n"; + << command_escape_prefix << "' as a prefix.\n"; } // Variables take preference to commands in auto, since commands can always @@ -546,7 +601,7 @@ bool DAP::RunLLDBCommands(llvm::StringRef prefix, llvm::ArrayRef commands) { bool required_command_failed = false; std::string output = - ::RunLLDBCommands(prefix, commands, required_command_failed); + ::RunLLDBCommands(debugger, prefix, commands, required_command_failed); SendOutput(OutputType::Console, output); return !required_command_failed; } @@ -691,16 +746,16 @@ bool DAP::HandleObject(const llvm::json::Object &object) { const auto packet_type = GetString(object, "type"); if (packet_type == "request") { const auto command = GetString(object, "command"); - auto handler_pos = request_handlers.find(std::string(command)); - if (handler_pos != request_handlers.end()) { - handler_pos->second(object); - return true; // Success - } else { + auto handler_pos = request_handlers.find(command); + if (handler_pos == request_handlers.end()) { if (log) *log << "error: unhandled command \"" << command.data() << "\"" << std::endl; return false; // Fail } + + handler_pos->second(*this, object); + return true; // Success } if (packet_type == "response") { @@ -900,7 +955,7 @@ bool StartDebuggingRequestHandler::DoExecute( return false; } - g_dap.SendReverseRequest( + dap.SendReverseRequest( "startDebugging", llvm::json::Object{{"request", request}, {"configuration", std::move(*configuration)}}, @@ -924,7 +979,7 @@ bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger, // If a new mode is not specified report the current mode. if (!command || llvm::StringRef(command[0]).empty()) { std::string mode; - switch (g_dap.repl_mode) { + switch (dap.repl_mode) { case ReplMode::Variable: mode = "variable"; break; @@ -945,11 +1000,11 @@ bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger, llvm::StringRef new_mode{command[0]}; if (new_mode == "variable") { - g_dap.repl_mode = ReplMode::Variable; + dap.repl_mode = ReplMode::Variable; } else if (new_mode == "command") { - g_dap.repl_mode = ReplMode::Command; + dap.repl_mode = ReplMode::Command; } else if (new_mode == "auto") { - g_dap.repl_mode = ReplMode::Auto; + dap.repl_mode = ReplMode::Auto; } else { lldb::SBStream error_message; error_message.Printf("Invalid repl-mode '%s'. Expected one of 'variable', " @@ -1021,7 +1076,7 @@ bool SendEventRequestHandler::DoExecute(lldb::SBDebugger debugger, event.try_emplace("body", std::move(*body)); } - g_dap.SendJSON(llvm::json::Value(std::move(event))); + dap.SendJSON(llvm::json::Value(std::move(event))); result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); return true; } @@ -1030,14 +1085,13 @@ void DAP::SetFrameFormat(llvm::StringRef format) { if (format.empty()) return; lldb::SBError error; - g_dap.frame_format = lldb::SBFormat(format.str().c_str(), error); + frame_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { - g_dap.SendOutput( - OutputType::Console, - llvm::formatv( - "The provided frame format '{0}' couldn't be parsed: {1}\n", format, - error.GetCString()) - .str()); + SendOutput(OutputType::Console, + llvm::formatv( + "The provided frame format '{0}' couldn't be parsed: {1}\n", + format, error.GetCString()) + .str()); } } @@ -1045,21 +1099,20 @@ void DAP::SetThreadFormat(llvm::StringRef format) { if (format.empty()) return; lldb::SBError error; - g_dap.thread_format = lldb::SBFormat(format.str().c_str(), error); + thread_format = lldb::SBFormat(format.str().c_str(), error); if (error.Fail()) { - g_dap.SendOutput( - OutputType::Console, - llvm::formatv( - "The provided thread format '{0}' couldn't be parsed: {1}\n", - format, error.GetCString()) - .str()); + SendOutput(OutputType::Console, + llvm::formatv( + "The provided thread format '{0}' couldn't be parsed: {1}\n", + format, error.GetCString()) + .str()); } } InstructionBreakpoint * DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) { for (auto &bp : instruction_breakpoints) { - if (bp.second.id == bp_id) + if (bp.second.bp.GetID() == bp_id) return &bp.second; } return nullptr; diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index acc10ade75fd1..b23be68ea002f 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -9,55 +9,38 @@ #ifndef LLDB_TOOLS_LLDB_DAP_DAP_H #define LLDB_TOOLS_LLDB_DAP_DAP_H -#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/Threading.h" -#include "llvm/Support/raw_ostream.h" - -#include "lldb/API/SBAttachInfo.h" -#include "lldb/API/SBBreakpoint.h" -#include "lldb/API/SBBreakpointLocation.h" -#include "lldb/API/SBCommandInterpreter.h" -#include "lldb/API/SBCommandReturnObject.h" -#include "lldb/API/SBCommunication.h" -#include "lldb/API/SBDebugger.h" -#include "lldb/API/SBEvent.h" -#include "lldb/API/SBFormat.h" -#include "lldb/API/SBHostOS.h" -#include "lldb/API/SBInstruction.h" -#include "lldb/API/SBInstructionList.h" -#include "lldb/API/SBLanguageRuntime.h" -#include "lldb/API/SBLaunchInfo.h" -#include "lldb/API/SBLineEntry.h" -#include "lldb/API/SBListener.h" -#include "lldb/API/SBProcess.h" -#include "lldb/API/SBStream.h" -#include "lldb/API/SBStringList.h" -#include "lldb/API/SBTarget.h" -#include "lldb/API/SBThread.h" - +#include "DAPForward.h" #include "ExceptionBreakpoint.h" #include "FunctionBreakpoint.h" #include "IOStream.h" #include "InstructionBreakpoint.h" +#include "OutputRedirector.h" #include "ProgressEvent.h" -#include "RunInTerminal.h" #include "SourceBreakpoint.h" +#include "lldb/API/SBBroadcaster.h" +#include "lldb/API/SBCommandInterpreter.h" +#include "lldb/API/SBDebugger.h" +#include "lldb/API/SBError.h" +#include "lldb/API/SBFile.h" +#include "lldb/API/SBFormat.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" +#include "lldb/API/SBValue.h" +#include "lldb/API/SBValueList.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/Threading.h" +#include +#include +#include +#include +#include #define VARREF_LOCALS (int64_t)1 #define VARREF_GLOBALS (int64_t)2 @@ -67,7 +50,8 @@ namespace lldb_dap { -typedef llvm::DenseMap SourceBreakpointMap; +typedef llvm::DenseMap, SourceBreakpoint> + SourceBreakpointMap; typedef llvm::StringMap FunctionBreakpointMap; typedef llvm::DenseMap InstructionBreakpointMap; @@ -82,7 +66,7 @@ enum DAPBroadcasterBits { eBroadcastBitStopProgressThread = 1u << 1 }; -typedef void (*RequestCallback)(const llvm::json::Object &command); +typedef void (*RequestCallback)(DAP &dap, const llvm::json::Object &command); typedef void (*ResponseCallback)(llvm::Expected value); enum class PacketStatus { @@ -135,31 +119,40 @@ struct Variables { }; struct StartDebuggingRequestHandler : public lldb::SBCommandPluginInterface { + DAP &dap; + explicit StartDebuggingRequestHandler(DAP &d) : dap(d) {}; bool DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject &result) override; }; struct ReplModeRequestHandler : public lldb::SBCommandPluginInterface { + DAP &dap; + explicit ReplModeRequestHandler(DAP &d) : dap(d) {}; bool DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject &result) override; }; struct SendEventRequestHandler : public lldb::SBCommandPluginInterface { + DAP &dap; + explicit SendEventRequestHandler(DAP &d) : dap(d) {}; bool DoExecute(lldb::SBDebugger debugger, char **command, lldb::SBCommandReturnObject &result) override; }; struct DAP { - std::string debug_adaptor_path; + llvm::StringRef debug_adaptor_path; + std::ofstream *log; InputStream input; OutputStream output; + lldb::SBFile in; + OutputRedirector out; + OutputRedirector err; lldb::SBDebugger debugger; lldb::SBTarget target; Variables variables; lldb::SBBroadcaster broadcaster; std::thread event_thread; std::thread progress_event_thread; - std::unique_ptr log; llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; InstructionBreakpointMap instruction_breakpoints; @@ -190,7 +183,7 @@ struct DAP { // the old process here so we can detect this case and keep running. lldb::pid_t restarting_process_id; bool configuration_done_sent; - std::map request_handlers; + std::map> request_handlers; bool waiting_for_run_in_terminal; ProgressEventReporter progress_event_reporter; // Keep track of the last stop thread index IDs as threads won't go away @@ -211,13 +204,23 @@ struct DAP { // will contain that expression. std::string last_nonempty_var_expression; - DAP(); + DAP(llvm::StringRef path, std::ofstream *log, ReplMode repl_mode, + StreamDescriptor input, StreamDescriptor output); ~DAP(); DAP(const DAP &rhs) = delete; void operator=(const DAP &rhs) = delete; ExceptionBreakpoint *GetExceptionBreakpoint(const std::string &filter); ExceptionBreakpoint *GetExceptionBreakpoint(const lldb::break_id_t bp_id); + /// Redirect stdout and stderr fo the IDE's console output. + /// + /// Errors in this operation will be printed to the log file and the IDE's + /// console output as well. + llvm::Error ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr); + + /// Stop the redirected IO threads and associated pipes. + void StopIO(); + // Serialize the JSON value into a string and send the JSON packet to // the "out" stream. void SendJSON(const llvm::json::Value &json); @@ -366,8 +369,6 @@ struct DAP { void SendJSON(const std::string &json_str); }; -extern DAP g_dap; - } // namespace lldb_dap #endif diff --git a/lldb/tools/lldb-dap/DAPForward.h b/lldb/tools/lldb-dap/DAPForward.h index 159d999a63c82..0196d83dcd6a9 100644 --- a/lldb/tools/lldb-dap/DAPForward.h +++ b/lldb/tools/lldb-dap/DAPForward.h @@ -9,6 +9,8 @@ #ifndef LLDB_TOOLS_LLDB_DAP_DAPFORWARD_H #define LLDB_TOOLS_LLDB_DAP_DAPFORWARD_H +// IWYU pragma: begin_exports + namespace lldb_dap { struct BreakpointBase; struct ExceptionBreakpoint; @@ -16,6 +18,7 @@ struct FunctionBreakpoint; struct SourceBreakpoint; struct Watchpoint; struct InstructionBreakpoint; +struct DAP; } // namespace lldb_dap namespace lldb { @@ -35,6 +38,7 @@ class SBLanguageRuntime; class SBLaunchInfo; class SBLineEntry; class SBListener; +class SBModule; class SBProcess; class SBStream; class SBStringList; @@ -44,4 +48,12 @@ class SBValue; class SBWatchpoint; } // namespace lldb +namespace llvm { +namespace json { +class Object; +} // namespace json +} // namespace llvm + +// IWYU pragma: end_exports + #endif diff --git a/lldb/tools/lldb-dap/ExceptionBreakpoint.cpp b/lldb/tools/lldb-dap/ExceptionBreakpoint.cpp index 130c237e65441..0fb865c19e574 100644 --- a/lldb/tools/lldb-dap/ExceptionBreakpoint.cpp +++ b/lldb/tools/lldb-dap/ExceptionBreakpoint.cpp @@ -9,6 +9,7 @@ #include "ExceptionBreakpoint.h" #include "BreakpointBase.h" #include "DAP.h" +#include "lldb/API/SBTarget.h" namespace lldb_dap { @@ -17,8 +18,8 @@ void ExceptionBreakpoint::SetBreakpoint() { return; bool catch_value = filter.find("_catch") != std::string::npos; bool throw_value = filter.find("_throw") != std::string::npos; - bp = g_dap.target.BreakpointCreateForException(language, catch_value, - throw_value); + bp = dap.target.BreakpointCreateForException(language, catch_value, + throw_value); // See comments in BreakpointBase::GetBreakpointLabel() for details of why // we add a label to our breakpoints. bp.AddName(BreakpointBase::GetBreakpointLabel()); @@ -27,7 +28,7 @@ void ExceptionBreakpoint::SetBreakpoint() { void ExceptionBreakpoint::ClearBreakpoint() { if (!bp.IsValid()) return; - g_dap.target.BreakpointDelete(bp.GetID()); + dap.target.BreakpointDelete(bp.GetID()); bp = lldb::SBBreakpoint(); } diff --git a/lldb/tools/lldb-dap/ExceptionBreakpoint.h b/lldb/tools/lldb-dap/ExceptionBreakpoint.h index 7b81d845cb26b..b83c5ef777352 100644 --- a/lldb/tools/lldb-dap/ExceptionBreakpoint.h +++ b/lldb/tools/lldb-dap/ExceptionBreakpoint.h @@ -9,21 +9,25 @@ #ifndef LLDB_TOOLS_LLDB_DAP_EXCEPTIONBREAKPOINT_H #define LLDB_TOOLS_LLDB_DAP_EXCEPTIONBREAKPOINT_H -#include - +#include "DAPForward.h" #include "lldb/API/SBBreakpoint.h" +#include "lldb/lldb-enumerations.h" +#include +#include namespace lldb_dap { struct ExceptionBreakpoint { + DAP &dap; std::string filter; std::string label; lldb::LanguageType language; - bool default_value; + bool default_value = false; lldb::SBBreakpoint bp; - ExceptionBreakpoint(std::string f, std::string l, lldb::LanguageType lang) - : filter(std::move(f)), label(std::move(l)), language(lang), - default_value(false), bp() {} + ExceptionBreakpoint(DAP &d, std::string f, std::string l, + lldb::LanguageType lang) + : dap(d), filter(std::move(f)), label(std::move(l)), language(lang), + bp() {} void SetBreakpoint(); void ClearBreakpoint(); diff --git a/lldb/tools/lldb-dap/FifoFiles.cpp b/lldb/tools/lldb-dap/FifoFiles.cpp index 9a6423f79471a..1f1bba80bd3b1 100644 --- a/lldb/tools/lldb-dap/FifoFiles.cpp +++ b/lldb/tools/lldb-dap/FifoFiles.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "FifoFiles.h" +#include "JSONUtils.h" #if !defined(_WIN32) #include @@ -18,11 +19,6 @@ #include #include #include -#include - -#include "llvm/Support/FileSystem.h" - -#include "lldb/lldb-defines.h" using namespace llvm; diff --git a/lldb/tools/lldb-dap/FifoFiles.h b/lldb/tools/lldb-dap/FifoFiles.h index 02a97cd5cbbd2..633ebeb2aedd4 100644 --- a/lldb/tools/lldb-dap/FifoFiles.h +++ b/lldb/tools/lldb-dap/FifoFiles.h @@ -9,10 +9,8 @@ #ifndef LLDB_TOOLS_LLDB_DAP_FIFOFILES_H #define LLDB_TOOLS_LLDB_DAP_FIFOFILES_H -#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX #include "llvm/Support/Error.h" - -#include "JSONUtils.h" +#include "llvm/Support/JSON.h" #include diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp index 21743bf908706..f266d751833c7 100644 --- a/lldb/tools/lldb-dap/FunctionBreakpoint.cpp +++ b/lldb/tools/lldb-dap/FunctionBreakpoint.cpp @@ -8,16 +8,17 @@ #include "FunctionBreakpoint.h" #include "DAP.h" +#include "JSONUtils.h" namespace lldb_dap { -FunctionBreakpoint::FunctionBreakpoint(const llvm::json::Object &obj) - : Breakpoint(obj), functionName(std::string(GetString(obj, "name"))) {} +FunctionBreakpoint::FunctionBreakpoint(DAP &d, const llvm::json::Object &obj) + : Breakpoint(d, obj), functionName(std::string(GetString(obj, "name"))) {} void FunctionBreakpoint::SetBreakpoint() { if (functionName.empty()) return; - bp = g_dap.target.BreakpointCreateByName(functionName.c_str()); + bp = dap.target.BreakpointCreateByName(functionName.c_str()); Breakpoint::SetBreakpoint(); } diff --git a/lldb/tools/lldb-dap/FunctionBreakpoint.h b/lldb/tools/lldb-dap/FunctionBreakpoint.h index b15ff1931a6b2..93f0b93b35291 100644 --- a/lldb/tools/lldb-dap/FunctionBreakpoint.h +++ b/lldb/tools/lldb-dap/FunctionBreakpoint.h @@ -10,14 +10,14 @@ #define LLDB_TOOLS_LLDB_DAP_FUNCTIONBREAKPOINT_H #include "Breakpoint.h" +#include "DAPForward.h" namespace lldb_dap { struct FunctionBreakpoint : public Breakpoint { std::string functionName; - FunctionBreakpoint() = default; - FunctionBreakpoint(const llvm::json::Object &obj); + FunctionBreakpoint(DAP &dap, const llvm::json::Object &obj); // Set this breakpoint in LLDB as a new breakpoint void SetBreakpoint(); diff --git a/lldb/tools/lldb-dap/IOStream.cpp b/lldb/tools/lldb-dap/IOStream.cpp index 96e9a1ed49532..d2e8ec40b0a7b 100644 --- a/lldb/tools/lldb-dap/IOStream.cpp +++ b/lldb/tools/lldb-dap/IOStream.cpp @@ -18,7 +18,6 @@ #include #include -#include using namespace lldb_dap; diff --git a/lldb/tools/lldb-dap/IOStream.h b/lldb/tools/lldb-dap/IOStream.h index b62502419182c..c91b2f717893c 100644 --- a/lldb/tools/lldb-dap/IOStream.h +++ b/lldb/tools/lldb-dap/IOStream.h @@ -9,16 +9,9 @@ #ifndef LLDB_TOOLS_LLDB_DAP_IOSTREAM_H #define LLDB_TOOLS_LLDB_DAP_IOSTREAM_H -#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX - #if defined(_WIN32) -// We need to #define NOMINMAX in order to skip `min()` and `max()` macro -// definitions that conflict with other system headers. -// We also need to #undef GetObject (which is defined to GetObjectW) because -// the JSON code we use also has methods named `GetObject()` and we conflict -// against these. -#define NOMINMAX -#include +#include "lldb/Host/windows/windows.h" +#include #else typedef int SOCKET; #endif @@ -54,6 +47,9 @@ struct StreamDescriptor { struct InputStream { StreamDescriptor descriptor; + explicit InputStream(StreamDescriptor descriptor) + : descriptor(std::move(descriptor)) {} + bool read_full(std::ofstream *log, size_t length, std::string &text); bool read_line(std::ofstream *log, std::string &line); @@ -64,6 +60,9 @@ struct InputStream { struct OutputStream { StreamDescriptor descriptor; + explicit OutputStream(StreamDescriptor descriptor) + : descriptor(std::move(descriptor)) {} + bool write_full(llvm::StringRef str); }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp index de4f6f5d86717..37daa8f0bdd5f 100644 --- a/lldb/tools/lldb-dap/InstructionBreakpoint.cpp +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.cpp @@ -9,20 +9,26 @@ #include "InstructionBreakpoint.h" #include "DAP.h" +#include "JSONUtils.h" +#include "lldb/API/SBBreakpoint.h" +#include "lldb/API/SBTarget.h" +#include "llvm/ADT/StringRef.h" namespace lldb_dap { // Instruction Breakpoint -InstructionBreakpoint::InstructionBreakpoint(const llvm::json::Object &obj) - : Breakpoint(obj), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), +InstructionBreakpoint::InstructionBreakpoint(DAP &d, + const llvm::json::Object &obj) + : Breakpoint(d, obj), instructionAddressReference(LLDB_INVALID_ADDRESS), offset(GetSigned(obj, "offset", 0)) { GetString(obj, "instructionReference") .getAsInteger(0, instructionAddressReference); instructionAddressReference += offset; } -void InstructionBreakpoint::SetInstructionBreakpoint() { - bp = g_dap.target.BreakpointCreateByAddress(instructionAddressReference); - id = bp.GetID(); +void InstructionBreakpoint::SetBreakpoint() { + bp = dap.target.BreakpointCreateByAddress(instructionAddressReference); + Breakpoint::SetBreakpoint(); } + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/InstructionBreakpoint.h b/lldb/tools/lldb-dap/InstructionBreakpoint.h index cf1516f46e955..b2e66a9db9e20 100644 --- a/lldb/tools/lldb-dap/InstructionBreakpoint.h +++ b/lldb/tools/lldb-dap/InstructionBreakpoint.h @@ -11,7 +11,9 @@ #define LLDB_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H #include "Breakpoint.h" -#include "llvm/ADT/StringRef.h" +#include "DAPForward.h" +#include "lldb/lldb-types.h" +#include namespace lldb_dap { @@ -19,16 +21,12 @@ namespace lldb_dap { struct InstructionBreakpoint : public Breakpoint { lldb::addr_t instructionAddressReference; - int32_t id; int32_t offset; - InstructionBreakpoint() - : Breakpoint(), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0), - offset(0) {} - InstructionBreakpoint(const llvm::json::Object &obj); + InstructionBreakpoint(DAP &d, const llvm::json::Object &obj); // Set instruction breakpoint in LLDB as a new breakpoint - void SetInstructionBreakpoint(); + void SetBreakpoint(); }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 3601ed6e1da4f..8df8d884be3c8 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -6,30 +6,53 @@ // //===----------------------------------------------------------------------===// -#include -#include -#include -#include -#include - -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/FormatAdapters.h" -#include "llvm/Support/FormatVariadic.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/ScopedPrinter.h" +#include "JSONUtils.h" -#include "lldb/API/SBBreakpoint.h" -#include "lldb/API/SBBreakpointLocation.h" +#include "BreakpointBase.h" +#include "DAP.h" +#include "ExceptionBreakpoint.h" +#include "LLDBUtils.h" +#include "lldb/API/SBAddress.h" +#include "lldb/API/SBCompileUnit.h" #include "lldb/API/SBDeclaration.h" +#include "lldb/API/SBEnvironment.h" +#include "lldb/API/SBError.h" +#include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBFunction.h" +#include "lldb/API/SBLineEntry.h" +#include "lldb/API/SBModule.h" +#include "lldb/API/SBQueue.h" +#include "lldb/API/SBSection.h" +#include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" #include "lldb/API/SBStructuredData.h" +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" +#include "lldb/API/SBType.h" #include "lldb/API/SBValue.h" -#include "lldb/Host/PosixApi.h" - -#include "DAP.h" -#include "ExceptionBreakpoint.h" -#include "JSONUtils.h" -#include "LLDBUtils.h" +#include "lldb/Host/PosixApi.h" // IWYU pragma: keep +#include "lldb/lldb-defines.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-types.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/Format.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/ScopedPrinter.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace lldb_dap { @@ -131,7 +154,7 @@ DecodeMemoryReference(llvm::StringRef memoryReference) { std::vector GetStrings(const llvm::json::Object *obj, llvm::StringRef key) { std::vector strs; - auto json_array = obj->getArray(key); + const auto *json_array = obj->getArray(key); if (!json_array) return strs; for (const auto &value : *json_array) { @@ -187,12 +210,6 @@ static bool IsClassStructOrUnionType(lldb::SBType t) { /// glance. static std::optional TryCreateAutoSummaryForContainer(lldb::SBValue &v) { - // We gate this feature because it performs GetNumChildren(), which can - // cause performance issues because LLDB needs to complete possibly huge - // types. - if (!g_dap.enable_auto_variable_summaries) - return std::nullopt; - if (!v.MightHaveChildren()) return std::nullopt; /// As this operation can be potentially slow, we limit the total time spent @@ -248,10 +265,7 @@ TryCreateAutoSummaryForContainer(lldb::SBValue &v) { /// Try to create a summary string for the given value that doesn't have a /// summary of its own. -static std::optional TryCreateAutoSummary(lldb::SBValue value) { - if (!g_dap.enable_auto_variable_summaries) - return std::nullopt; - +static std::optional TryCreateAutoSummary(lldb::SBValue &value) { // We use the dereferenced value for generating the summary. if (value.GetType().IsPointerType() || value.GetType().IsReferenceType()) value = value.Dereference(); @@ -462,10 +476,12 @@ static std::string ConvertDebugInfoSizeToString(uint64_t debug_info) { } return oss.str(); } -llvm::json::Value CreateModule(lldb::SBModule &module) { + +llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module) { llvm::json::Object object; - if (!module.IsValid()) + if (!target.IsValid() || !module.IsValid()) return llvm::json::Value(std::move(object)); + const char *uuid = module.GetUUIDString(); object.try_emplace("id", uuid ? std::string(uuid) : std::string("")); object.try_emplace("name", std::string(module.GetFileSpec().GetFilename())); @@ -491,7 +507,7 @@ llvm::json::Value CreateModule(lldb::SBModule &module) { object.try_emplace("symbolStatus", "Symbols not found."); } std::string loaded_addr = std::to_string( - module.GetObjectFileHeaderAddress().GetLoadAddress(g_dap.target)); + module.GetObjectFileHeaderAddress().GetLoadAddress(target)); object.try_emplace("addressRange", loaded_addr); std::string version_str; uint32_t version_nums[3]; @@ -682,7 +698,7 @@ llvm::json::Value CreateSource(llvm::StringRef source_path) { return llvm::json::Value(std::move(source)); } -std::optional CreateSource(lldb::SBFrame &frame) { +static std::optional CreateSource(lldb::SBFrame &frame) { auto line_entry = frame.GetLineEntry(); // A line entry of 0 indicates the line is compiler generated i.e. no source // file is associated with the frame. @@ -753,15 +769,15 @@ std::optional CreateSource(lldb::SBFrame &frame) { // }, // "required": [ "id", "name", "line", "column" ] // } -llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { +llvm::json::Value CreateStackFrame(lldb::SBFrame &frame, + lldb::SBFormat &format) { llvm::json::Object object; int64_t frame_id = MakeDAPFrameID(frame); object.try_emplace("id", frame_id); std::string frame_name; lldb::SBStream stream; - if (g_dap.frame_format && - frame.GetDescriptionWithFormat(g_dap.frame_format, stream).Success()) { + if (format && frame.GetDescriptionWithFormat(format, stream).Success()) { frame_name = stream.GetData(); // `function_name` can be a nullptr, which throws an error when assigned to @@ -778,7 +794,7 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { } // We only include `[opt]` if a custom frame format is not specified. - if (!g_dap.frame_format && frame.GetFunction().GetIsOptimized()) + if (!format && frame.GetFunction().GetIsOptimized()) frame_name += " [opt]"; EmplaceSafeString(object, "name", frame_name); @@ -812,11 +828,11 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) { return llvm::json::Value(std::move(object)); } -llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread) { +llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, + lldb::SBFormat &format) { std::string name; lldb::SBStream stream; - if (g_dap.thread_format && - thread.GetDescriptionWithFormat(g_dap.thread_format, stream).Success()) { + if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { name = stream.GetData(); } else { const uint32_t thread_idx = thread.GetExtendedBacktraceOriginatingIndexID(); @@ -834,70 +850,6 @@ llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread) { {"presentationHint", "label"}}); } -// Response to `setInstructionBreakpoints` request. -// "Breakpoint": { -// "type": "object", -// "description": "Response to `setInstructionBreakpoints` request.", -// "properties": { -// "id": { -// "type": "number", -// "description": "The identifier for the breakpoint. It is needed if -// breakpoint events are used to update or remove breakpoints." -// }, -// "verified": { -// "type": "boolean", -// "description": "If true, the breakpoint could be set (but not -// necessarily at the desired location." -// }, -// "message": { -// "type": "string", -// "description": "A message about the state of the breakpoint. -// This is shown to the user and can be used to explain why a breakpoint -// could not be verified." -// }, -// "source": { -// "type": "Source", -// "description": "The source where the breakpoint is located." -// }, -// "line": { -// "type": "number", -// "description": "The start line of the actual range covered by the -// breakpoint." -// }, -// "column": { -// "type": "number", -// "description": "The start column of the actual range covered by the -// breakpoint." -// }, -// "endLine": { -// "type": "number", -// "description": "The end line of the actual range covered by the -// breakpoint." -// }, -// "endColumn": { -// "type": "number", -// "description": "The end column of the actual range covered by the -// breakpoint. If no end line is given, then the end column is assumed to -// be in the start line." -// }, -// "instructionReference": { -// "type": "string", -// "description": "A memory reference to where the breakpoint is set." -// }, -// "offset": { -// "type": "number", -// "description": "The offset from the instruction reference. -// This can be negative." -// }, -// }, -// "required": [ "id", "verified", "line"] -// } -llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) { - llvm::json::Object object; - ibp->CreateJsonObject(object); - return llvm::json::Value(std::move(object)); -} - // "Thread": { // "type": "object", // "description": "A Thread", @@ -913,13 +865,12 @@ llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) { // }, // "required": [ "id", "name" ] // } -llvm::json::Value CreateThread(lldb::SBThread &thread) { +llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format) { llvm::json::Object object; object.try_emplace("id", (int64_t)thread.GetThreadID()); std::string thread_str; lldb::SBStream stream; - if (g_dap.thread_format && - thread.GetDescriptionWithFormat(g_dap.thread_format, stream).Success()) { + if (format && thread.GetDescriptionWithFormat(format, stream).Success()) { thread_str = stream.GetData(); } else { const char *thread_name = thread.GetName(); @@ -1007,7 +958,7 @@ llvm::json::Value CreateThread(lldb::SBThread &thread) { // "required": [ "event", "body" ] // }] // } -llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, +llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, uint32_t stop_id) { llvm::json::Object event(CreateEventObject("stopped")); llvm::json::Object body; @@ -1017,13 +968,13 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, body.try_emplace("reason", "step"); break; case lldb::eStopReasonBreakpoint: { - ExceptionBreakpoint *exc_bp = g_dap.GetExceptionBPFromStopReason(thread); + ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { body.try_emplace("reason", "exception"); EmplaceSafeString(body, "description", exc_bp->label); } else { InstructionBreakpoint *inst_bp = - g_dap.GetInstructionBPFromStopReason(thread); + dap.GetInstructionBPFromStopReason(thread); if (inst_bp) { body.try_emplace("reason", "instruction breakpoint"); } else { @@ -1080,21 +1031,21 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, } } // "threadCausedFocus" is used in tests to validate breaking behavior. - if (tid == g_dap.focus_tid) { + if (tid == dap.focus_tid) { body.try_emplace("threadCausedFocus", true); } - body.try_emplace("preserveFocusHint", tid != g_dap.focus_tid); + body.try_emplace("preserveFocusHint", tid != dap.focus_tid); body.try_emplace("allThreadsStopped", true); event.try_emplace("body", std::move(body)); return llvm::json::Value(std::move(event)); } -const char *GetNonNullVariableName(lldb::SBValue v) { +const char *GetNonNullVariableName(lldb::SBValue &v) { const char *name = v.GetName(); return name ? name : ""; } -std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v, +std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v, bool is_name_duplicated) { lldb::SBStream name_builder; name_builder.Print(GetNonNullVariableName(v)); @@ -1111,7 +1062,9 @@ std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v, return name_builder.GetData(); } -VariableDescription::VariableDescription(lldb::SBValue v, bool format_hex, +VariableDescription::VariableDescription(lldb::SBValue v, + bool auto_variable_summaries, + bool format_hex, bool is_name_duplicated, std::optional custom_name) : v(v) { @@ -1142,7 +1095,7 @@ VariableDescription::VariableDescription(lldb::SBValue v, bool format_hex, } else { value = llvm::StringRef(v.GetValue()).str(); summary = llvm::StringRef(v.GetSummary()).str(); - if (summary.empty()) + if (summary.empty() && auto_variable_summaries) auto_summary = TryCreateAutoSummary(v); std::optional effective_summary = @@ -1226,7 +1179,7 @@ bool ValuePointsToCode(lldb::SBValue v) { lldb::addr_t addr = v.GetValueAsAddress(); lldb::SBLineEntry line_entry = - g_dap.target.ResolveLoadAddress(addr).GetLineEntry(); + v.GetTarget().ResolveLoadAddress(addr).GetLineEntry(); return line_entry.IsValid(); } @@ -1387,9 +1340,12 @@ std::pair UnpackLocation(int64_t location_id) { // "required": [ "name", "value", "variablesReference" ] // } llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, - bool format_hex, bool is_name_duplicated, + bool format_hex, bool auto_variable_summaries, + bool synthetic_child_debugging, + bool is_name_duplicated, std::optional custom_name) { - VariableDescription desc(v, format_hex, is_name_duplicated, custom_name); + VariableDescription desc(v, auto_variable_summaries, format_hex, + is_name_duplicated, custom_name); llvm::json::Object object; EmplaceSafeString(object, "name", desc.name); EmplaceSafeString(object, "value", desc.display_value); @@ -1425,7 +1381,7 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, size_t num_children = v.GetNumChildren(); // If we are creating a "[raw]" fake child for each synthetic type, we // have to account for it when returning indexed variables. - if (g_dap.enable_synthetic_child_debugging) + if (synthetic_child_debugging) ++num_children; object.try_emplace("indexedVariables", num_children); } @@ -1456,7 +1412,7 @@ llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, return llvm::json::Value(std::move(object)); } -llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit) { +llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit) { llvm::json::Object object; char unit_path_arr[PATH_MAX]; unit.GetFileSpec().GetPath(unit_path_arr, sizeof(unit_path_arr)); @@ -1477,7 +1433,7 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, // the terminal in a new window. run_in_terminal_args.try_emplace("kind", "integrated"); - auto launch_request_arguments = launch_request.getObject("arguments"); + const auto *launch_request_arguments = launch_request.getObject("arguments"); // The program path must be the first entry in the "args" field std::vector args = {debug_adaptor_path.str(), "--comm-file", comm_file.str()}; @@ -1503,11 +1459,7 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, llvm::StringRef key = envs.GetNameAtIndex(index); llvm::StringRef value = envs.GetValueAtIndex(index); - if (key.empty()) - g_dap.SendOutput(OutputType::Stderr, - "empty environment variable for value: \"" + - value.str() + '\"'); - else + if (!key.empty()) env_json.try_emplace(key, value); } run_in_terminal_args.try_emplace("env", @@ -1519,11 +1471,11 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, // Keep all the top level items from the statistics dump, except for the // "modules" array. It can be huge and cause delay // Array and dictionary value will return as pairs -void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key, - llvm::json::Object &out) { +static void FilterAndGetValueForKey(const lldb::SBStructuredData data, + const char *key, llvm::json::Object &out) { lldb::SBStructuredData value = data.GetValueForKey(key); std::string key_utf8 = llvm::json::fixUTF8(key); - if (strcmp(key, "modules") == 0) + if (llvm::StringRef(key) == "modules") return; switch (value.GetType()) { case lldb::eStructuredDataTypeFloat: @@ -1562,8 +1514,8 @@ void FilterAndGetValueForKey(const lldb::SBStructuredData data, const char *key, } } -void addStatistic(llvm::json::Object &event) { - lldb::SBStructuredData statistics = g_dap.target.GetStatistics(); +static void addStatistic(lldb::SBTarget &target, llvm::json::Object &event) { + lldb::SBStructuredData statistics = target.GetStatistics(); bool is_dictionary = statistics.GetType() == lldb::eStructuredDataTypeDictionary; if (!is_dictionary) @@ -1580,9 +1532,9 @@ void addStatistic(llvm::json::Object &event) { event.try_emplace("statistics", std::move(stats_body)); } -llvm::json::Object CreateTerminatedEventObject() { +llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target) { llvm::json::Object event(CreateEventObject("terminated")); - addStatistic(event); + addStatistic(target, event); return event; } diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 54fc432347572..db56d98777347 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -9,14 +9,22 @@ #ifndef LLDB_TOOLS_LLDB_DAP_JSONUTILS_H #define LLDB_TOOLS_LLDB_DAP_JSONUTILS_H -#include "BreakpointBase.h" #include "DAPForward.h" -#include "lldb/API/SBModule.h" +#include "lldb/API/SBCompileUnit.h" +#include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBFormat.h" +#include "lldb/API/SBLineEntry.h" +#include "lldb/API/SBType.h" +#include "lldb/API/SBValue.h" +#include "lldb/lldb-types.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/JSON.h" #include #include +#include #include +#include +#include namespace lldb_dap { @@ -260,13 +268,16 @@ CreateBreakpoint(BreakpointBase *bp, /// Converts a LLDB module to a VS Code DAP module for use in "modules" events. /// +/// \param[in] target +/// A LLDB target object to convert into a JSON value. +/// /// \param[in] module /// A LLDB module object to convert into a JSON value /// /// \return /// A "Module" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateModule(lldb::SBModule &module); +llvm::json::Value CreateModule(lldb::SBTarget &target, lldb::SBModule &module); /// Create a "Event" JSON object using \a event_name as the event name /// @@ -356,10 +367,15 @@ llvm::json::Value CreateSource(llvm::StringRef source_path); /// The LLDB stack frame to use when populating out the "StackFrame" /// object. /// +/// \param[in] format +/// The LLDB format to use when populating out the "StackFrame" +/// object. +/// /// \return /// A "StackFrame" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); +llvm::json::Value CreateStackFrame(lldb::SBFrame &frame, + lldb::SBFormat &format); /// Create a "StackFrame" label object for a LLDB thread. /// @@ -375,21 +391,14 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame); /// The LLDB thread to use when populating out the "Thread" /// object. /// -/// \return -/// A "StackFrame" JSON object with that follows the formal JSON -/// definition outlined by Microsoft. -llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread); - -/// Create a "instruction" object for a LLDB disassemble object as described in -/// the Visual Studio Code debug adaptor definition. +/// \param[in] format +/// The configured formatter for the DAP session. /// -/// \param[in] bp -/// The LLDB instruction object used to populate the disassembly -/// instruction. /// \return -/// A "Scope" JSON object with that follows the formal JSON +/// A "StackFrame" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp); +llvm::json::Value CreateExtendedStackFrameLabel(lldb::SBThread &thread, + lldb::SBFormat &format); /// Create a "Thread" object for a LLDB thread object. /// @@ -404,10 +413,14 @@ llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp); /// The LLDB thread to use when populating out the "Thread" /// object. /// +/// \param[in] format +/// The LLDB format to use when populating out the "Thread" +/// object. +/// /// \return /// A "Thread" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateThread(lldb::SBThread &thread); +llvm::json::Value CreateThread(lldb::SBThread &thread, lldb::SBFormat &format); /// Create a "StoppedEvent" object for a LLDB thread object. /// @@ -423,25 +436,32 @@ llvm::json::Value CreateThread(lldb::SBThread &thread); /// "allThreadsStopped" - set to True to indicate that all threads /// stop when any thread stops. /// +/// \param[in] dap +/// The DAP session associated with the stopped thread. +/// /// \param[in] thread /// The LLDB thread to use when populating out the "StoppedEvent" /// object. /// +/// \param[in] stop_id +/// The stop id for this event. +/// /// \return /// A "StoppedEvent" JSON object with that follows the formal JSON /// definition outlined by Microsoft. -llvm::json::Value CreateThreadStopped(lldb::SBThread &thread, uint32_t stop_id); +llvm::json::Value CreateThreadStopped(DAP &dap, lldb::SBThread &thread, + uint32_t stop_id); /// \return /// The variable name of \a value or a default placeholder. -const char *GetNonNullVariableName(lldb::SBValue value); +const char *GetNonNullVariableName(lldb::SBValue &value); /// VSCode can't display two variables with the same name, so we need to /// distinguish them by using a suffix. /// /// If the source and line information is present, we use it as the suffix. /// Otherwise, we fallback to the variable address or register location. -std::string CreateUniqueVariableNameForDisplay(lldb::SBValue v, +std::string CreateUniqueVariableNameForDisplay(lldb::SBValue &v, bool is_name_duplicated); /// Helper struct that parses the metadata of an \a lldb::SBValue and produces @@ -468,8 +488,8 @@ struct VariableDescription { /// The SBValue for this variable. lldb::SBValue v; - VariableDescription(lldb::SBValue v, bool format_hex = false, - bool is_name_duplicated = false, + VariableDescription(lldb::SBValue v, bool auto_variable_summaries, + bool format_hex = false, bool is_name_duplicated = false, std::optional custom_name = {}); /// Create a JSON object that represents these extensions to the DAP variable @@ -518,9 +538,12 @@ std::pair UnpackLocation(int64_t location_id); /// properties. /// /// \param[in] format_hex -/// It set to true the variable will be formatted as hex in +/// If set to true the variable will be formatted as hex in /// the "value" key value pair for the value of the variable. /// +/// \param[in] auto_variable_summaries +/// IF set to true the variable will create an automatic variable summary. +/// /// \param[in] is_name_duplicated /// Whether the same variable name appears multiple times within the same /// context (e.g. locals). This can happen due to shadowed variables in @@ -537,11 +560,12 @@ std::pair UnpackLocation(int64_t location_id); /// A "Variable" JSON object with that follows the formal JSON /// definition outlined by Microsoft. llvm::json::Value CreateVariable(lldb::SBValue v, int64_t var_ref, - bool format_hex, + bool format_hex, bool auto_variable_summaries, + bool synthetic_child_debugging, bool is_name_duplicated = false, std::optional custom_name = {}); -llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit unit); +llvm::json::Value CreateCompileUnit(lldb::SBCompileUnit &unit); /// Create a runInTerminal reverse request object /// @@ -574,7 +598,7 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request, /// /// \return /// A body JSON object with debug info and breakpoint info -llvm::json::Object CreateTerminatedEventObject(); +llvm::json::Object CreateTerminatedEventObject(lldb::SBTarget &target); /// Convert a given JSON object to a string. std::string JSONToString(const llvm::json::Value &json); diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index c9db50ee4d1e9..67c3d94020e8b 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -8,12 +8,14 @@ #include "LLDBUtils.h" #include "DAP.h" +#include "JSONUtils.h" +#include "lldb/API/SBStringList.h" #include namespace lldb_dap { -bool RunLLDBCommands(llvm::StringRef prefix, +bool RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands, llvm::raw_ostream &strm, bool parse_command_directives) { if (commands.empty()) @@ -21,7 +23,7 @@ bool RunLLDBCommands(llvm::StringRef prefix, bool did_print_prefix = false; - lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter(); + lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter(); for (llvm::StringRef command : commands) { lldb::SBCommandReturnObject result; bool quiet_on_success = false; @@ -76,24 +78,23 @@ bool RunLLDBCommands(llvm::StringRef prefix, return true; } -std::string RunLLDBCommands(llvm::StringRef prefix, +std::string RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands, bool &required_command_failed, bool parse_command_directives) { required_command_failed = false; std::string s; llvm::raw_string_ostream strm(s); - required_command_failed = - !RunLLDBCommands(prefix, commands, strm, parse_command_directives); - strm.flush(); + required_command_failed = !RunLLDBCommands(debugger, prefix, commands, strm, + parse_command_directives); return s; } std::string -RunLLDBCommandsVerbatim(llvm::StringRef prefix, +RunLLDBCommandsVerbatim(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands) { bool required_command_failed = false; - return RunLLDBCommands(prefix, commands, required_command_failed, + return RunLLDBCommands(debugger, prefix, commands, required_command_failed, /*parse_command_directives=*/false); } diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h index d5072d19029a1..a9e13bb3678da 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.h +++ b/lldb/tools/lldb-dap/LLDBUtils.h @@ -10,6 +10,7 @@ #define LLDB_TOOLS_LLDB_DAP_LLDBUTILS_H #include "DAPForward.h" +#include "lldb/API/SBDebugger.h" #include "lldb/API/SBEnvironment.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" @@ -30,6 +31,9 @@ namespace lldb_dap { /// emitted regardless, and \b false is returned without executing the /// remaining commands. /// +/// \param[in] debugger +/// The debugger that will execute the lldb commands. +/// /// \param[in] prefix /// A string that will be printed into \a strm prior to emitting /// the prompt + command and command output. Can be NULL. @@ -48,7 +52,7 @@ namespace lldb_dap { /// \return /// \b true, unless a command prefixed with \b ! fails and parsing of /// command directives is enabled. -bool RunLLDBCommands(llvm::StringRef prefix, +bool RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands, llvm::raw_ostream &strm, bool parse_command_directives); @@ -57,6 +61,9 @@ bool RunLLDBCommands(llvm::StringRef prefix, /// All output from every command, including the prompt + the command /// is returned in the std::string return value. /// +/// \param[in] debugger +/// The debugger that will execute the lldb commands. +/// /// \param[in] prefix /// A string that will be printed into \a strm prior to emitting /// the prompt + command and command output. Can be NULL. @@ -75,14 +82,14 @@ bool RunLLDBCommands(llvm::StringRef prefix, /// \return /// A std::string that contains the prefix and all commands and /// command output. -std::string RunLLDBCommands(llvm::StringRef prefix, +std::string RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands, bool &required_command_failed, bool parse_command_directives = true); /// Similar to the method above, but without parsing command directives. std::string -RunLLDBCommandsVerbatim(llvm::StringRef prefix, +RunLLDBCommandsVerbatim(lldb::SBDebugger &debugger, llvm::StringRef prefix, const llvm::ArrayRef &commands); /// Check if a thread has a stop reason. diff --git a/lldb/tools/lldb-dap/OutputRedirector.cpp b/lldb/tools/lldb-dap/OutputRedirector.cpp index 2c2f49569869b..df21fb850dc55 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.cpp +++ b/lldb/tools/lldb-dap/OutputRedirector.cpp @@ -6,6 +6,12 @@ // //===----------------------------------------------------------------------===/ +#include "OutputRedirector.h" +#include "DAP.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" +#include +#include #if defined(_WIN32) #include #include @@ -13,51 +19,77 @@ #include #endif -#include "DAP.h" -#include "OutputRedirector.h" -#include "llvm/ADT/StringRef.h" - using namespace llvm; +static constexpr auto kCloseSentinel = StringLiteral::withInnerNUL("\0"); + namespace lldb_dap { -Error RedirectFd(int fd, std::function callback) { +int OutputRedirector::kInvalidDescriptor = -1; + +OutputRedirector::OutputRedirector() : m_fd(kInvalidDescriptor) {} + +Expected OutputRedirector::GetWriteFileDescriptor() { + if (m_fd == kInvalidDescriptor) + return createStringError(std::errc::bad_file_descriptor, + "write handle is not open for writing"); + return m_fd; +} + +Error OutputRedirector::RedirectTo(std::function callback) { + assert(m_fd == kInvalidDescriptor && "Output readirector already started."); int new_fd[2]; + #if defined(_WIN32) - if (_pipe(new_fd, 4096, O_TEXT) == -1) { + if (::_pipe(new_fd, OutputBufferSize, O_TEXT) == -1) { #else - if (pipe(new_fd) == -1) { + if (::pipe(new_fd) == -1) { #endif int error = errno; return createStringError(inconvertibleErrorCode(), - "Couldn't create new pipe for fd %d. %s", fd, - strerror(error)); - } - - if (dup2(new_fd[1], fd) == -1) { - int error = errno; - return createStringError(inconvertibleErrorCode(), - "Couldn't override the fd %d. %s", fd, - strerror(error)); + "Couldn't create new pipe %s", strerror(error)); } int read_fd = new_fd[0]; - std::thread t([read_fd, callback]() { + m_fd = new_fd[1]; + m_forwarder = std::thread([this, callback, read_fd]() { char buffer[OutputBufferSize]; - while (true) { - ssize_t bytes_count = read(read_fd, &buffer, sizeof(buffer)); - if (bytes_count == 0) - return; + while (!m_stopped) { + ssize_t bytes_count = ::read(read_fd, &buffer, sizeof(buffer)); if (bytes_count == -1) { - if (errno == EAGAIN || errno == EINTR) + // Skip non-fatal errors. + if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) continue; break; } - callback(StringRef(buffer, bytes_count)); + + StringRef data(buffer, bytes_count); + if (m_stopped) + data.consume_back(kCloseSentinel); + if (data.empty()) + break; + + callback(data); } + ::close(read_fd); }); - t.detach(); + return Error::success(); } +void OutputRedirector::Stop() { + m_stopped = true; + + if (m_fd != kInvalidDescriptor) { + int fd = m_fd; + m_fd = kInvalidDescriptor; + // Closing the pipe may not be sufficient to wake up the thread in case the + // write descriptor is duplicated (to stdout/err or to another process). + // Write a null byte to ensure the read call returns. + (void)::write(fd, kCloseSentinel.data(), kCloseSentinel.size()); + ::close(fd); + m_forwarder.join(); + } +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/OutputRedirector.h b/lldb/tools/lldb-dap/OutputRedirector.h index dba51016775bf..a47ea96f71f14 100644 --- a/lldb/tools/lldb-dap/OutputRedirector.h +++ b/lldb/tools/lldb-dap/OutputRedirector.h @@ -9,19 +9,40 @@ #ifndef LLDB_TOOLS_LLDB_DAP_OUTPUT_REDIRECTOR_H #define LLDB_TOOLS_LLDB_DAP_OUTPUT_REDIRECTOR_H -#include - #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" +#include +#include +#include namespace lldb_dap { -/// Redirects the output of a given file descriptor to a callback. -/// -/// \return -/// \a Error::success if the redirection was set up correctly, or an error -/// otherwise. -llvm::Error RedirectFd(int fd, std::function callback); +class OutputRedirector { +public: + static int kInvalidDescriptor; + + /// Creates writable file descriptor that will invoke the given callback on + /// each write in a background thread. + /// + /// \return + /// \a Error::success if the redirection was set up correctly, or an error + /// otherwise. + llvm::Error RedirectTo(std::function callback); + + llvm::Expected GetWriteFileDescriptor(); + void Stop(); + + ~OutputRedirector() { Stop(); } + + OutputRedirector(); + OutputRedirector(const OutputRedirector &) = delete; + OutputRedirector &operator=(const OutputRedirector &) = delete; + +private: + std::atomic m_stopped = false; + int m_fd; + std::thread m_forwarder; +}; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/ProgressEvent.cpp b/lldb/tools/lldb-dap/ProgressEvent.cpp index 7ed351f695e85..6a4978c055e51 100644 --- a/lldb/tools/lldb-dap/ProgressEvent.cpp +++ b/lldb/tools/lldb-dap/ProgressEvent.cpp @@ -110,7 +110,6 @@ json::Value ProgressEvent::ToJSON() const { std::string progress_id_str; llvm::raw_string_ostream progress_id_strm(progress_id_str); progress_id_strm << m_progress_id; - progress_id_strm.flush(); body.try_emplace("progressId", progress_id_str); if (m_event_type == progressStart) { diff --git a/lldb/tools/lldb-dap/ProgressEvent.h b/lldb/tools/lldb-dap/ProgressEvent.h index 8577010f6b886..d1b9b9dd887cd 100644 --- a/lldb/tools/lldb-dap/ProgressEvent.h +++ b/lldb/tools/lldb-dap/ProgressEvent.h @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include +#include #include #include #include diff --git a/lldb/tools/lldb-dap/RunInTerminal.cpp b/lldb/tools/lldb-dap/RunInTerminal.cpp index ad019b8a56a4f..4fe09e2885a8e 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.cpp +++ b/lldb/tools/lldb-dap/RunInTerminal.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "RunInTerminal.h" +#include "JSONUtils.h" #if !defined(_WIN32) #include @@ -15,14 +16,10 @@ #endif #include -#include #include -#include #include "llvm/Support/FileSystem.h" -#include "lldb/lldb-defines.h" - using namespace llvm; namespace lldb_dap { diff --git a/lldb/tools/lldb-dap/RunInTerminal.h b/lldb/tools/lldb-dap/RunInTerminal.h index 2fbe3acbb4084..b20f8beb6071d 100644 --- a/lldb/tools/lldb-dap/RunInTerminal.h +++ b/lldb/tools/lldb-dap/RunInTerminal.h @@ -10,9 +10,11 @@ #define LLDB_TOOLS_LLDB_DAP_RUNINTERMINAL_H #include "FifoFiles.h" +#include "lldb/API/SBError.h" #include -#include +#include +#include namespace lldb_dap { diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp index f5dd1346cb9e5..418e205312c9f 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp +++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp @@ -7,19 +7,33 @@ //===----------------------------------------------------------------------===// #include "SourceBreakpoint.h" +#include "BreakpointBase.h" #include "DAP.h" +#include "JSONUtils.h" +#include "lldb/API/SBBreakpoint.h" +#include "lldb/API/SBFileSpecList.h" +#include "lldb/API/SBFrame.h" +#include "lldb/API/SBTarget.h" +#include "lldb/API/SBThread.h" +#include "lldb/API/SBValue.h" +#include "lldb/lldb-enumerations.h" +#include +#include +#include +#include namespace lldb_dap { -SourceBreakpoint::SourceBreakpoint(const llvm::json::Object &obj) - : Breakpoint(obj), logMessage(std::string(GetString(obj, "logMessage"))), +SourceBreakpoint::SourceBreakpoint(DAP &dap, const llvm::json::Object &obj) + : Breakpoint(dap, obj), + logMessage(std::string(GetString(obj, "logMessage"))), line(GetUnsigned(obj, "line", 0)), column(GetUnsigned(obj, "column", 0)) { } void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { lldb::SBFileSpecList module_list; - bp = g_dap.target.BreakpointCreateByLocation(source_path.str().c_str(), line, - column, 0, module_list); + bp = dap.target.BreakpointCreateByLocation(source_path.str().c_str(), line, + column, 0, module_list); if (!logMessage.empty()) SetLogMessage(); Breakpoint::SetBreakpoint(); @@ -135,7 +149,7 @@ lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text, return error; } // hex number in the text - if (isxdigit(text[0])) { + if (std::isxdigit(text[0])) { // Make a string that can hold onto two hex chars plus a // NULL terminator char hex_str[3] = {0, 0, 0}; @@ -143,7 +157,7 @@ lldb::SBError SourceBreakpoint::FormatLogText(llvm::StringRef text, text = text.drop_front(); - if (!text.empty() && isxdigit(text[0])) { + if (!text.empty() && std::isxdigit(text[0])) { hex_str[1] = text[0]; text = text.drop_front(); } @@ -278,7 +292,7 @@ void SourceBreakpoint::SetLogMessage() { void SourceBreakpoint::NotifyLogMessageError(llvm::StringRef error) { std::string message = "Log message has error: "; message += error; - g_dap.SendOutput(OutputType::Console, message); + dap.SendOutput(OutputType::Console, message); } /*static*/ @@ -303,14 +317,16 @@ bool SourceBreakpoint::BreakpointHitCallback( frame.GetValueForVariablePath(expr, lldb::eDynamicDontRunTarget); if (value.GetError().Fail()) value = frame.EvaluateExpression(expr); - output += VariableDescription(value).display_value; + output += + VariableDescription(value, bp->dap.enable_auto_variable_summaries) + .display_value; } else { output += messagePart.text; } } if (!output.empty() && output.back() != '\n') output.push_back('\n'); // Ensure log message has line break. - g_dap.SendOutput(OutputType::Console, output.c_str()); + bp->dap.SendOutput(OutputType::Console, output.c_str()); // Do not stop. return false; diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.h b/lldb/tools/lldb-dap/SourceBreakpoint.h index aa3fbe6d0f96d..064bd29d9fc79 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.h +++ b/lldb/tools/lldb-dap/SourceBreakpoint.h @@ -10,7 +10,12 @@ #define LLDB_TOOLS_LLDB_DAP_SOURCEBREAKPOINT_H #include "Breakpoint.h" +#include "DAPForward.h" +#include "lldb/API/SBError.h" #include "llvm/ADT/StringRef.h" +#include +#include +#include namespace lldb_dap { @@ -31,8 +36,7 @@ struct SourceBreakpoint : public Breakpoint { uint32_t line; ///< The source line of the breakpoint or logpoint uint32_t column; ///< An optional source column of the breakpoint - SourceBreakpoint() : Breakpoint(), line(0), column(0) {} - SourceBreakpoint(const llvm::json::Object &obj); + SourceBreakpoint(DAP &d, const llvm::json::Object &obj); // Set this breakpoint in LLDB as a new breakpoint void SetBreakpoint(const llvm::StringRef source_path); diff --git a/lldb/tools/lldb-dap/Watchpoint.cpp b/lldb/tools/lldb-dap/Watchpoint.cpp index 2176550944914..0e68a35877c66 100644 --- a/lldb/tools/lldb-dap/Watchpoint.cpp +++ b/lldb/tools/lldb-dap/Watchpoint.cpp @@ -9,10 +9,17 @@ #include "Watchpoint.h" #include "DAP.h" #include "JSONUtils.h" +#include "lldb/API/SBTarget.h" +#include "lldb/lldb-enumerations.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" +#include +#include namespace lldb_dap { -Watchpoint::Watchpoint(const llvm::json::Object &obj) : BreakpointBase(obj) { +Watchpoint::Watchpoint(DAP &d, const llvm::json::Object &obj) + : BreakpointBase(d, obj) { llvm::StringRef dataId = GetString(obj, "dataId"); std::string accessType = GetString(obj, "accessType").str(); auto [addr_str, size_str] = dataId.split('/'); @@ -42,7 +49,7 @@ void Watchpoint::CreateJsonObject(llvm::json::Object &object) { } void Watchpoint::SetWatchpoint() { - wp = g_dap.target.WatchpointCreateByAddress(addr, size, options, error); + wp = dap.target.WatchpointCreateByAddress(addr, size, options, error); if (!condition.empty()) SetCondition(); if (!hitCondition.empty()) diff --git a/lldb/tools/lldb-dap/Watchpoint.h b/lldb/tools/lldb-dap/Watchpoint.h index 4d2e58ed75336..77cea67bb9781 100644 --- a/lldb/tools/lldb-dap/Watchpoint.h +++ b/lldb/tools/lldb-dap/Watchpoint.h @@ -10,9 +10,12 @@ #define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H #include "BreakpointBase.h" +#include "DAPForward.h" #include "lldb/API/SBError.h" #include "lldb/API/SBWatchpoint.h" #include "lldb/API/SBWatchpointOptions.h" +#include "lldb/lldb-types.h" +#include namespace lldb_dap { @@ -24,9 +27,8 @@ struct Watchpoint : public BreakpointBase { lldb::SBWatchpoint wp; lldb::SBError error; - Watchpoint() = default; - Watchpoint(const llvm::json::Object &obj); - Watchpoint(lldb::SBWatchpoint wp) : wp(wp) {} + Watchpoint(DAP &d, const llvm::json::Object &obj); + Watchpoint(DAP &d, lldb::SBWatchpoint wp) : BreakpointBase(d), wp(wp) {} void SetCondition() override; void SetHitCondition() override; diff --git a/lldb/tools/lldb-dap/lldb-dap.cpp b/lldb/tools/lldb-dap/lldb-dap.cpp index eab036c4e6cf1..b74bcaedb9152 100644 --- a/lldb/tools/lldb-dap/lldb-dap.cpp +++ b/lldb/tools/lldb-dap/lldb-dap.cpp @@ -7,20 +7,54 @@ //===----------------------------------------------------------------------===// #include "DAP.h" +#include "FifoFiles.h" +#include "JSONUtils.h" +#include "LLDBUtils.h" +#include "RunInTerminal.h" #include "Watchpoint.h" #include "lldb/API/SBDeclaration.h" +#include "lldb/API/SBEvent.h" +#include "lldb/API/SBInstruction.h" +#include "lldb/API/SBListener.h" #include "lldb/API/SBMemoryRegionInfo.h" +#include "lldb/API/SBStream.h" +#include "lldb/API/SBStringList.h" +#include "lldb/Host/Config.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" #include "llvm/Support/Base64.h" - +#include "llvm/Support/Errno.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/PrettyStackTrace.h" +#include "llvm/Support/raw_ostream.h" +#include +#include #include #include #include +#include #include #include #include +#include +#include +#include #include +#include #include #include +#include +#include + #if defined(_WIN32) // We need to #define NOMINMAX in order to skip `min()` and `max()` macro // definitions that conflict with other system headers. @@ -41,40 +75,6 @@ #include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "lldb/API/SBEnvironment.h" -#include "lldb/API/SBStream.h" -#include "lldb/Host/Config.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/ScopeExit.h" -#include "llvm/ADT/StringExtras.h" -#include "llvm/Option/Arg.h" -#include "llvm/Option/ArgList.h" -#include "llvm/Option/OptTable.h" -#include "llvm/Option/Option.h" -#include "llvm/Support/Errno.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/InitLLVM.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/PrettyStackTrace.h" -#include "llvm/Support/raw_ostream.h" - -#include "JSONUtils.h" -#include "LLDBUtils.h" -#include "OutputRedirector.h" - #if defined(_WIN32) #ifndef PATH_MAX #define PATH_MAX MAX_PATH @@ -120,34 +120,33 @@ constexpr int StackPageSize = 20; /// Prints a welcome message on the editor if the preprocessor variable /// LLDB_DAP_WELCOME_MESSAGE is defined. -static void PrintWelcomeMessage() { +static void PrintWelcomeMessage(DAP &dap) { #ifdef LLDB_DAP_WELCOME_MESSAGE - g_dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE); + dap.SendOutput(OutputType::Console, LLDB_DAP_WELCOME_MESSAGE); #endif } -lldb::SBValueList *GetTopLevelScope(int64_t variablesReference) { +lldb::SBValueList *GetTopLevelScope(DAP &dap, int64_t variablesReference) { switch (variablesReference) { case VARREF_LOCALS: - return &g_dap.variables.locals; + return &dap.variables.locals; case VARREF_GLOBALS: - return &g_dap.variables.globals; + return &dap.variables.globals; case VARREF_REGS: - return &g_dap.variables.registers; + return &dap.variables.registers; default: return nullptr; } } -SOCKET AcceptConnection(int portno) { +SOCKET AcceptConnection(std::ofstream *log, int portno) { // Accept a socket connection from any host on "portno". SOCKET newsockfd = -1; struct sockaddr_in serv_addr, cli_addr; SOCKET sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { - if (g_dap.log) - *g_dap.log << "error: opening socket (" << strerror(errno) << ")" - << std::endl; + if (log) + *log << "error: opening socket (" << strerror(errno) << ")" << std::endl; } else { memset((char *)&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; @@ -155,9 +154,9 @@ SOCKET AcceptConnection(int portno) { serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); serv_addr.sin_port = htons(portno); if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { - if (g_dap.log) - *g_dap.log << "error: binding socket (" << strerror(errno) << ")" - << std::endl; + if (log) + *log << "error: binding socket (" << strerror(errno) << ")" + << std::endl; } else { listen(sockfd, 5); socklen_t clilen = sizeof(cli_addr); @@ -165,9 +164,8 @@ SOCKET AcceptConnection(int portno) { llvm::sys::RetryAfterSignal(static_cast(-1), accept, sockfd, (struct sockaddr *)&cli_addr, &clilen); if (newsockfd < 0) - if (g_dap.log) - *g_dap.log << "error: accept (" << strerror(errno) << ")" - << std::endl; + if (log) + *log << "error: accept (" << strerror(errno) << ")" << std::endl; } #if defined(_WIN32) closesocket(sockfd); @@ -191,66 +189,65 @@ std::vector MakeArgv(const llvm::ArrayRef &strs) { } // Send a "exited" event to indicate the process has exited. -void SendProcessExitedEvent(lldb::SBProcess &process) { +void SendProcessExitedEvent(DAP &dap, lldb::SBProcess &process) { llvm::json::Object event(CreateEventObject("exited")); llvm::json::Object body; body.try_emplace("exitCode", (int64_t)process.GetExitStatus()); event.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(event))); + dap.SendJSON(llvm::json::Value(std::move(event))); } -void SendThreadExitedEvent(lldb::tid_t tid) { +void SendThreadExitedEvent(DAP &dap, lldb::tid_t tid) { llvm::json::Object event(CreateEventObject("thread")); llvm::json::Object body; body.try_emplace("reason", "exited"); body.try_emplace("threadId", (int64_t)tid); event.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(event))); + dap.SendJSON(llvm::json::Value(std::move(event))); } // Send a "continued" event to indicate the process is in the running state. -void SendContinuedEvent() { - lldb::SBProcess process = g_dap.target.GetProcess(); +void SendContinuedEvent(DAP &dap) { + lldb::SBProcess process = dap.target.GetProcess(); if (!process.IsValid()) { return; } // If the focus thread is not set then we haven't reported any thread status // to the client, so nothing to report. - if (!g_dap.configuration_done_sent || - g_dap.focus_tid == LLDB_INVALID_THREAD_ID) { + if (!dap.configuration_done_sent || dap.focus_tid == LLDB_INVALID_THREAD_ID) { return; } llvm::json::Object event(CreateEventObject("continued")); llvm::json::Object body; - body.try_emplace("threadId", (int64_t)g_dap.focus_tid); + body.try_emplace("threadId", (int64_t)dap.focus_tid); body.try_emplace("allThreadsContinued", true); event.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(event))); + dap.SendJSON(llvm::json::Value(std::move(event))); } // Send a "terminated" event to indicate the process is done being // debugged. -void SendTerminatedEvent() { +void SendTerminatedEvent(DAP &dap) { // Prevent races if the process exits while we're being asked to disconnect. - llvm::call_once(g_dap.terminated_event_flag, [&] { - g_dap.RunTerminateCommands(); + llvm::call_once(dap.terminated_event_flag, [&] { + dap.RunTerminateCommands(); // Send a "terminated" event - llvm::json::Object event(CreateTerminatedEventObject()); - g_dap.SendJSON(llvm::json::Value(std::move(event))); + llvm::json::Object event(CreateTerminatedEventObject(dap.target)); + dap.SendJSON(llvm::json::Value(std::move(event))); }); } // Send a thread stopped event for all threads as long as the process // is stopped. -void SendThreadStoppedEvent() { - lldb::SBProcess process = g_dap.target.GetProcess(); +void SendThreadStoppedEvent(DAP &dap) { + lldb::SBProcess process = dap.target.GetProcess(); if (process.IsValid()) { auto state = process.GetState(); if (state == lldb::eStateStopped) { llvm::DenseSet old_thread_ids; - old_thread_ids.swap(g_dap.thread_ids); + old_thread_ids.swap(dap.thread_ids); uint32_t stop_id = process.GetStopID(); const uint32_t num_threads = process.GetNumThreads(); @@ -266,10 +263,10 @@ void SendThreadStoppedEvent() { const lldb::tid_t tid = thread.GetThreadID(); const bool has_reason = ThreadHasStopReason(thread); // If the focus thread doesn't have a stop reason, clear the thread ID - if (tid == g_dap.focus_tid) { + if (tid == dap.focus_tid) { focus_thread_exists = true; if (!has_reason) - g_dap.focus_tid = LLDB_INVALID_THREAD_ID; + dap.focus_tid = LLDB_INVALID_THREAD_ID; } if (has_reason) { ++num_threads_with_reason; @@ -278,47 +275,46 @@ void SendThreadStoppedEvent() { } } - // We will have cleared g_dap.focus_tid if the focus thread doesn't have + // We will have cleared dap.focus_tid if the focus thread doesn't have // a stop reason, so if it was cleared, or wasn't set, or doesn't exist, // then set the focus thread to the first thread with a stop reason. - if (!focus_thread_exists || g_dap.focus_tid == LLDB_INVALID_THREAD_ID) - g_dap.focus_tid = first_tid_with_reason; + if (!focus_thread_exists || dap.focus_tid == LLDB_INVALID_THREAD_ID) + dap.focus_tid = first_tid_with_reason; // If no threads stopped with a reason, then report the first one so // we at least let the UI know we stopped. if (num_threads_with_reason == 0) { lldb::SBThread thread = process.GetThreadAtIndex(0); - g_dap.focus_tid = thread.GetThreadID(); - g_dap.SendJSON(CreateThreadStopped(thread, stop_id)); + dap.focus_tid = thread.GetThreadID(); + dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); } else { for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - g_dap.thread_ids.insert(thread.GetThreadID()); + dap.thread_ids.insert(thread.GetThreadID()); if (ThreadHasStopReason(thread)) { - g_dap.SendJSON(CreateThreadStopped(thread, stop_id)); + dap.SendJSON(CreateThreadStopped(dap, thread, stop_id)); } } } for (auto tid : old_thread_ids) { - auto end = g_dap.thread_ids.end(); - auto pos = g_dap.thread_ids.find(tid); + auto end = dap.thread_ids.end(); + auto pos = dap.thread_ids.find(tid); if (pos == end) - SendThreadExitedEvent(tid); + SendThreadExitedEvent(dap, tid); } } else { - if (g_dap.log) - *g_dap.log << "error: SendThreadStoppedEvent() when process" - " isn't stopped (" - << lldb::SBDebugger::StateAsCString(state) << ')' - << std::endl; + if (dap.log) + *dap.log << "error: SendThreadStoppedEvent() when process" + " isn't stopped (" + << lldb::SBDebugger::StateAsCString(state) << ')' << std::endl; } } else { - if (g_dap.log) - *g_dap.log << "error: SendThreadStoppedEvent() invalid process" - << std::endl; + if (dap.log) + *dap.log << "error: SendThreadStoppedEvent() invalid process" + << std::endl; } - g_dap.RunStopCommands(); + dap.RunStopCommands(); } // "ProcessEvent": { @@ -375,14 +371,14 @@ void SendThreadStoppedEvent() { // } // ] // } -void SendProcessEvent(LaunchMethod launch_method) { - lldb::SBFileSpec exe_fspec = g_dap.target.GetExecutable(); +void SendProcessEvent(DAP &dap, LaunchMethod launch_method) { + lldb::SBFileSpec exe_fspec = dap.target.GetExecutable(); char exe_path[PATH_MAX]; exe_fspec.GetPath(exe_path, sizeof(exe_path)); llvm::json::Object event(CreateEventObject("process")); llvm::json::Object body; EmplaceSafeString(body, "name", std::string(exe_path)); - const auto pid = g_dap.target.GetProcess().GetProcessID(); + const auto pid = dap.target.GetProcess().GetProcessID(); body.try_emplace("systemProcessId", (int64_t)pid); body.try_emplace("isLocalProcess", true); const char *startMethod = nullptr; @@ -399,32 +395,32 @@ void SendProcessEvent(LaunchMethod launch_method) { } body.try_emplace("startMethod", startMethod); event.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(event))); + dap.SendJSON(llvm::json::Value(std::move(event))); } // Grab any STDOUT and STDERR from the process and send it up to VS Code // via an "output" event to the "stdout" and "stderr" categories. -void SendStdOutStdErr(lldb::SBProcess &process) { +void SendStdOutStdErr(DAP &dap, lldb::SBProcess &process) { char buffer[OutputBufferSize]; size_t count; while ((count = process.GetSTDOUT(buffer, sizeof(buffer))) > 0) - g_dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); + dap.SendOutput(OutputType::Stdout, llvm::StringRef(buffer, count)); while ((count = process.GetSTDERR(buffer, sizeof(buffer))) > 0) - g_dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); + dap.SendOutput(OutputType::Stderr, llvm::StringRef(buffer, count)); } -void ProgressEventThreadFunction() { +void ProgressEventThreadFunction(DAP &dap) { lldb::SBListener listener("lldb-dap.progress.listener"); - g_dap.debugger.GetBroadcaster().AddListener( + dap.debugger.GetBroadcaster().AddListener( listener, lldb::SBDebugger::eBroadcastBitProgress | lldb::SBDebugger::eBroadcastBitExternalProgress); - g_dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); + dap.broadcaster.AddListener(listener, eBroadcastBitStopProgressThread); lldb::SBEvent event; bool done = false; while (!done) { if (listener.WaitForEvent(1, event)) { const auto event_mask = event.GetType(); - if (event.BroadcasterMatchesRef(g_dap.broadcaster)) { + if (event.BroadcasterMatchesRef(dap.broadcaster)) { if (event_mask & eBroadcastBitStopProgressThread) { done = true; } @@ -436,7 +432,7 @@ void ProgressEventThreadFunction() { const char *message = lldb::SBDebugger::GetProgressFromEvent( event, progress_id, completed, total, is_debugger_specific); if (message) - g_dap.SendProgressEvent(progress_id, message, completed, total); + dap.SendProgressEvent(progress_id, message, completed, total); } } } @@ -447,9 +443,9 @@ void ProgressEventThreadFunction() { // "FILE *" to output packets back to VS Code and they have mutexes in them // them prevent multiple threads from writing simultaneously so no locking // is required. -void EventThreadFunction() { +void EventThreadFunction(DAP &dap) { lldb::SBEvent event; - lldb::SBListener listener = g_dap.debugger.GetListener(); + lldb::SBListener listener = dap.debugger.GetListener(); bool done = false; while (!done) { if (listener.WaitForEvent(1, event)) { @@ -485,50 +481,50 @@ void EventThreadFunction() { // stop events which we do not want to send an event for. We will // manually send a stopped event in request_configurationDone(...) // so don't send any before then. - if (g_dap.configuration_done_sent) { + if (dap.configuration_done_sent) { // Only report a stopped event if the process was not // automatically restarted. if (!lldb::SBProcess::GetRestartedFromEvent(event)) { - SendStdOutStdErr(process); - SendThreadStoppedEvent(); + SendStdOutStdErr(dap, process); + SendThreadStoppedEvent(dap); } } break; case lldb::eStateRunning: - g_dap.WillContinue(); - SendContinuedEvent(); + dap.WillContinue(); + SendContinuedEvent(dap); break; case lldb::eStateExited: lldb::SBStream stream; process.GetStatus(stream); - g_dap.SendOutput(OutputType::Console, stream.GetData()); + dap.SendOutput(OutputType::Console, stream.GetData()); // When restarting, we can get an "exited" event for the process we // just killed with the old PID, or even with no PID. In that case // we don't have to terminate the session. if (process.GetProcessID() == LLDB_INVALID_PROCESS_ID || - process.GetProcessID() == g_dap.restarting_process_id) { - g_dap.restarting_process_id = LLDB_INVALID_PROCESS_ID; + process.GetProcessID() == dap.restarting_process_id) { + dap.restarting_process_id = LLDB_INVALID_PROCESS_ID; } else { // Run any exit LLDB commands the user specified in the // launch.json - g_dap.RunExitCommands(); - SendProcessExitedEvent(process); - SendTerminatedEvent(); + dap.RunExitCommands(); + SendProcessExitedEvent(dap, process); + SendTerminatedEvent(dap); done = true; } break; } } else if ((event_mask & lldb::SBProcess::eBroadcastBitSTDOUT) || (event_mask & lldb::SBProcess::eBroadcastBitSTDERR)) { - SendStdOutStdErr(process); + SendStdOutStdErr(dap, process); } } else if (lldb::SBBreakpoint::EventIsBreakpointEvent(event)) { if (event_mask & lldb::SBTarget::eBroadcastBitBreakpointChanged) { auto event_type = lldb::SBBreakpoint::GetBreakpointEventTypeFromEvent(event); - auto bp = - Breakpoint(lldb::SBBreakpoint::GetBreakpointFromEvent(event)); + auto bp = Breakpoint( + dap, lldb::SBBreakpoint::GetBreakpointFromEvent(event)); // If the breakpoint was originated from the IDE, it will have the // BreakpointBase::GetBreakpointLabel() label attached. Regardless // of wether the locations were added or removed, the breakpoint @@ -550,10 +546,10 @@ void EventThreadFunction() { body.try_emplace("breakpoint", source_bp); body.try_emplace("reason", "changed"); bp_event.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(bp_event))); + dap.SendJSON(llvm::json::Value(std::move(bp_event))); } } - } else if (event.BroadcasterMatchesRef(g_dap.broadcaster)) { + } else if (event.BroadcasterMatchesRef(dap.broadcaster)) { if (event_mask & eBroadcastBitStopEventThread) { done = true; } @@ -562,9 +558,11 @@ void EventThreadFunction() { } } -lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name) { +lldb::SBValue FindVariable(DAP &dap, uint64_t variablesReference, + llvm::StringRef name) { lldb::SBValue variable; - if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) { + if (lldb::SBValueList *top_scope = + GetTopLevelScope(dap, variablesReference)) { bool is_duplicated_variable_name = name.contains(" @"); // variablesReference is one of our scopes, not an actual variable it is // asking for a variable in locals or globals or registers @@ -586,7 +584,7 @@ lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name) { // We have a named item within an actual variable so we need to find it // withing the container variable by name. - lldb::SBValue container = g_dap.variables.GetVariable(variablesReference); + lldb::SBValue container = dap.variables.GetVariable(variablesReference); variable = container.GetChildMemberWithName(name.data()); if (!variable.IsValid()) { if (name.starts_with("[")) { @@ -604,7 +602,7 @@ lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name) { // Both attach and launch take a either a sourcePath or sourceMap // argument (or neither), from which we need to set the target.source-map. -void SetSourceMapFromArguments(const llvm::json::Object &arguments) { +void SetSourceMapFromArguments(DAP &dap, const llvm::json::Object &arguments) { const char *sourceMapHelp = "source must be be an array of two-element arrays, " "each containing a source and replacement path string.\n"; @@ -623,7 +621,7 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) { if (mapping == nullptr || mapping->size() != 2 || (*mapping)[0].kind() != llvm::json::Value::String || (*mapping)[1].kind() != llvm::json::Value::String) { - g_dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); return; } const auto mapFrom = GetAsString((*mapping)[0]); @@ -638,7 +636,7 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) { } } else { if (ObjectContainsKey(arguments, sourceMapKey)) { - g_dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); + dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp)); return; } if (sourcePath.empty()) @@ -648,7 +646,7 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) { } strm.flush(); if (!sourceMapCommand.empty()) { - g_dap.RunLLDBCommands("Setting source map:", {sourceMapCommand}); + dap.RunLLDBCommands("Setting source map:", {sourceMapCommand}); } } @@ -684,14 +682,15 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) { // 13. th3->s2 // // s=3,l=3 = [th0->s3, label1, th1->s0] -bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames, - int64_t &offset, const int64_t start_frame, - const int64_t levels) { +bool FillStackFrames(DAP &dap, lldb::SBThread &thread, + llvm::json::Array &stack_frames, int64_t &offset, + const int64_t start_frame, const int64_t levels) { bool reached_end_of_stack = false; for (int64_t i = start_frame; static_cast(stack_frames.size()) < levels; i++) { if (i == -1) { - stack_frames.emplace_back(CreateExtendedStackFrameLabel(thread)); + stack_frames.emplace_back( + CreateExtendedStackFrameLabel(thread, dap.frame_format)); continue; } @@ -702,10 +701,10 @@ bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames, break; } - stack_frames.emplace_back(CreateStackFrame(frame)); + stack_frames.emplace_back(CreateStackFrame(frame, dap.frame_format)); } - if (g_dap.display_extended_backtrace && reached_end_of_stack) { + if (dap.display_extended_backtrace && reached_end_of_stack) { // Check for any extended backtraces. for (uint32_t bt = 0; bt < thread.GetProcess().GetNumExtendedBacktraceTypes(); bt++) { @@ -715,7 +714,7 @@ bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames, continue; reached_end_of_stack = FillStackFrames( - backtrace, stack_frames, offset, + dap, backtrace, stack_frames, offset, (start_frame - offset) > 0 ? start_frame - offset : -1, levels); if (static_cast(stack_frames.size()) >= levels) break; @@ -753,15 +752,15 @@ bool FillStackFrames(lldb::SBThread &thread, llvm::json::Array &stack_frames, // acknowledgement, so no body field is required." // }] // } -void request_attach(const llvm::json::Object &request) { - g_dap.is_attach = true; - g_dap.last_launch_or_attach_request = request; +void request_attach(DAP &dap, const llvm::json::Object &request) { + dap.is_attach = true; + dap.last_launch_or_attach_request = request; llvm::json::Object response; lldb::SBError error; FillResponse(request, response); lldb::SBAttachInfo attach_info; const int invalid_port = 0; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); const lldb::pid_t pid = GetUnsigned(arguments, "pid", LLDB_INVALID_PROCESS_ID); const auto gdb_remote_port = @@ -772,30 +771,29 @@ void request_attach(const llvm::json::Object &request) { attach_info.SetProcessID(pid); const auto wait_for = GetBoolean(arguments, "waitFor", false); attach_info.SetWaitForLaunch(wait_for, false /*async*/); - g_dap.init_commands = GetStrings(arguments, "initCommands"); - g_dap.pre_run_commands = GetStrings(arguments, "preRunCommands"); - g_dap.stop_commands = GetStrings(arguments, "stopCommands"); - g_dap.exit_commands = GetStrings(arguments, "exitCommands"); - g_dap.terminate_commands = GetStrings(arguments, "terminateCommands"); + dap.init_commands = GetStrings(arguments, "initCommands"); + dap.pre_run_commands = GetStrings(arguments, "preRunCommands"); + dap.stop_commands = GetStrings(arguments, "stopCommands"); + dap.exit_commands = GetStrings(arguments, "exitCommands"); + dap.terminate_commands = GetStrings(arguments, "terminateCommands"); auto attachCommands = GetStrings(arguments, "attachCommands"); llvm::StringRef core_file = GetString(arguments, "coreFile"); const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30); - g_dap.stop_at_entry = + dap.stop_at_entry = core_file.empty() ? GetBoolean(arguments, "stopOnEntry", false) : true; - g_dap.post_run_commands = GetStrings(arguments, "postRunCommands"); + dap.post_run_commands = GetStrings(arguments, "postRunCommands"); const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot"); - g_dap.enable_auto_variable_summaries = + dap.enable_auto_variable_summaries = GetBoolean(arguments, "enableAutoVariableSummaries", false); - g_dap.enable_synthetic_child_debugging = + dap.enable_synthetic_child_debugging = GetBoolean(arguments, "enableSyntheticChildDebugging", false); - g_dap.display_extended_backtrace = + dap.display_extended_backtrace = GetBoolean(arguments, "displayExtendedBacktrace", false); - g_dap.command_escape_prefix = - GetString(arguments, "commandEscapePrefix", "`"); - g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); - g_dap.SetThreadFormat(GetString(arguments, "customThreadFormat")); + dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`"); + dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); + dap.SetThreadFormat(GetString(arguments, "customThreadFormat")); - PrintWelcomeMessage(); + PrintWelcomeMessage(dap); // This is a hack for loading DWARF in .o files on Mac where the .o files // in the debug map of the main executable have relative paths which require @@ -805,29 +803,29 @@ void request_attach(const llvm::json::Object &request) { llvm::sys::fs::set_current_path(debuggerRoot); // Run any initialize LLDB commands the user specified in the launch.json - if (llvm::Error err = g_dap.RunInitCommands()) { + if (llvm::Error err = dap.RunInitCommands()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } - SetSourceMapFromArguments(*arguments); + SetSourceMapFromArguments(dap, *arguments); lldb::SBError status; - g_dap.SetTarget(g_dap.CreateTargetFromArguments(*arguments, status)); + dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status)); if (status.Fail()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", status.GetCString()); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } // Run any pre run LLDB commands the user specified in the launch.json - if (llvm::Error err = g_dap.RunPreRunCommands()) { + if (llvm::Error err = dap.RunPreRunCommands()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -836,15 +834,15 @@ void request_attach(const llvm::json::Object &request) { char attach_msg[256]; auto attach_msg_len = snprintf(attach_msg, sizeof(attach_msg), "Waiting to attach to \"%s\"...", - g_dap.target.GetExecutable().GetFilename()); - g_dap.SendOutput(OutputType::Console, - llvm::StringRef(attach_msg, attach_msg_len)); + dap.target.GetExecutable().GetFilename()); + dap.SendOutput(OutputType::Console, + llvm::StringRef(attach_msg, attach_msg_len)); } if (attachCommands.empty()) { // No "attachCommands", just attach normally. // Disable async events so the attach will be successful when we return from // the launch call and the launch will happen synchronously - g_dap.debugger.SetAsync(false); + dap.debugger.SetAsync(false); if (core_file.empty()) { if ((pid != LLDB_INVALID_PROCESS_ID) && (gdb_remote_port != invalid_port)) { @@ -852,44 +850,44 @@ void request_attach(const llvm::json::Object &request) { error.SetErrorString("The user can't specify both pid and port"); } else if (gdb_remote_port != invalid_port) { // If port is specified and pid is not. - lldb::SBListener listener = g_dap.debugger.GetListener(); + lldb::SBListener listener = dap.debugger.GetListener(); // If the user hasn't provided the hostname property, default localhost // being used. std::string connect_url = llvm::formatv("connect://{0}:", gdb_remote_hostname); connect_url += std::to_string(gdb_remote_port); - g_dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", - error); + dap.target.ConnectRemote(listener, connect_url.c_str(), "gdb-remote", + error); } else { // Attach by process name or id. - g_dap.target.Attach(attach_info, error); + dap.target.Attach(attach_info, error); } } else - g_dap.target.LoadCore(core_file.data(), error); + dap.target.LoadCore(core_file.data(), error); // Reenable async events - g_dap.debugger.SetAsync(true); + dap.debugger.SetAsync(true); } else { // We have "attachCommands" that are a set of commands that are expected // to execute the commands after which a process should be created. If there // is no valid process after running these commands, we have failed. - if (llvm::Error err = g_dap.RunAttachCommands(attachCommands)) { + if (llvm::Error err = dap.RunAttachCommands(attachCommands)) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } // The custom commands might have created a new target so we should use the // selected target after these commands are run. - g_dap.target = g_dap.debugger.GetSelectedTarget(); + dap.target = dap.debugger.GetSelectedTarget(); // Make sure the process is attached and stopped before proceeding as the // the launch commands are not run using the synchronous mode. - error = g_dap.WaitForProcessToStop(timeout_seconds); + error = dap.WaitForProcessToStop(timeout_seconds); } if (error.Success() && core_file.empty()) { - auto attached_pid = g_dap.target.GetProcess().GetProcessID(); + auto attached_pid = dap.target.GetProcess().GetProcessID(); if (attached_pid == LLDB_INVALID_PROCESS_ID) { if (attachCommands.empty()) error.SetErrorString("failed to attach to a process"); @@ -902,16 +900,206 @@ void request_attach(const llvm::json::Object &request) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", std::string(error.GetCString())); } else { - g_dap.RunPostRunCommands(); + dap.RunPostRunCommands(); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); if (error.Success()) { - SendProcessEvent(Attach); - g_dap.SendJSON(CreateEventObject("initialized")); + SendProcessEvent(dap, Attach); + dap.SendJSON(CreateEventObject("initialized")); } } +// "BreakpointLocationsRequest": { +// "allOf": [ { "$ref": "#/definitions/Request" }, { +// "type": "object", +// "description": "The `breakpointLocations` request returns all possible +// locations for source breakpoints in a given range.\nClients should only +// call this request if the corresponding capability +// `supportsBreakpointLocationsRequest` is true.", +// "properties": { +// "command": { +// "type": "string", +// "enum": [ "breakpointLocations" ] +// }, +// "arguments": { +// "$ref": "#/definitions/BreakpointLocationsArguments" +// } +// }, +// "required": [ "command" ] +// }] +// }, +// "BreakpointLocationsArguments": { +// "type": "object", +// "description": "Arguments for `breakpointLocations` request.", +// "properties": { +// "source": { +// "$ref": "#/definitions/Source", +// "description": "The source location of the breakpoints; either +// `source.path` or `source.sourceReference` must be specified." +// }, +// "line": { +// "type": "integer", +// "description": "Start line of range to search possible breakpoint +// locations in. If only the line is specified, the request returns all +// possible locations in that line." +// }, +// "column": { +// "type": "integer", +// "description": "Start position within `line` to search possible +// breakpoint locations in. It is measured in UTF-16 code units and the +// client capability `columnsStartAt1` determines whether it is 0- or +// 1-based. If no column is given, the first position in the start line is +// assumed." +// }, +// "endLine": { +// "type": "integer", +// "description": "End line of range to search possible breakpoint +// locations in. If no end line is given, then the end line is assumed to +// be the start line." +// }, +// "endColumn": { +// "type": "integer", +// "description": "End position within `endLine` to search possible +// breakpoint locations in. It is measured in UTF-16 code units and the +// client capability `columnsStartAt1` determines whether it is 0- or +// 1-based. If no end column is given, the last position in the end line +// is assumed." +// } +// }, +// "required": [ "source", "line" ] +// }, +// "BreakpointLocationsResponse": { +// "allOf": [ { "$ref": "#/definitions/Response" }, { +// "type": "object", +// "description": "Response to `breakpointLocations` request.\nContains +// possible locations for source breakpoints.", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": { +// "$ref": "#/definitions/BreakpointLocation" +// }, +// "description": "Sorted set of possible breakpoint locations." +// } +// }, +// "required": [ "breakpoints" ] +// } +// }, +// "required": [ "body" ] +// }] +// }, +// "BreakpointLocation": { +// "type": "object", +// "description": "Properties of a breakpoint location returned from the +// `breakpointLocations` request.", +// "properties": { +// "line": { +// "type": "integer", +// "description": "Start line of breakpoint location." +// }, +// "column": { +// "type": "integer", +// "description": "The start position of a breakpoint location. Position +// is measured in UTF-16 code units and the client capability +// `columnsStartAt1` determines whether it is 0- or 1-based." +// }, +// "endLine": { +// "type": "integer", +// "description": "The end line of breakpoint location if the location +// covers a range." +// }, +// "endColumn": { +// "type": "integer", +// "description": "The end position of a breakpoint location (if the +// location covers a range). Position is measured in UTF-16 code units and +// the client capability `columnsStartAt1` determines whether it is 0- or +// 1-based." +// } +// }, +// "required": [ "line" ] +// }, +void request_breakpointLocations(DAP &dap, const llvm::json::Object &request) { + llvm::json::Object response; + FillResponse(request, response); + auto *arguments = request.getObject("arguments"); + auto *source = arguments->getObject("source"); + std::string path = GetString(source, "path").str(); + uint64_t start_line = GetUnsigned(arguments, "line", 0); + uint64_t start_column = GetUnsigned(arguments, "column", 0); + uint64_t end_line = GetUnsigned(arguments, "endLine", start_line); + uint64_t end_column = + GetUnsigned(arguments, "endColumn", std::numeric_limits::max()); + + lldb::SBFileSpec file_spec(path.c_str(), true); + lldb::SBSymbolContextList compile_units = + dap.target.FindCompileUnits(file_spec); + + // Find all relevant lines & columns + llvm::SmallVector, 8> locations; + for (uint32_t c_idx = 0, c_limit = compile_units.GetSize(); c_idx < c_limit; + ++c_idx) { + const lldb::SBCompileUnit &compile_unit = + compile_units.GetContextAtIndex(c_idx).GetCompileUnit(); + if (!compile_unit.IsValid()) + continue; + lldb::SBFileSpec primary_file_spec = compile_unit.GetFileSpec(); + + // Go through the line table and find all matching lines / columns + for (uint32_t l_idx = 0, l_limit = compile_unit.GetNumLineEntries(); + l_idx < l_limit; ++l_idx) { + lldb::SBLineEntry line_entry = compile_unit.GetLineEntryAtIndex(l_idx); + + // Filter by line / column + uint32_t line = line_entry.GetLine(); + if (line < start_line || line > end_line) + continue; + uint32_t column = line_entry.GetColumn(); + if (column == LLDB_INVALID_COLUMN_NUMBER) + continue; + if (line == start_line && column < start_column) + continue; + if (line == end_line && column > end_column) + continue; + + // Make sure we are in the right file. + // We might have a match on line & column range and still + // be in the wrong file, e.g. for included files. + // Given that the involved pointers point into LLDB's string pool, + // we can directly compare the `const char*` pointers. + if (line_entry.GetFileSpec().GetFilename() != + primary_file_spec.GetFilename() || + line_entry.GetFileSpec().GetDirectory() != + primary_file_spec.GetDirectory()) + continue; + + locations.emplace_back(line, column); + } + } + + // The line entries are sorted by addresses, but we must return the list + // ordered by line / column position. + std::sort(locations.begin(), locations.end()); + locations.erase(std::unique(locations.begin(), locations.end()), + locations.end()); + + llvm::json::Array locations_json; + for (auto &l : locations) { + llvm::json::Object location; + location.try_emplace("line", l.first); + location.try_emplace("column", l.second); + locations_json.emplace_back(std::move(location)); + } + + llvm::json::Object body; + body.try_emplace("breakpoints", std::move(locations_json)); + response.try_emplace("body", std::move(body)); + dap.SendJSON(llvm::json::Value(std::move(response))); +} + // "ContinueRequest": { // "allOf": [ { "$ref": "#/definitions/Request" }, { // "type": "object", @@ -966,15 +1154,15 @@ void request_attach(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_continue(const llvm::json::Object &request) { +void request_continue(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - lldb::SBProcess process = g_dap.target.GetProcess(); + lldb::SBProcess process = dap.target.GetProcess(); lldb::SBError error = process.Continue(); llvm::json::Object body; body.try_emplace("allThreadsContinued", true); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "ConfigurationDoneRequest": { @@ -1008,15 +1196,15 @@ void request_continue(const llvm::json::Object &request) { // just an acknowledgement, so no body field is required." // }] // }, -void request_configurationDone(const llvm::json::Object &request) { +void request_configurationDone(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - g_dap.SendJSON(llvm::json::Value(std::move(response))); - g_dap.configuration_done_sent = true; - if (g_dap.stop_at_entry) - SendThreadStoppedEvent(); + dap.SendJSON(llvm::json::Value(std::move(response))); + dap.configuration_done_sent = true; + if (dap.stop_at_entry) + SendThreadStoppedEvent(dap); else - g_dap.target.GetProcess().Continue(); + dap.target.GetProcess().Continue(); } // "DisconnectRequest": { @@ -1063,15 +1251,15 @@ void request_configurationDone(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // } -void request_disconnect(const llvm::json::Object &request) { +void request_disconnect(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); - bool defaultTerminateDebuggee = g_dap.is_attach ? false : true; + bool defaultTerminateDebuggee = dap.is_attach ? false : true; bool terminateDebuggee = GetBoolean(arguments, "terminateDebuggee", defaultTerminateDebuggee); - lldb::SBProcess process = g_dap.target.GetProcess(); + lldb::SBProcess process = dap.target.GetProcess(); auto state = process.GetState(); switch (state) { case lldb::eStateInvalid: @@ -1087,24 +1275,25 @@ void request_disconnect(const llvm::json::Object &request) { case lldb::eStateSuspended: case lldb::eStateStopped: case lldb::eStateRunning: - g_dap.debugger.SetAsync(false); + dap.debugger.SetAsync(false); lldb::SBError error = terminateDebuggee ? process.Kill() : process.Detach(); if (!error.Success()) EmplaceSafeString(response, "error", error.GetCString()); - g_dap.debugger.SetAsync(true); + dap.debugger.SetAsync(true); break; } - SendTerminatedEvent(); - g_dap.SendJSON(llvm::json::Value(std::move(response))); - if (g_dap.event_thread.joinable()) { - g_dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); - g_dap.event_thread.join(); + SendTerminatedEvent(dap); + dap.SendJSON(llvm::json::Value(std::move(response))); + if (dap.event_thread.joinable()) { + dap.broadcaster.BroadcastEventByType(eBroadcastBitStopEventThread); + dap.event_thread.join(); } - if (g_dap.progress_event_thread.joinable()) { - g_dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread); - g_dap.progress_event_thread.join(); + if (dap.progress_event_thread.joinable()) { + dap.broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread); + dap.progress_event_thread.join(); } - g_dap.disconnecting = true; + dap.StopIO(); + dap.disconnecting = true; } // "ExceptionInfoRequest": { @@ -1204,18 +1393,18 @@ void request_disconnect(const llvm::json::Object &request) { // } // } // }, -void request_exceptionInfo(const llvm::json::Object &request) { +void request_exceptionInfo(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); llvm::json::Object body; - lldb::SBThread thread = g_dap.GetLLDBThread(*arguments); + lldb::SBThread thread = dap.GetLLDBThread(*arguments); if (thread.IsValid()) { auto stopReason = thread.GetStopReason(); if (stopReason == lldb::eStopReasonSignal) body.try_emplace("exceptionId", "signal"); else if (stopReason == lldb::eStopReasonBreakpoint) { - ExceptionBreakpoint *exc_bp = g_dap.GetExceptionBPFromStopReason(thread); + ExceptionBreakpoint *exc_bp = dap.GetExceptionBPFromStopReason(thread); if (exc_bp) { EmplaceSafeString(body, "exceptionId", exc_bp->filter); EmplaceSafeString(body, "description", exc_bp->label); @@ -1261,7 +1450,7 @@ void request_exceptionInfo(const llvm::json::Object &request) { response["success"] = llvm::json::Value(false); } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "CompletionsRequest": { @@ -1379,14 +1568,14 @@ void request_exceptionInfo(const llvm::json::Object &request) { // "interface", "module", "property", "unit", "value", "enum", "keyword", // "snippet", "text", "color", "file", "reference", "customcolor" ] // } -void request_completions(const llvm::json::Object &request) { +void request_completions(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); // If we have a frame, try to set the context for variable completions. - lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments); + lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); if (frame.IsValid()) { frame.GetThread().GetProcess().SetSelectedThread(frame.GetThread()); frame.GetThread().SetSelectedFrame(frame.GetFrameID()); @@ -1406,19 +1595,19 @@ void request_completions(const llvm::json::Object &request) { llvm::json::Array targets; bool had_escape_prefix = - llvm::StringRef(text).starts_with(g_dap.command_escape_prefix); - ReplMode completion_mode = g_dap.DetectReplMode(frame, text, true); + llvm::StringRef(text).starts_with(dap.command_escape_prefix); + ReplMode completion_mode = dap.DetectReplMode(frame, text, true); // Handle the offset change introduced by stripping out the // `command_escape_prefix`. if (had_escape_prefix) { - if (offset < static_cast(g_dap.command_escape_prefix.size())) { + if (offset < static_cast(dap.command_escape_prefix.size())) { body.try_emplace("targets", std::move(targets)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } - offset -= g_dap.command_escape_prefix.size(); + offset -= dap.command_escape_prefix.size(); } // While the user is typing then we likely have an incomplete input and cannot @@ -1435,9 +1624,8 @@ void request_completions(const llvm::json::Object &request) { lldb::SBStringList matches; lldb::SBStringList descriptions; - if (!g_dap.debugger.GetCommandInterpreter() - .HandleCompletionWithDescriptions(line.c_str(), cursor, 0, 100, - matches, descriptions)) + if (!dap.debugger.GetCommandInterpreter().HandleCompletionWithDescriptions( + line.c_str(), cursor, 0, 100, matches, descriptions)) continue; // The first element is the common substring after the cursor position for @@ -1467,7 +1655,7 @@ void request_completions(const llvm::json::Object &request) { body.try_emplace("targets", std::move(targets)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "EvaluateRequest": { @@ -1594,31 +1782,31 @@ void request_completions(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_evaluate(const llvm::json::Object &request) { +void request_evaluate(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; - auto arguments = request.getObject("arguments"); - lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments); + const auto *arguments = request.getObject("arguments"); + lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); std::string expression = GetString(arguments, "expression").str(); llvm::StringRef context = GetString(arguments, "context"); bool repeat_last_command = - expression.empty() && g_dap.last_nonempty_var_expression.empty(); + expression.empty() && dap.last_nonempty_var_expression.empty(); if (context == "repl" && (repeat_last_command || (!expression.empty() && - g_dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) { + dap.DetectReplMode(frame, expression, false) == ReplMode::Command))) { // Since the current expression is not for a variable, clear the // last_nonempty_var_expression field. - g_dap.last_nonempty_var_expression.clear(); + dap.last_nonempty_var_expression.clear(); // If we're evaluating a command relative to the current frame, set the // focus_tid to the current frame for any thread related events. if (frame.IsValid()) { - g_dap.focus_tid = frame.GetThread().GetThreadID(); + dap.focus_tid = frame.GetThread().GetThreadID(); } - auto result = - RunLLDBCommandsVerbatim(llvm::StringRef(), {std::string(expression)}); + auto result = RunLLDBCommandsVerbatim(dap.debugger, llvm::StringRef(), + {std::string(expression)}); EmplaceSafeString(body, "result", result); body.try_emplace("variablesReference", (int64_t)0); } else { @@ -1628,9 +1816,9 @@ void request_evaluate(const llvm::json::Object &request) { // evaluation); otherwise save the current non-empty expression for the // next (possibly empty) variable expression. if (expression.empty()) - expression = g_dap.last_nonempty_var_expression; + expression = dap.last_nonempty_var_expression; else - g_dap.last_nonempty_var_expression = expression; + dap.last_nonempty_var_expression = expression; } // Always try to get the answer from the local variables if possible. If // this fails, then if the context is not "hover", actually evaluate an @@ -1659,12 +1847,12 @@ void request_evaluate(const llvm::json::Object &request) { else EmplaceSafeString(response, "message", "evaluate failed"); } else { - VariableDescription desc(value); + VariableDescription desc(value, dap.enable_auto_variable_summaries); EmplaceSafeString(body, "result", desc.GetResult(context)); EmplaceSafeString(body, "type", desc.display_type_name); int64_t var_ref = 0; if (value.MightHaveChildren() || ValuePointsToCode(value)) - var_ref = g_dap.variables.InsertVariable( + var_ref = dap.variables.InsertVariable( value, /*is_permanent=*/context == "repl"); if (value.MightHaveChildren()) body.try_emplace("variablesReference", var_ref); @@ -1678,7 +1866,7 @@ void request_evaluate(const llvm::json::Object &request) { } } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "compileUnitsRequest": { @@ -1721,16 +1909,16 @@ void request_evaluate(const llvm::json::Object &request) { // } // }] // } -void request_compileUnits(const llvm::json::Object &request) { +void request_compileUnits(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; llvm::json::Array units; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); std::string module_id = std::string(GetString(arguments, "moduleId")); - int num_modules = g_dap.target.GetNumModules(); + int num_modules = dap.target.GetNumModules(); for (int i = 0; i < num_modules; i++) { - auto curr_module = g_dap.target.GetModuleAtIndex(i); + auto curr_module = dap.target.GetModuleAtIndex(i); if (module_id == curr_module.GetUUIDString()) { int num_units = curr_module.GetNumCompileUnits(); for (int j = 0; j < num_units; j++) { @@ -1742,7 +1930,7 @@ void request_compileUnits(const llvm::json::Object &request) { } } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "modulesRequest": { @@ -1771,20 +1959,20 @@ void request_compileUnits(const llvm::json::Object &request) { // } // }] // } -void request_modules(const llvm::json::Object &request) { +void request_modules(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Array modules; - for (size_t i = 0; i < g_dap.target.GetNumModules(); i++) { - lldb::SBModule module = g_dap.target.GetModuleAtIndex(i); - modules.emplace_back(CreateModule(module)); + for (size_t i = 0; i < dap.target.GetNumModules(); i++) { + lldb::SBModule module = dap.target.GetModuleAtIndex(i); + modules.emplace_back(CreateModule(dap.target, module)); } llvm::json::Object body; body.try_emplace("modules", std::move(modules)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "InitializeRequest": { @@ -1863,49 +2051,75 @@ void request_modules(const llvm::json::Object &request) { // } // }] // } -void request_initialize(const llvm::json::Object &request) { +void request_initialize(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; - auto log_cb = [](const char *buf, void *baton) -> void { - g_dap.SendOutput(OutputType::Console, llvm::StringRef{buf}); - }; - - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); // sourceInitFile option is not from formal DAP specification. It is only // used by unit tests to prevent sourcing .lldbinit files from environment // which may affect the outcome of tests. bool source_init_file = GetBoolean(arguments, "sourceInitFile", true); - g_dap.debugger = lldb::SBDebugger::Create(source_init_file, log_cb, nullptr); - if (llvm::Error err = g_dap.RunPreInitCommands()) { + // Do not source init files until in/out/err are configured. + dap.debugger = lldb::SBDebugger::Create(false); + dap.debugger.SetInputFile(dap.in); + auto out_fd = dap.out.GetWriteFileDescriptor(); + if (llvm::Error err = out_fd.takeError()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } + dap.debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false)); + auto err_fd = dap.err.GetWriteFileDescriptor(); + if (llvm::Error err = err_fd.takeError()) { + response["success"] = false; + EmplaceSafeString(response, "message", llvm::toString(std::move(err))); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } + dap.debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false)); + + auto interp = dap.debugger.GetCommandInterpreter(); - g_dap.PopulateExceptionBreakpoints(); - auto cmd = g_dap.debugger.GetCommandInterpreter().AddMultiwordCommand( + if (source_init_file) { + dap.debugger.SkipLLDBInitFiles(false); + dap.debugger.SkipAppInitFiles(false); + lldb::SBCommandReturnObject init; + interp.SourceInitFileInGlobalDirectory(init); + interp.SourceInitFileInHomeDirectory(init); + } + + if (llvm::Error err = dap.RunPreInitCommands()) { + response["success"] = false; + EmplaceSafeString(response, "message", llvm::toString(std::move(err))); + dap.SendJSON(llvm::json::Value(std::move(response))); + return; + } + + dap.PopulateExceptionBreakpoints(); + auto cmd = dap.debugger.GetCommandInterpreter().AddMultiwordCommand( "lldb-dap", "Commands for managing lldb-dap."); if (GetBoolean(arguments, "supportsStartDebuggingRequest", false)) { cmd.AddCommand( - "start-debugging", new StartDebuggingRequestHandler(), + "start-debugging", new StartDebuggingRequestHandler(dap), "Sends a startDebugging request from the debug adapter to the client " "to start a child debug session of the same type as the caller."); } cmd.AddCommand( - "repl-mode", new ReplModeRequestHandler(), + "repl-mode", new ReplModeRequestHandler(dap), "Get or set the repl behavior of lldb-dap evaluation requests."); - cmd.AddCommand("send-event", new SendEventRequestHandler(), + cmd.AddCommand("send-event", new SendEventRequestHandler(dap), "Sends an DAP event to the client."); - g_dap.progress_event_thread = std::thread(ProgressEventThreadFunction); + dap.progress_event_thread = + std::thread(ProgressEventThreadFunction, std::ref(dap)); // Start our event thread so we can receive events from the debugger, target, // process and more. - g_dap.event_thread = std::thread(EventThreadFunction); + dap.event_thread = std::thread(EventThreadFunction, std::ref(dap)); // The debug adapter supports the configurationDoneRequest. body.try_emplace("supportsConfigurationDoneRequest", true); @@ -1921,7 +2135,7 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsEvaluateForHovers", true); // Available filters or options for the setExceptionBreakpoints request. llvm::json::Array filters; - for (const auto &exc_bp : *g_dap.exception_breakpoints) { + for (const auto &exc_bp : *dap.exception_breakpoints) { filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp)); } body.try_emplace("exceptionBreakpointFilters", std::move(filters)); @@ -1943,6 +2157,8 @@ void request_initialize(const llvm::json::Object &request) { body.try_emplace("supportsCompletionsRequest", true); // The debug adapter supports the disassembly request. body.try_emplace("supportsDisassembleRequest", true); + // The debug adapter supports the `breakpointLocations` request. + body.try_emplace("supportsBreakpointLocationsRequest", true); // The debug adapter supports stepping granularities (argument `granularity`) // for the stepping requests. body.try_emplace("supportsSteppingGranularity", true); @@ -1996,16 +2212,17 @@ void request_initialize(const llvm::json::Object &request) { // Put in non-DAP specification lldb specific information. llvm::json::Object lldb_json; - lldb_json.try_emplace("version", g_dap.debugger.GetVersionString()); + lldb_json.try_emplace("version", dap.debugger.GetVersionString()); body.try_emplace("__lldb", std::move(lldb_json)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } -llvm::Error request_runInTerminal(const llvm::json::Object &launch_request, +llvm::Error request_runInTerminal(DAP &dap, + const llvm::json::Object &launch_request, const uint64_t timeout_seconds) { - g_dap.is_attach = true; + dap.is_attach = true; lldb::SBAttachInfo attach_info; llvm::Expected> comm_file_or_err = @@ -2021,25 +2238,25 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request, debugger_pid = getpid(); #endif llvm::json::Object reverse_request = CreateRunInTerminalReverseRequest( - launch_request, g_dap.debug_adaptor_path, comm_file.m_path, debugger_pid); - g_dap.SendReverseRequest("runInTerminal", std::move(reverse_request), - [](llvm::Expected value) { - if (!value) { - llvm::Error err = value.takeError(); - llvm::errs() - << "runInTerminal request failed: " - << llvm::toString(std::move(err)) << "\n"; - } - }); + launch_request, dap.debug_adaptor_path, comm_file.m_path, debugger_pid); + dap.SendReverseRequest("runInTerminal", std::move(reverse_request), + [](llvm::Expected value) { + if (!value) { + llvm::Error err = value.takeError(); + llvm::errs() + << "runInTerminal request failed: " + << llvm::toString(std::move(err)) << "\n"; + } + }); if (llvm::Expected pid = comm_channel.GetLauncherPid()) attach_info.SetProcessID(*pid); else return pid.takeError(); - g_dap.debugger.SetAsync(false); + dap.debugger.SetAsync(false); lldb::SBError error; - g_dap.target.Attach(attach_info, error); + dap.target.Attach(attach_info, error); if (error.Fail()) return llvm::createStringError(llvm::inconvertibleErrorCode(), @@ -2057,11 +2274,11 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request, // process right in the middle of the exec. To the user, what we are doing is // transparent, as they will only be able to see the process since the exec, // completely unaware of the preparatory work. - g_dap.target.GetProcess().Continue(); + dap.target.GetProcess().Continue(); // Now that the actual target is just starting (i.e. exec was just invoked), // we return the debugger to its async state. - g_dap.debugger.SetAsync(true); + dap.debugger.SetAsync(true); // If sending the notification failed, the launcher should be dead by now and // the async didAttach notification should have an error message, so we @@ -2078,13 +2295,13 @@ llvm::Error request_runInTerminal(const llvm::json::Object &launch_request, // runInTerminal if applicable. It doesn't do any of the additional // initialization and bookkeeping stuff that is needed for `request_launch`. // This way we can reuse the process launching logic for RestartRequest too. -lldb::SBError LaunchProcess(const llvm::json::Object &request) { +lldb::SBError LaunchProcess(DAP &dap, const llvm::json::Object &request) { lldb::SBError error; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); auto launchCommands = GetStrings(arguments, "launchCommands"); // Instantiate a launch info instance for the target. - auto launch_info = g_dap.target.GetLaunchInfo(); + auto launch_info = dap.target.GetLaunchInfo(); // Grab the current working directory if there is one and set it in the // launch info. @@ -2117,29 +2334,29 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) { const uint64_t timeout_seconds = GetUnsigned(arguments, "timeout", 30); if (GetBoolean(arguments, "runInTerminal", false)) { - if (llvm::Error err = request_runInTerminal(request, timeout_seconds)) + if (llvm::Error err = request_runInTerminal(dap, request, timeout_seconds)) error.SetErrorString(llvm::toString(std::move(err)).c_str()); } else if (launchCommands.empty()) { // Disable async events so the launch will be successful when we return from // the launch call and the launch will happen synchronously - g_dap.debugger.SetAsync(false); - g_dap.target.Launch(launch_info, error); - g_dap.debugger.SetAsync(true); + dap.debugger.SetAsync(false); + dap.target.Launch(launch_info, error); + dap.debugger.SetAsync(true); } else { // Set the launch info so that run commands can access the configured // launch details. - g_dap.target.SetLaunchInfo(launch_info); - if (llvm::Error err = g_dap.RunLaunchCommands(launchCommands)) { + dap.target.SetLaunchInfo(launch_info); + if (llvm::Error err = dap.RunLaunchCommands(launchCommands)) { error.SetErrorString(llvm::toString(std::move(err)).c_str()); return error; } // The custom commands might have created a new target so we should use the // selected target after these commands are run. - g_dap.target = g_dap.debugger.GetSelectedTarget(); + dap.target = dap.debugger.GetSelectedTarget(); // Make sure the process is launched and stopped at the entry point before // proceeding as the launch commands are not run using the synchronous // mode. - error = g_dap.WaitForProcessToStop(timeout_seconds); + error = dap.WaitForProcessToStop(timeout_seconds); } return error; } @@ -2178,32 +2395,31 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // } -void request_launch(const llvm::json::Object &request) { - g_dap.is_attach = false; - g_dap.last_launch_or_attach_request = request; +void request_launch(DAP &dap, const llvm::json::Object &request) { + dap.is_attach = false; + dap.last_launch_or_attach_request = request; llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - g_dap.init_commands = GetStrings(arguments, "initCommands"); - g_dap.pre_run_commands = GetStrings(arguments, "preRunCommands"); - g_dap.stop_commands = GetStrings(arguments, "stopCommands"); - g_dap.exit_commands = GetStrings(arguments, "exitCommands"); - g_dap.terminate_commands = GetStrings(arguments, "terminateCommands"); - g_dap.post_run_commands = GetStrings(arguments, "postRunCommands"); - g_dap.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); + const auto *arguments = request.getObject("arguments"); + dap.init_commands = GetStrings(arguments, "initCommands"); + dap.pre_run_commands = GetStrings(arguments, "preRunCommands"); + dap.stop_commands = GetStrings(arguments, "stopCommands"); + dap.exit_commands = GetStrings(arguments, "exitCommands"); + dap.terminate_commands = GetStrings(arguments, "terminateCommands"); + dap.post_run_commands = GetStrings(arguments, "postRunCommands"); + dap.stop_at_entry = GetBoolean(arguments, "stopOnEntry", false); const llvm::StringRef debuggerRoot = GetString(arguments, "debuggerRoot"); - g_dap.enable_auto_variable_summaries = + dap.enable_auto_variable_summaries = GetBoolean(arguments, "enableAutoVariableSummaries", false); - g_dap.enable_synthetic_child_debugging = + dap.enable_synthetic_child_debugging = GetBoolean(arguments, "enableSyntheticChildDebugging", false); - g_dap.display_extended_backtrace = + dap.display_extended_backtrace = GetBoolean(arguments, "displayExtendedBacktrace", false); - g_dap.command_escape_prefix = - GetString(arguments, "commandEscapePrefix", "`"); - g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); - g_dap.SetThreadFormat(GetString(arguments, "customThreadFormat")); + dap.command_escape_prefix = GetString(arguments, "commandEscapePrefix", "`"); + dap.SetFrameFormat(GetString(arguments, "customFrameFormat")); + dap.SetThreadFormat(GetString(arguments, "customThreadFormat")); - PrintWelcomeMessage(); + PrintWelcomeMessage(dap); // This is a hack for loading DWARF in .o files on Mac where the .o files // in the debug map of the main executable have relative paths which @@ -2215,50 +2431,50 @@ void request_launch(const llvm::json::Object &request) { // Run any initialize LLDB commands the user specified in the launch.json. // This is run before target is created, so commands can't do anything with // the targets - preRunCommands are run with the target. - if (llvm::Error err = g_dap.RunInitCommands()) { + if (llvm::Error err = dap.RunInitCommands()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } - SetSourceMapFromArguments(*arguments); + SetSourceMapFromArguments(dap, *arguments); lldb::SBError status; - g_dap.SetTarget(g_dap.CreateTargetFromArguments(*arguments, status)); + dap.SetTarget(dap.CreateTargetFromArguments(*arguments, status)); if (status.Fail()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", status.GetCString()); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } // Run any pre run LLDB commands the user specified in the launch.json - if (llvm::Error err = g_dap.RunPreRunCommands()) { + if (llvm::Error err = dap.RunPreRunCommands()) { response["success"] = false; EmplaceSafeString(response, "message", llvm::toString(std::move(err))); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } - status = LaunchProcess(request); + status = LaunchProcess(dap, request); if (status.Fail()) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", std::string(status.GetCString())); } else { - g_dap.RunPostRunCommands(); + dap.RunPostRunCommands(); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); if (!status.Fail()) { - if (g_dap.is_attach) - SendProcessEvent(Attach); // this happens when doing runInTerminal + if (dap.is_attach) + SendProcessEvent(dap, Attach); // this happens when doing runInTerminal else - SendProcessEvent(Launch); + SendProcessEvent(dap, Launch); } - g_dap.SendJSON(CreateEventObject("initialized")); + dap.SendJSON(CreateEventObject("initialized")); } // Check if the step-granularity is `instruction` @@ -2312,15 +2528,15 @@ static bool hasInstructionGranularity(const llvm::json::Object &requestArgs) { // acknowledgement, so no body field is required." // }] // } -void request_next(const llvm::json::Object &request) { +void request_next(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - lldb::SBThread thread = g_dap.GetLLDBThread(*arguments); + const auto *arguments = request.getObject("arguments"); + lldb::SBThread thread = dap.GetLLDBThread(*arguments); if (thread.IsValid()) { // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. - g_dap.focus_tid = thread.GetThreadID(); + dap.focus_tid = thread.GetThreadID(); if (hasInstructionGranularity(*arguments)) { thread.StepInstruction(/*step_over=*/true); } else { @@ -2329,7 +2545,7 @@ void request_next(const llvm::json::Object &request) { } else { response["success"] = llvm::json::Value(false); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "PauseRequest": { @@ -2368,12 +2584,12 @@ void request_next(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // } -void request_pause(const llvm::json::Object &request) { +void request_pause(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - lldb::SBProcess process = g_dap.target.GetProcess(); + lldb::SBProcess process = dap.target.GetProcess(); lldb::SBError error = process.Stop(); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "RestartRequest": { @@ -2417,14 +2633,14 @@ void request_pause(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // }, -void request_restart(const llvm::json::Object &request) { +void request_restart(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - if (!g_dap.last_launch_or_attach_request) { + if (!dap.last_launch_or_attach_request) { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", "Restart request received but no process was launched."); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } // Check if we were in a "launch" session or an "attach" session. @@ -2436,35 +2652,36 @@ void request_restart(const llvm::json::Object &request) { // Note that when using runInTerminal we're technically attached, but it's an // implementation detail. The adapter *did* launch the process in response to // a "launch" command, so we can still stop it and re-run it. This is why we - // don't just check `g_dap.is_attach`. - if (GetString(*g_dap.last_launch_or_attach_request, "command") == "attach") { + // don't just check `dap.is_attach`. + if (GetString(*dap.last_launch_or_attach_request, "command") == "attach") { response["success"] = llvm::json::Value(false); EmplaceSafeString(response, "message", "Restarting an \"attach\" session is not supported."); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } // The optional `arguments` field in RestartRequest can contain an updated // version of the launch arguments. If there's one, use it. - auto restart_arguments = request.getObject("arguments"); + const auto *restart_arguments = request.getObject("arguments"); if (restart_arguments) { - auto launch_request_arguments = restart_arguments->getObject("arguments"); + const auto *launch_request_arguments = + restart_arguments->getObject("arguments"); if (launch_request_arguments) { - (*g_dap.last_launch_or_attach_request)["arguments"] = + (*dap.last_launch_or_attach_request)["arguments"] = llvm::json::Value(llvm::json::Object(*launch_request_arguments)); } } // Keep track of the old PID so when we get a "process exited" event from the // killed process we can detect it and not shut down the whole session. - lldb::SBProcess process = g_dap.target.GetProcess(); - g_dap.restarting_process_id = process.GetProcessID(); + lldb::SBProcess process = dap.target.GetProcess(); + dap.restarting_process_id = process.GetProcessID(); // Stop the current process if necessary. The logic here is similar to // CommandObjectProcessLaunchOrAttach::StopProcessIfNecessary, except that // we don't ask the user for confirmation. - g_dap.debugger.SetAsync(false); + dap.debugger.SetAsync(false); if (process.IsValid()) { lldb::StateType state = process.GetState(); if (state != lldb::eStateConnected) { @@ -2472,21 +2689,21 @@ void request_restart(const llvm::json::Object &request) { } // Clear the list of thread ids to avoid sending "thread exited" events // for threads of the process we are terminating. - g_dap.thread_ids.clear(); + dap.thread_ids.clear(); } - g_dap.debugger.SetAsync(true); - LaunchProcess(*g_dap.last_launch_or_attach_request); + dap.debugger.SetAsync(true); + LaunchProcess(dap, *dap.last_launch_or_attach_request); // This is normally done after receiving a "configuration done" request. // Because we're restarting, configuration has already happened so we can // continue the process right away. - if (g_dap.stop_at_entry) { - SendThreadStoppedEvent(); + if (dap.stop_at_entry) { + SendThreadStoppedEvent(dap); } else { - g_dap.target.GetProcess().Continue(); + dap.target.GetProcess().Continue(); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "ScopesRequest": { @@ -2540,12 +2757,12 @@ void request_restart(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_scopes(const llvm::json::Object &request) { +void request_scopes(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; - auto arguments = request.getObject("arguments"); - lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments); + const auto *arguments = request.getObject("arguments"); + lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); // As the user selects different stack frames in the GUI, a "scopes" request // will be sent to the DAP. This is the only way we know that the user has // selected a frame in a thread. There are no other notifications that are @@ -2565,18 +2782,18 @@ void request_scopes(const llvm::json::Object &request) { frame.GetThread().SetSelectedFrame(frame.GetFrameID()); } - g_dap.variables.locals = frame.GetVariables(/*arguments=*/true, - /*locals=*/true, - /*statics=*/false, - /*in_scope_only=*/true); - g_dap.variables.globals = frame.GetVariables(/*arguments=*/false, - /*locals=*/false, - /*statics=*/true, - /*in_scope_only=*/true); - g_dap.variables.registers = frame.GetRegisters(); - body.try_emplace("scopes", g_dap.CreateTopLevelScopes()); + dap.variables.locals = frame.GetVariables(/*arguments=*/true, + /*locals=*/true, + /*statics=*/false, + /*in_scope_only=*/true); + dap.variables.globals = frame.GetVariables(/*arguments=*/false, + /*locals=*/false, + /*statics=*/true, + /*in_scope_only=*/true); + dap.variables.registers = frame.GetRegisters(); + body.try_emplace("scopes", dap.CreateTopLevelScopes()); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SetBreakpointsRequest": { @@ -2689,14 +2906,14 @@ void request_scopes(const llvm::json::Object &request) { // }, // "required": [ "line" ] // } -void request_setBreakpoints(const llvm::json::Object &request) { +void request_setBreakpoints(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; lldb::SBError error; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - auto source = arguments->getObject("source"); + const auto *arguments = request.getObject("arguments"); + const auto *source = arguments->getObject("source"); const auto path = GetString(source, "path"); - auto breakpoints = arguments->getArray("breakpoints"); + const auto *breakpoints = arguments->getArray("breakpoints"); llvm::json::Array response_breakpoints; // Decode the source breakpoint infos for this "setBreakpoints" request @@ -2705,28 +2922,20 @@ void request_setBreakpoints(const llvm::json::Object &request) { // to an empty array. if (breakpoints) { for (const auto &bp : *breakpoints) { - auto bp_obj = bp.getAsObject(); + const auto *bp_obj = bp.getAsObject(); if (bp_obj) { - SourceBreakpoint src_bp(*bp_obj); - request_bps[src_bp.line] = src_bp; - + SourceBreakpoint src_bp(dap, *bp_obj); + std::pair bp_pos(src_bp.line, src_bp.column); + request_bps.try_emplace(bp_pos, src_bp); + const auto [iv, inserted] = + dap.source_breakpoints[path].try_emplace(bp_pos, src_bp); // We check if this breakpoint already exists to update it - auto existing_source_bps = g_dap.source_breakpoints.find(path); - if (existing_source_bps != g_dap.source_breakpoints.end()) { - const auto &existing_bp = - existing_source_bps->second.find(src_bp.line); - if (existing_bp != existing_source_bps->second.end()) { - existing_bp->second.UpdateBreakpoint(src_bp); - AppendBreakpoint(&existing_bp->second, response_breakpoints, path, - src_bp.line); - continue; - } - } - // At this point the breakpoint is new - g_dap.source_breakpoints[path][src_bp.line] = src_bp; - SourceBreakpoint &new_bp = g_dap.source_breakpoints[path][src_bp.line]; - new_bp.SetBreakpoint(path.data()); - AppendBreakpoint(&new_bp, response_breakpoints, path, new_bp.line); + if (inserted) + iv->getSecond().SetBreakpoint(path.data()); + else + iv->getSecond().UpdateBreakpoint(src_bp); + AppendBreakpoint(&iv->getSecond(), response_breakpoints, path, + src_bp.line); } } } @@ -2734,13 +2943,13 @@ void request_setBreakpoints(const llvm::json::Object &request) { // Delete any breakpoints in this source file that aren't in the // request_bps set. There is no call to remove breakpoints other than // calling this function with a smaller or empty "breakpoints" list. - auto old_src_bp_pos = g_dap.source_breakpoints.find(path); - if (old_src_bp_pos != g_dap.source_breakpoints.end()) { + auto old_src_bp_pos = dap.source_breakpoints.find(path); + if (old_src_bp_pos != dap.source_breakpoints.end()) { for (auto &old_bp : old_src_bp_pos->second) { auto request_pos = request_bps.find(old_bp.first); if (request_pos == request_bps.end()) { // This breakpoint no longer exists in this source file, delete it - g_dap.target.BreakpointDelete(old_bp.second.bp.GetID()); + dap.target.BreakpointDelete(old_bp.second.bp.GetID()); old_src_bp_pos->second.erase(old_bp.first); } } @@ -2749,7 +2958,7 @@ void request_setBreakpoints(const llvm::json::Object &request) { llvm::json::Object body; body.try_emplace("breakpoints", std::move(response_breakpoints)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SetExceptionBreakpointsRequest": { @@ -2799,32 +3008,33 @@ void request_setBreakpoints(const llvm::json::Object &request) { // just an acknowledgement, so no body field is required." // }] // } -void request_setExceptionBreakpoints(const llvm::json::Object &request) { +void request_setExceptionBreakpoints(DAP &dap, + const llvm::json::Object &request) { llvm::json::Object response; lldb::SBError error; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - auto filters = arguments->getArray("filters"); + const auto *arguments = request.getObject("arguments"); + const auto *filters = arguments->getArray("filters"); // Keep a list of any exception breakpoint filter names that weren't set // so we can clear any exception breakpoints if needed. std::set unset_filters; - for (const auto &bp : *g_dap.exception_breakpoints) + for (const auto &bp : *dap.exception_breakpoints) unset_filters.insert(bp.filter); for (const auto &value : *filters) { const auto filter = GetAsString(value); - auto exc_bp = g_dap.GetExceptionBreakpoint(std::string(filter)); + auto *exc_bp = dap.GetExceptionBreakpoint(std::string(filter)); if (exc_bp) { exc_bp->SetBreakpoint(); unset_filters.erase(std::string(filter)); } } for (const auto &filter : unset_filters) { - auto exc_bp = g_dap.GetExceptionBreakpoint(filter); + auto *exc_bp = dap.GetExceptionBreakpoint(filter); if (exc_bp) exc_bp->ClearBreakpoint(); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SetFunctionBreakpointsRequest": { @@ -2905,61 +3115,49 @@ void request_setExceptionBreakpoints(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_setFunctionBreakpoints(const llvm::json::Object &request) { +void request_setFunctionBreakpoints(DAP &dap, + const llvm::json::Object &request) { llvm::json::Object response; lldb::SBError error; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - auto breakpoints = arguments->getArray("breakpoints"); - FunctionBreakpointMap request_bps; + const auto *arguments = request.getObject("arguments"); + const auto *breakpoints = arguments->getArray("breakpoints"); llvm::json::Array response_breakpoints; - for (const auto &value : *breakpoints) { - auto bp_obj = value.getAsObject(); - if (bp_obj == nullptr) - continue; - FunctionBreakpoint func_bp(*bp_obj); - request_bps[func_bp.functionName] = std::move(func_bp); - } - std::vector remove_names; - // Disable any function breakpoints that aren't in the request_bps. + // Disable any function breakpoints that aren't in this request. // There is no call to remove function breakpoints other than calling this // function with a smaller or empty "breakpoints" list. - for (auto &pair : g_dap.function_breakpoints) { - auto request_pos = request_bps.find(pair.first()); - if (request_pos == request_bps.end()) { - // This function breakpoint no longer exists delete it from LLDB - g_dap.target.BreakpointDelete(pair.second.bp.GetID()); - remove_names.push_back(pair.first()); - } else { - // Update the existing breakpoint as any setting withing the function - // breakpoint might have changed. - pair.second.UpdateBreakpoint(request_pos->second); - // Remove this breakpoint from the request breakpoints since we have - // handled it here and we don't need to set a new breakpoint below. - request_bps.erase(request_pos); - // Add this breakpoint info to the response - AppendBreakpoint(&pair.second, response_breakpoints); - } + const auto name_iter = dap.function_breakpoints.keys(); + llvm::DenseSet seen(name_iter.begin(), name_iter.end()); + for (const auto &value : *breakpoints) { + const auto *bp_obj = value.getAsObject(); + if (!bp_obj) + continue; + FunctionBreakpoint fn_bp(dap, *bp_obj); + const auto [it, inserted] = + dap.function_breakpoints.try_emplace(fn_bp.functionName, dap, *bp_obj); + if (inserted) + it->second.SetBreakpoint(); + else + it->second.UpdateBreakpoint(fn_bp); + + AppendBreakpoint(&it->second, response_breakpoints); + seen.erase(fn_bp.functionName); } + // Remove any breakpoints that are no longer in our list - for (const auto &name : remove_names) - g_dap.function_breakpoints.erase(name); - - // Any breakpoints that are left in "request_bps" are breakpoints that - // need to be set. - for (auto &pair : request_bps) { - // Add this breakpoint info to the response - g_dap.function_breakpoints[pair.first()] = std::move(pair.second); - FunctionBreakpoint &new_bp = g_dap.function_breakpoints[pair.first()]; - new_bp.SetBreakpoint(); - AppendBreakpoint(&new_bp, response_breakpoints); + for (const auto &name : seen) { + auto fn_bp = dap.function_breakpoints.find(name); + if (fn_bp == dap.function_breakpoints.end()) + continue; + dap.target.BreakpointDelete(fn_bp->second.bp.GetID()); + dap.function_breakpoints.erase(name); } llvm::json::Object body; body.try_emplace("breakpoints", std::move(response_breakpoints)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "DataBreakpointInfoRequest": { @@ -3054,7 +3252,7 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_dataBreakpointInfo(const llvm::json::Object &request) { +void request_dataBreakpointInfo(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body; @@ -3064,8 +3262,8 @@ void request_dataBreakpointInfo(const llvm::json::Object &request) { const auto variablesReference = GetUnsigned(arguments, "variablesReference", 0); llvm::StringRef name = GetString(arguments, "name"); - lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments); - lldb::SBValue variable = FindVariable(variablesReference, name); + lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); + lldb::SBValue variable = FindVariable(dap, variablesReference, name); std::string addr, size; if (variable.IsValid()) { @@ -3100,7 +3298,7 @@ void request_dataBreakpointInfo(const llvm::json::Object &request) { addr = llvm::utohexstr(load_addr); lldb::SBMemoryRegionInfo region; lldb::SBError err = - g_dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); + dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region); // Only lldb-server supports "qMemoryRegionInfo". So, don't fail this // request if SBProcess::GetMemoryRegionInfo returns error. if (err.Success()) { @@ -3130,7 +3328,7 @@ void request_dataBreakpointInfo(const llvm::json::Object &request) { size + " bytes at " + addr + " " + name.str()); } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SetDataBreakpointsRequest": { @@ -3193,22 +3391,20 @@ void request_dataBreakpointInfo(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_setDataBreakpoints(const llvm::json::Object &request) { +void request_setDataBreakpoints(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; lldb::SBError error; FillResponse(request, response); const auto *arguments = request.getObject("arguments"); const auto *breakpoints = arguments->getArray("breakpoints"); llvm::json::Array response_breakpoints; - g_dap.target.DeleteAllWatchpoints(); + dap.target.DeleteAllWatchpoints(); std::vector watchpoints; if (breakpoints) { for (const auto &bp : *breakpoints) { const auto *bp_obj = bp.getAsObject(); - if (bp_obj) { - Watchpoint wp(*bp_obj); - watchpoints.push_back(wp); - } + if (bp_obj) + watchpoints.emplace_back(dap, *bp_obj); } } // If two watchpoints start at the same address, the latter overwrite the @@ -3227,7 +3423,7 @@ void request_setDataBreakpoints(const llvm::json::Object &request) { llvm::json::Object body; body.try_emplace("breakpoints", std::move(response_breakpoints)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SourceRequest": { @@ -3288,12 +3484,12 @@ void request_setDataBreakpoints(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_source(const llvm::json::Object &request) { +void request_source(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Object body{{"content", ""}}; response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "StackTraceRequest": { @@ -3374,12 +3570,12 @@ void request_source(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_stackTrace(const llvm::json::Object &request) { +void request_stackTrace(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); lldb::SBError error; - auto arguments = request.getObject("arguments"); - lldb::SBThread thread = g_dap.GetLLDBThread(*arguments); + const auto *arguments = request.getObject("arguments"); + lldb::SBThread thread = dap.GetLLDBThread(*arguments); llvm::json::Array stack_frames; llvm::json::Object body; @@ -3388,7 +3584,7 @@ void request_stackTrace(const llvm::json::Object &request) { const auto levels = GetUnsigned(arguments, "levels", 0); int64_t offset = 0; bool reached_end_of_stack = - FillStackFrames(thread, stack_frames, offset, start_frame, + FillStackFrames(dap, thread, stack_frames, offset, start_frame, levels == 0 ? INT64_MAX : levels); body.try_emplace("totalFrames", start_frame + stack_frames.size() + @@ -3397,7 +3593,7 @@ void request_stackTrace(const llvm::json::Object &request) { body.try_emplace("stackFrames", std::move(stack_frames)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "StepInRequest": { @@ -3450,25 +3646,25 @@ void request_stackTrace(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // } -void request_stepIn(const llvm::json::Object &request) { +void request_stepIn(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); std::string step_in_target; uint64_t target_id = GetUnsigned(arguments, "targetId", 0); - auto it = g_dap.step_in_targets.find(target_id); - if (it != g_dap.step_in_targets.end()) + auto it = dap.step_in_targets.find(target_id); + if (it != dap.step_in_targets.end()) step_in_target = it->second; const bool single_thread = GetBoolean(arguments, "singleThread", false); lldb::RunMode run_mode = single_thread ? lldb::eOnlyThisThread : lldb::eOnlyDuringStepping; - lldb::SBThread thread = g_dap.GetLLDBThread(*arguments); + lldb::SBThread thread = dap.GetLLDBThread(*arguments); if (thread.IsValid()) { // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. - g_dap.focus_tid = thread.GetThreadID(); + dap.focus_tid = thread.GetThreadID(); if (hasInstructionGranularity(*arguments)) { thread.StepInstruction(/*step_over=*/false); } else { @@ -3477,7 +3673,7 @@ void request_stepIn(const llvm::json::Object &request) { } else { response["success"] = llvm::json::Value(false); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "StepInTargetsRequest": { @@ -3533,24 +3729,24 @@ void request_stepIn(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_stepInTargets(const llvm::json::Object &request) { +void request_stepInTargets(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); - g_dap.step_in_targets.clear(); - lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments); + dap.step_in_targets.clear(); + lldb::SBFrame frame = dap.GetLLDBFrame(*arguments); if (frame.IsValid()) { lldb::SBAddress pc_addr = frame.GetPCAddress(); lldb::SBAddress line_end_addr = pc_addr.GetLineEntry().GetSameLineContiguousAddressRangeEnd(true); - lldb::SBInstructionList insts = g_dap.target.ReadInstructions( + lldb::SBInstructionList insts = dap.target.ReadInstructions( pc_addr, line_end_addr, /*flavor_string=*/nullptr); if (!insts.IsValid()) { response["success"] = false; response["message"] = "Failed to get instructions for frame."; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -3561,29 +3757,29 @@ void request_stepInTargets(const llvm::json::Object &request) { if (!inst.IsValid()) break; - lldb::addr_t inst_addr = inst.GetAddress().GetLoadAddress(g_dap.target); + lldb::addr_t inst_addr = inst.GetAddress().GetLoadAddress(dap.target); // Note: currently only x86/x64 supports flow kind. lldb::InstructionControlFlowKind flow_kind = - inst.GetControlFlowKind(g_dap.target); + inst.GetControlFlowKind(dap.target); if (flow_kind == lldb::eInstructionControlFlowKindCall) { // Use call site instruction address as id which is easy to debug. llvm::json::Object step_in_target; step_in_target["id"] = inst_addr; - llvm::StringRef call_operand_name = inst.GetOperands(g_dap.target); + llvm::StringRef call_operand_name = inst.GetOperands(dap.target); lldb::addr_t call_target_addr; if (call_operand_name.getAsInteger(0, call_target_addr)) continue; lldb::SBAddress call_target_load_addr = - g_dap.target.ResolveLoadAddress(call_target_addr); + dap.target.ResolveLoadAddress(call_target_addr); if (!call_target_load_addr.IsValid()) continue; // The existing ThreadPlanStepInRange only accept step in target // function with debug info. - lldb::SBSymbolContext sc = g_dap.target.ResolveSymbolContextForAddress( + lldb::SBSymbolContext sc = dap.target.ResolveSymbolContextForAddress( call_target_load_addr, lldb::eSymbolContextFunction); // The existing ThreadPlanStepInRange only accept step in target @@ -3596,7 +3792,7 @@ void request_stepInTargets(const llvm::json::Object &request) { if (step_in_target_name.empty()) continue; - g_dap.step_in_targets.try_emplace(inst_addr, step_in_target_name); + dap.step_in_targets.try_emplace(inst_addr, step_in_target_name); step_in_target.try_emplace("label", step_in_target_name); step_in_targets.emplace_back(std::move(step_in_target)); } @@ -3608,7 +3804,7 @@ void request_stepInTargets(const llvm::json::Object &request) { response["success"] = llvm::json::Value(false); response["message"] = "Failed to get frame for input frameId."; } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "StepOutRequest": { @@ -3647,20 +3843,20 @@ void request_stepInTargets(const llvm::json::Object &request) { // acknowledgement, so no body field is required." // }] // } -void request_stepOut(const llvm::json::Object &request) { +void request_stepOut(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - lldb::SBThread thread = g_dap.GetLLDBThread(*arguments); + const auto *arguments = request.getObject("arguments"); + lldb::SBThread thread = dap.GetLLDBThread(*arguments); if (thread.IsValid()) { // Remember the thread ID that caused the resume so we can set the // "threadCausedFocus" boolean value in the "stopped" events. - g_dap.focus_tid = thread.GetThreadID(); + dap.focus_tid = thread.GetThreadID(); thread.StepOut(); } else { response["success"] = llvm::json::Value(false); } - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "ThreadsRequest": { @@ -3698,8 +3894,8 @@ void request_stepOut(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_threads(const llvm::json::Object &request) { - lldb::SBProcess process = g_dap.target.GetProcess(); +void request_threads(DAP &dap, const llvm::json::Object &request) { + lldb::SBProcess process = dap.target.GetProcess(); llvm::json::Object response; FillResponse(request, response); @@ -3707,7 +3903,7 @@ void request_threads(const llvm::json::Object &request) { llvm::json::Array threads; for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) { lldb::SBThread thread = process.GetThreadAtIndex(thread_idx); - threads.emplace_back(CreateThread(thread)); + threads.emplace_back(CreateThread(thread, dap.thread_format)); } if (threads.size() == 0) { response["success"] = llvm::json::Value(false); @@ -3715,7 +3911,7 @@ void request_threads(const llvm::json::Object &request) { llvm::json::Object body; body.try_emplace("threads", std::move(threads)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "SetVariableRequest": { @@ -3811,12 +4007,12 @@ void request_threads(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_setVariable(const llvm::json::Object &request) { +void request_setVariable(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Array variables; llvm::json::Object body; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); // This is a reference to the containing variable/scope const auto variablesReference = GetUnsigned(arguments, "variablesReference", 0); @@ -3836,28 +4032,28 @@ void request_setVariable(const llvm::json::Object &request) { // only specifies the variable reference of the enclosing scope/variable, and // the name of the variable. We could have two shadowed variables with the // same name in "Locals" or "Globals". In our case the "id" absolute index - // of the variable within the g_dap.variables list. + // of the variable within the dap.variables list. const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX); if (id_value != UINT64_MAX) { - variable = g_dap.variables.GetVariable(id_value); + variable = dap.variables.GetVariable(id_value); } else { - variable = FindVariable(variablesReference, name); + variable = FindVariable(dap, variablesReference, name); } if (variable.IsValid()) { lldb::SBError error; bool success = variable.SetValueFromCString(value.data(), error); if (success) { - VariableDescription desc(variable); + VariableDescription desc(variable, dap.enable_auto_variable_summaries); EmplaceSafeString(body, "result", desc.display_value); EmplaceSafeString(body, "type", desc.display_type_name); - // We don't know the index of the variable in our g_dap.variables + // We don't know the index of the variable in our dap.variables // so always insert a new one to get its variablesReference. // is_permanent is false because debug console does not support // setVariable request. int64_t new_var_ref = - g_dap.variables.InsertVariable(variable, /*is_permanent=*/false); + dap.variables.InsertVariable(variable, /*is_permanent=*/false); if (variable.MightHaveChildren()) body.try_emplace("variablesReference", new_var_ref); else @@ -3876,7 +4072,7 @@ void request_setVariable(const llvm::json::Object &request) { } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "VariablesRequest": { @@ -3952,21 +4148,22 @@ void request_setVariable(const llvm::json::Object &request) { // "required": [ "body" ] // }] // } -void request_variables(const llvm::json::Object &request) { +void request_variables(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Array variables; - auto arguments = request.getObject("arguments"); + const auto *arguments = request.getObject("arguments"); const auto variablesReference = GetUnsigned(arguments, "variablesReference", 0); const int64_t start = GetSigned(arguments, "start", 0); const int64_t count = GetSigned(arguments, "count", 0); bool hex = false; - auto format = arguments->getObject("format"); + const auto *format = arguments->getObject("format"); if (format) hex = GetBoolean(format, "hex", false); - if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) { + if (lldb::SBValueList *top_scope = + GetTopLevelScope(dap, variablesReference)) { // variablesReference is one of our scopes, not an actual variable it is // asking for the list of args, locals or globals. int64_t start_idx = 0; @@ -3978,8 +4175,8 @@ void request_variables(const llvm::json::Object &request) { // and resolve what the pointer resolves to. Only change the format if the // format was set to the default format or if it was hex as some registers // have formats set for them. - const uint32_t addr_size = g_dap.target.GetProcess().GetAddressByteSize(); - lldb::SBValue reg_set = g_dap.variables.registers.GetValueAtIndex(0); + const uint32_t addr_size = dap.target.GetProcess().GetAddressByteSize(); + lldb::SBValue reg_set = dap.variables.registers.GetValueAtIndex(0); const uint32_t num_regs = reg_set.GetNumChildren(); for (uint32_t reg_idx = 0; reg_idx < num_regs; ++reg_idx) { lldb::SBValue reg = reg_set.GetChildAtIndex(reg_idx); @@ -4036,25 +4233,28 @@ void request_variables(const llvm::json::Object &request) { break; int64_t var_ref = - g_dap.variables.InsertVariable(variable, /*is_permanent=*/false); + dap.variables.InsertVariable(variable, /*is_permanent=*/false); variables.emplace_back(CreateVariable( - variable, var_ref, hex, + variable, var_ref, hex, dap.enable_auto_variable_summaries, + dap.enable_synthetic_child_debugging, variable_name_counts[GetNonNullVariableName(variable)] > 1)); } } else { // We are expanding a variable that has children, so we will return its // children. - lldb::SBValue variable = g_dap.variables.GetVariable(variablesReference); + lldb::SBValue variable = dap.variables.GetVariable(variablesReference); if (variable.IsValid()) { auto addChild = [&](lldb::SBValue child, std::optional custom_name = {}) { if (!child.IsValid()) return; bool is_permanent = - g_dap.variables.IsPermanentVariableReference(variablesReference); - int64_t var_ref = g_dap.variables.InsertVariable(child, is_permanent); + dap.variables.IsPermanentVariableReference(variablesReference); + int64_t var_ref = dap.variables.InsertVariable(child, is_permanent); variables.emplace_back(CreateVariable( - child, var_ref, hex, /*is_name_duplicated=*/false, custom_name)); + child, var_ref, hex, dap.enable_auto_variable_summaries, + dap.enable_synthetic_child_debugging, + /*is_name_duplicated=*/false, custom_name)); }; const int64_t num_children = variable.GetNumChildren(); int64_t end_idx = start + ((count == 0) ? num_children : count); @@ -4066,7 +4266,7 @@ void request_variables(const llvm::json::Object &request) { // "[raw]" child that can be used to inspect the raw version of a // synthetic member. That eliminates the need for the user to go to the // debug console and type `frame var to get these values. - if (g_dap.enable_synthetic_child_debugging && variable.IsSynthetic() && + if (dap.enable_synthetic_child_debugging && variable.IsSynthetic() && i == num_children) addChild(variable.GetNonSyntheticValue(), "[raw]"); } @@ -4074,7 +4274,7 @@ void request_variables(const llvm::json::Object &request) { llvm::json::Object body; body.try_emplace("variables", std::move(variables)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "LocationsRequest": { @@ -4154,7 +4354,7 @@ void request_variables(const llvm::json::Object &request) { // } // }] // }, -void request_locations(const llvm::json::Object &request) { +void request_locations(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); auto *arguments = request.getObject("arguments"); @@ -4163,11 +4363,11 @@ void request_locations(const llvm::json::Object &request) { // We use the lowest bit to distinguish between value location and declaration // location auto [var_ref, is_value_location] = UnpackLocation(location_id); - lldb::SBValue variable = g_dap.variables.GetVariable(var_ref); + lldb::SBValue variable = dap.variables.GetVariable(var_ref); if (!variable.IsValid()) { response["success"] = false; response["message"] = "Invalid variable reference"; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -4179,18 +4379,18 @@ void request_locations(const llvm::json::Object &request) { response["success"] = false; response["message"] = "Value locations are only available for pointers and references"; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } lldb::addr_t addr = variable.GetValueAsAddress(); lldb::SBLineEntry line_entry = - g_dap.target.ResolveLoadAddress(addr).GetLineEntry(); + dap.target.ResolveLoadAddress(addr).GetLineEntry(); if (!line_entry.IsValid()) { response["success"] = false; response["message"] = "Failed to resolve line entry for location"; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -4205,7 +4405,7 @@ void request_locations(const llvm::json::Object &request) { if (!decl.IsValid()) { response["success"] = false; response["message"] = "No declaration location available"; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -4217,7 +4417,7 @@ void request_locations(const llvm::json::Object &request) { } response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "DisassembleRequest": { @@ -4292,7 +4492,7 @@ void request_locations(const llvm::json::Object &request) { // } // }] // } -void request_disassemble(const llvm::json::Object &request) { +void request_disassemble(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); auto *arguments = request.getObject("arguments"); @@ -4303,28 +4503,27 @@ void request_disassemble(const llvm::json::Object &request) { response["success"] = false; response["message"] = "Malformed memory reference: " + memoryReference.str(); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } lldb::addr_t addr_ptr = *addr_opt; addr_ptr += GetSigned(arguments, "instructionOffset", 0); - lldb::SBAddress addr(addr_ptr, g_dap.target); + lldb::SBAddress addr(addr_ptr, dap.target); if (!addr.IsValid()) { response["success"] = false; response["message"] = "Memory reference not found in the current binary."; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } const auto inst_count = GetUnsigned(arguments, "instructionCount", 0); - lldb::SBInstructionList insts = - g_dap.target.ReadInstructions(addr, inst_count); + lldb::SBInstructionList insts = dap.target.ReadInstructions(addr, inst_count); if (!insts.IsValid()) { response["success"] = false; response["message"] = "Failed to find instructions for memory address."; - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } @@ -4334,11 +4533,11 @@ void request_disassemble(const llvm::json::Object &request) { for (size_t i = 0; i < num_insts; ++i) { lldb::SBInstruction inst = insts.GetInstructionAtIndex(i); auto addr = inst.GetAddress(); - const auto inst_addr = addr.GetLoadAddress(g_dap.target); - const char *m = inst.GetMnemonic(g_dap.target); - const char *o = inst.GetOperands(g_dap.target); - const char *c = inst.GetComment(g_dap.target); - auto d = inst.GetData(g_dap.target); + const auto inst_addr = addr.GetLoadAddress(dap.target); + const char *m = inst.GetMnemonic(dap.target); + const char *o = inst.GetOperands(dap.target); + const char *c = inst.GetComment(dap.target); + auto d = inst.GetData(dap.target); std::string bytes; llvm::raw_string_ostream sb(bytes); @@ -4424,7 +4623,7 @@ void request_disassemble(const llvm::json::Object &request) { llvm::json::Object body; body.try_emplace("instructions", std::move(instructions)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // "ReadMemoryRequest": { @@ -4504,7 +4703,7 @@ void request_disassemble(const llvm::json::Object &request) { // } // }] // }, -void request_readMemory(const llvm::json::Object &request) { +void request_readMemory(DAP &dap, const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); auto *arguments = request.getObject("arguments"); @@ -4515,7 +4714,7 @@ void request_readMemory(const llvm::json::Object &request) { response["success"] = false; response["message"] = "Malformed memory reference: " + memoryReference.str(); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } lldb::addr_t addr_int = *addr_opt; @@ -4529,13 +4728,13 @@ void request_readMemory(const llvm::json::Object &request) { std::vector buf; buf.resize(count_read); lldb::SBError error; - lldb::SBAddress addr{addr_int, g_dap.target}; + lldb::SBAddress addr{addr_int, dap.target}; size_t count_result = - g_dap.target.ReadMemory(addr, buf.data(), count_read, error); + dap.target.ReadMemory(addr, buf.data(), count_read, error); if (count_result == 0) { response["success"] = false; EmplaceSafeString(response, "message", error.GetCString()); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); return; } buf.resize(std::min(count_result, count_requested)); @@ -4545,31 +4744,33 @@ void request_readMemory(const llvm::json::Object &request) { body.try_emplace("address", formatted_addr); body.try_emplace("data", llvm::encodeBase64(buf)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } // A request used in testing to get the details on all breakpoints that are // currently set in the target. This helps us to test "setBreakpoints" and // "setFunctionBreakpoints" requests to verify we have the correct set of // breakpoints currently set in LLDB. -void request__testGetTargetBreakpoints(const llvm::json::Object &request) { +void request__testGetTargetBreakpoints(DAP &dap, + const llvm::json::Object &request) { llvm::json::Object response; FillResponse(request, response); llvm::json::Array response_breakpoints; - for (uint32_t i = 0; g_dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) { - auto bp = Breakpoint(g_dap.target.GetBreakpointAtIndex(i)); + for (uint32_t i = 0; dap.target.GetBreakpointAtIndex(i).IsValid(); ++i) { + auto bp = Breakpoint(dap, dap.target.GetBreakpointAtIndex(i)); AppendBreakpoint(&bp, response_breakpoints); } llvm::json::Object body; body.try_emplace("breakpoints", std::move(response_breakpoints)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } -// "SetInstructionBreakpointsRequest" : { -// "allOf" : [ -// {"$ref" : "#/definitions/Request"}, { -// "type" : "object", +// "SetInstructionBreakpointsRequest": { +// "allOf": [ +// {"$ref": "#/definitions/Request"}, +// { +// "type": "object", // "description" : // "Replaces all existing instruction breakpoints. Typically, " // "instruction breakpoints would be set from a disassembly window. " @@ -4578,285 +4779,268 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) { // "(with reason `instruction breakpoint`) is generated.\nClients " // "should only call this request if the corresponding capability " // "`supportsInstructionBreakpoints` is true.", -// "properties" : { -// "command" : {"type" : "string", "enum" : -// ["setInstructionBreakpoints"]}, "arguments" : -// {"$ref" : "#/definitions/SetInstructionBreakpointsArguments"} +// "properties": { +// "command": { "type": "string", "enum": ["setInstructionBreakpoints"] +// }, "arguments": {"$ref": +// "#/definitions/SetInstructionBreakpointsArguments"} // }, -// "required" : [ "command", "arguments" ] +// "required": [ "command", "arguments" ] // } // ] // }, -// "SetInstructionBreakpointsArguments" -// : { -// "type" : "object", -// "description" : "Arguments for `setInstructionBreakpoints` request", -// "properties" : { -// "breakpoints" : { -// "type" : "array", -// "items" : {"$ref" : "#/definitions/InstructionBreakpoint"}, -// "description" : "The instruction references of the breakpoints" -// } -// }, -// "required" : ["breakpoints"] -// }, -// "SetInstructionBreakpointsResponse" -// : { -// "allOf" : [ -// {"$ref" : "#/definitions/Response"}, { -// "type" : "object", -// "description" : "Response to `setInstructionBreakpoints` request", -// "properties" : { -// "body" : { -// "type" : "object", -// "properties" : { -// "breakpoints" : { -// "type" : "array", -// "items" : {"$ref" : "#/definitions/Breakpoint"}, -// "description" : -// "Information about the breakpoints. The array elements -// " "correspond to the elements of the `breakpoints` -// array." -// } -// }, -// "required" : ["breakpoints"] +// "SetInstructionBreakpointsArguments": { +// "type": "object", +// "description": "Arguments for `setInstructionBreakpoints` request", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": {"$ref": "#/definitions/InstructionBreakpoint"}, +// "description": "The instruction references of the breakpoints" +// } +// }, +// "required": ["breakpoints"] +// }, +// "SetInstructionBreakpointsResponse": { +// "allOf": [ +// {"$ref": "#/definitions/Response"}, +// { +// "type": "object", +// "description": "Response to `setInstructionBreakpoints` request", +// "properties": { +// "body": { +// "type": "object", +// "properties": { +// "breakpoints": { +// "type": "array", +// "items": {"$ref": "#/definitions/Breakpoint"}, +// "description": +// "Information about the breakpoints. The array elements +// " "correspond to the elements of the `breakpoints` +// array." // } // }, -// "required" : ["body"] +// "required": ["breakpoints"] // } -// ] -// }, -// "InstructionBreakpoint" : { -// "type" : "object", -// "description" : "Properties of a breakpoint passed to the " +// }, +// "required": ["body"] +// } +// ] +// }, +// "InstructionBreakpoint": { +// "type": "object", +// "description": "Properties of a breakpoint passed to the " // "`setInstructionBreakpoints` request", -// "properties" : { -// "instructionReference" : { -// "type" : "string", +// "properties": { +// "instructionReference": { +// "type": "string", // "description" : // "The instruction reference of the breakpoint.\nThis should be a " // "memory or instruction pointer reference from an // `EvaluateResponse`, " // "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`." // }, -// "offset" : { -// "type" : "integer", -// "description" : "The offset from the instruction reference in " +// "offset": { +// "type": "integer", +// "description": "The offset from the instruction reference in " // "bytes.\nThis can be negative." // }, -// "condition" : { -// "type" : "string", -// "description" : "An expression for conditional breakpoints.\nIt is only +// "condition": { +// "type": "string", +// "description": "An expression for conditional breakpoints.\nIt is only // " // "honored by a debug adapter if the corresponding " // "capability `supportsConditionalBreakpoints` is true." // }, -// "hitCondition" : { -// "type" : "string", -// "description" : "An expression that controls how many hits of the " +// "hitCondition": { +// "type": "string", +// "description": "An expression that controls how many hits of the " // "breakpoint are ignored.\nThe debug adapter is expected // " "to interpret the expression as needed.\nThe // attribute " "is only honored by a debug adapter if the // corresponding " "capability // `supportsHitConditionalBreakpoints` is true." // }, -// "mode" : { -// "type" : "string", -// "description" : "The mode of this breakpoint. If defined, this must be +// "mode": { +// "type": "string", +// "description": "The mode of this breakpoint. If defined, this must be // " // "one of the `breakpointModes` the debug adapter " // "advertised in its `Capabilities`." // } // }, -// "required" : ["instructionReference"] +// "required": ["instructionReference"] // }, -// "Breakpoint" -// : { -// "type" : "object", +// "Breakpoint": { +// "type": "object", +// "description" : +// "Information about a breakpoint created in `setBreakpoints`, " +// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or " +// "`setDataBreakpoints` requests.", +// "properties": { +// "id": { +// "type": "integer", // "description" : -// "Information about a breakpoint created in `setBreakpoints`, " -// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or " -// "`setDataBreakpoints` requests.", -// "properties" : { -// "id" : { -// "type" : "integer", -// "description" : -// "The identifier for the breakpoint. It is needed if breakpoint -// " "events are used to update or remove breakpoints." -// }, -// "verified" : { -// "type" : "boolean", -// "description" : "If true, the breakpoint could be set (but not " -// "necessarily at the desired location)." -// }, -// "message" : { -// "type" : "string", -// "description" : "A message about the state of the breakpoint.\nThis -// " -// "is shown to the user and can be used to explain -// why " "a breakpoint could not be verified." -// }, -// "source" : { -// "$ref" : "#/definitions/Source", -// "description" : "The source where the breakpoint is located." -// }, -// "line" : { -// "type" : "integer", -// "description" : -// "The start line of the actual range covered by the breakpoint." -// }, -// "column" : { -// "type" : "integer", -// "description" : -// "Start position of the source range covered by the breakpoint. -// " "It is measured in UTF-16 code units and the client -// capability " -// "`columnsStartAt1` determines whether it is 0- or 1-based." -// }, -// "endLine" : { -// "type" : "integer", -// "description" : -// "The end line of the actual range covered by the breakpoint." -// }, -// "endColumn" : { -// "type" : "integer", -// "description" : -// "End position of the source range covered by the breakpoint. It -// " "is measured in UTF-16 code units and the client capability " -// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf -// " "no end line is given, then the end column is assumed to be -// in " "the start line." -// }, -// "instructionReference" : { -// "type" : "string", -// "description" : "A memory reference to where the breakpoint is -// set." -// }, -// "offset" : { -// "type" : "integer", -// "description" : "The offset from the instruction reference.\nThis " -// "can be negative." -// }, -// "reason" : { -// "type" : "string", -// "description" : -// "A machine-readable explanation of why a breakpoint may not be -// " "verified. If a breakpoint is verified or a specific reason -// is " "not known, the adapter should omit this property. -// Possible " "values include:\n\n- `pending`: Indicates a -// breakpoint might be " "verified in the future, but the adapter -// cannot verify it in the " "current state.\n - `failed`: -// Indicates a breakpoint was not " "able to be verified, and the -// adapter does not believe it can be " "verified without -// intervention.", -// "enum" : [ "pending", "failed" ] -// } -// }, -// "required" : ["verified"] +// "The identifier for the breakpoint. It is needed if breakpoint +// " "events are used to update or remove breakpoints." // }, - -void request_setInstructionBreakpoints(const llvm::json::Object &request) { +// "verified": { +// "type": "boolean", +// "description": "If true, the breakpoint could be set (but not " +// "necessarily at the desired location)." +// }, +// "message": { +// "type": "string", +// "description": "A message about the state of the breakpoint.\nThis +// " +// "is shown to the user and can be used to explain +// why " "a breakpoint could not be verified." +// }, +// "source": { +// "$ref": "#/definitions/Source", +// "description": "The source where the breakpoint is located." +// }, +// "line": { +// "type": "integer", +// "description" : +// "The start line of the actual range covered by the breakpoint." +// }, +// "column": { +// "type": "integer", +// "description" : +// "Start position of the source range covered by the breakpoint. +// " "It is measured in UTF-16 code units and the client +// capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based." +// }, +// "endLine": { +// "type": "integer", +// "description" : +// "The end line of the actual range covered by the breakpoint." +// }, +// "endColumn": { +// "type": "integer", +// "description" : +// "End position of the source range covered by the breakpoint. It +// " "is measured in UTF-16 code units and the client capability " +// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf +// " "no end line is given, then the end column is assumed to be +// in " "the start line." +// }, +// "instructionReference": { +// "type": "string", +// "description": "A memory reference to where the breakpoint is +// set." +// }, +// "offset": { +// "type": "integer", +// "description": "The offset from the instruction reference.\nThis " +// "can be negative." +// }, +// "reason": { +// "type": "string", +// "description" : +// "A machine-readable explanation of why a breakpoint may not be +// " "verified. If a breakpoint is verified or a specific reason +// is " "not known, the adapter should omit this property. +// Possible " "values include:\n\n- `pending`: Indicates a +// breakpoint might be " "verified in the future, but the adapter +// cannot verify it in the " "current state.\n - `failed`: +// Indicates a breakpoint was not " "able to be verified, and the +// adapter does not believe it can be " "verified without +// intervention.", +// "enum": [ "pending", "failed" ] +// } +// }, +// "required": ["verified"] +// }, +void request_setInstructionBreakpoints(DAP &dap, + const llvm::json::Object &request) { llvm::json::Object response; llvm::json::Array response_breakpoints; llvm::json::Object body; FillResponse(request, response); - auto arguments = request.getObject("arguments"); - auto breakpoints = arguments->getArray("breakpoints"); + const auto *arguments = request.getObject("arguments"); + const auto *breakpoints = arguments->getArray("breakpoints"); - // It holds active instruction breakpoint list received from DAP. - InstructionBreakpointMap request_ibp; - if (breakpoints) { - for (const auto &bp : *breakpoints) { - auto bp_obj = bp.getAsObject(); - if (bp_obj) { - // Read instruction breakpoint request. - InstructionBreakpoint inst_bp(*bp_obj); - // Store them into map for reference. - request_ibp[inst_bp.instructionAddressReference] = std::move(inst_bp); - } - } + // Disable any instruction breakpoints that aren't in this request. + // There is no call to remove instruction breakpoints other than calling this + // function with a smaller or empty "breakpoints" list. + llvm::DenseSet seen; + for (const auto &addr : dap.instruction_breakpoints) + seen.insert(addr.first); - // Iterate previous active instruction breakpoint list. - for (auto &prev_ibp : g_dap.instruction_breakpoints) { - // Find previous instruction breakpoint reference address in newly - // received instruction breakpoint list. - auto inst_reference = request_ibp.find(prev_ibp.first); - // Request for remove and delete the breakpoint, if the prev instruction - // breakpoint ID is not available in active instrcation breakpoint list. - // Means delete removed breakpoint instance. - if (inst_reference == request_ibp.end()) { - g_dap.target.BreakpointDelete(prev_ibp.second.id); - // Update Prev instruction breakpoint list. - g_dap.instruction_breakpoints.erase(prev_ibp.first); - } else { - // Instead of recreating breakpoint instance, update the breakpoint if - // there are any conditional changes. - prev_ibp.second.UpdateBreakpoint(inst_reference->second); - request_ibp.erase(inst_reference); - response_breakpoints.emplace_back( - CreateInstructionBreakpoint(&prev_ibp.second)); - } - } + for (const auto &bp : *breakpoints) { + const auto *bp_obj = bp.getAsObject(); + if (!bp_obj) + continue; + // Read instruction breakpoint request. + InstructionBreakpoint inst_bp(dap, *bp_obj); + const auto [iv, inserted] = dap.instruction_breakpoints.try_emplace( + inst_bp.instructionAddressReference, dap, *bp_obj); + if (inserted) + iv->second.SetBreakpoint(); + else + iv->second.UpdateBreakpoint(inst_bp); + AppendBreakpoint(&iv->second, response_breakpoints); + seen.erase(inst_bp.instructionAddressReference); + } - for (auto &req_bpi : request_ibp) { - // Add this breakpoint info to the response - g_dap.instruction_breakpoints[req_bpi.first] = std::move(req_bpi.second); - InstructionBreakpoint &new_bp = - g_dap.instruction_breakpoints[req_bpi.first]; - new_bp.SetInstructionBreakpoint(); - response_breakpoints.emplace_back(CreateInstructionBreakpoint(&new_bp)); - } + for (const auto &addr : seen) { + auto inst_bp = dap.instruction_breakpoints.find(addr); + if (inst_bp == dap.instruction_breakpoints.end()) + continue; + dap.target.BreakpointDelete(inst_bp->second.bp.GetID()); + dap.instruction_breakpoints.erase(addr); } body.try_emplace("breakpoints", std::move(response_breakpoints)); response.try_emplace("body", std::move(body)); - g_dap.SendJSON(llvm::json::Value(std::move(response))); + dap.SendJSON(llvm::json::Value(std::move(response))); } -void RegisterRequestCallbacks() { - g_dap.RegisterRequestCallback("attach", request_attach); - g_dap.RegisterRequestCallback("completions", request_completions); - g_dap.RegisterRequestCallback("continue", request_continue); - g_dap.RegisterRequestCallback("configurationDone", request_configurationDone); - g_dap.RegisterRequestCallback("disconnect", request_disconnect); - g_dap.RegisterRequestCallback("evaluate", request_evaluate); - g_dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo); - g_dap.RegisterRequestCallback("initialize", request_initialize); - g_dap.RegisterRequestCallback("launch", request_launch); - g_dap.RegisterRequestCallback("next", request_next); - g_dap.RegisterRequestCallback("pause", request_pause); - g_dap.RegisterRequestCallback("restart", request_restart); - g_dap.RegisterRequestCallback("scopes", request_scopes); - g_dap.RegisterRequestCallback("setBreakpoints", request_setBreakpoints); - g_dap.RegisterRequestCallback("setExceptionBreakpoints", - request_setExceptionBreakpoints); - g_dap.RegisterRequestCallback("setFunctionBreakpoints", - request_setFunctionBreakpoints); - g_dap.RegisterRequestCallback("dataBreakpointInfo", - request_dataBreakpointInfo); - g_dap.RegisterRequestCallback("setDataBreakpoints", - request_setDataBreakpoints); - g_dap.RegisterRequestCallback("setVariable", request_setVariable); - g_dap.RegisterRequestCallback("source", request_source); - g_dap.RegisterRequestCallback("stackTrace", request_stackTrace); - g_dap.RegisterRequestCallback("stepIn", request_stepIn); - g_dap.RegisterRequestCallback("stepInTargets", request_stepInTargets); - g_dap.RegisterRequestCallback("stepOut", request_stepOut); - g_dap.RegisterRequestCallback("threads", request_threads); - g_dap.RegisterRequestCallback("variables", request_variables); - g_dap.RegisterRequestCallback("locations", request_locations); - g_dap.RegisterRequestCallback("disassemble", request_disassemble); - g_dap.RegisterRequestCallback("readMemory", request_readMemory); - // Instruction breakpoint request - g_dap.RegisterRequestCallback("setInstructionBreakpoints", - request_setInstructionBreakpoints); +void RegisterRequestCallbacks(DAP &dap) { + dap.RegisterRequestCallback("attach", request_attach); + dap.RegisterRequestCallback("breakpointLocations", + request_breakpointLocations); + dap.RegisterRequestCallback("completions", request_completions); + dap.RegisterRequestCallback("continue", request_continue); + dap.RegisterRequestCallback("configurationDone", request_configurationDone); + dap.RegisterRequestCallback("disconnect", request_disconnect); + dap.RegisterRequestCallback("evaluate", request_evaluate); + dap.RegisterRequestCallback("exceptionInfo", request_exceptionInfo); + dap.RegisterRequestCallback("initialize", request_initialize); + dap.RegisterRequestCallback("launch", request_launch); + dap.RegisterRequestCallback("next", request_next); + dap.RegisterRequestCallback("pause", request_pause); + dap.RegisterRequestCallback("restart", request_restart); + dap.RegisterRequestCallback("scopes", request_scopes); + dap.RegisterRequestCallback("setBreakpoints", request_setBreakpoints); + dap.RegisterRequestCallback("setExceptionBreakpoints", + request_setExceptionBreakpoints); + dap.RegisterRequestCallback("setFunctionBreakpoints", + request_setFunctionBreakpoints); + dap.RegisterRequestCallback("dataBreakpointInfo", request_dataBreakpointInfo); + dap.RegisterRequestCallback("setDataBreakpoints", request_setDataBreakpoints); + dap.RegisterRequestCallback("setVariable", request_setVariable); + dap.RegisterRequestCallback("source", request_source); + dap.RegisterRequestCallback("stackTrace", request_stackTrace); + dap.RegisterRequestCallback("stepIn", request_stepIn); + dap.RegisterRequestCallback("stepInTargets", request_stepInTargets); + dap.RegisterRequestCallback("stepOut", request_stepOut); + dap.RegisterRequestCallback("threads", request_threads); + dap.RegisterRequestCallback("variables", request_variables); + dap.RegisterRequestCallback("locations", request_locations); + dap.RegisterRequestCallback("disassemble", request_disassemble); + dap.RegisterRequestCallback("readMemory", request_readMemory); + dap.RegisterRequestCallback("setInstructionBreakpoints", + request_setInstructionBreakpoints); // Custom requests - g_dap.RegisterRequestCallback("compileUnits", request_compileUnits); - g_dap.RegisterRequestCallback("modules", request_modules); + dap.RegisterRequestCallback("compileUnits", request_compileUnits); + dap.RegisterRequestCallback("modules", request_modules); // Testing requests - g_dap.RegisterRequestCallback("_testGetTargetBreakpoints", - request__testGetTargetBreakpoints); + dap.RegisterRequestCallback("_testGetTargetBreakpoints", + request__testGetTargetBreakpoints); } } // anonymous namespace @@ -4902,9 +5086,9 @@ static void printHelp(LLDBDAPOptTable &table, llvm::StringRef tool_name) { // // In case of errors launching the target, a suitable error message will be // emitted to the debug adaptor. -void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, - llvm::StringRef comm_file, - lldb::pid_t debugger_pid, char *argv[]) { +static void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, + llvm::StringRef comm_file, + lldb::pid_t debugger_pid, char *argv[]) { #if defined(_WIN32) llvm::errs() << "runInTerminal is only supported on POSIX systems\n"; exit(EXIT_FAILURE); @@ -4948,46 +5132,21 @@ void LaunchRunInTerminalTarget(llvm::opt::Arg &target_arg, } /// used only by TestVSCode_redirection_to_console.py -void redirection_test() { +static void redirection_test() { printf("stdout message\n"); fprintf(stderr, "stderr message\n"); fflush(stdout); fflush(stderr); } -/// Redirect stdout and stderr fo the IDE's console output. -/// -/// Errors in this operation will be printed to the log file and the IDE's -/// console output as well. -/// -/// \return -/// A fd pointing to the original stdout. -int SetupStdoutStderrRedirection() { - int stdoutfd = fileno(stdout); - int new_stdout_fd = dup(stdoutfd); - auto output_callback_stderr = [](llvm::StringRef data) { - g_dap.SendOutput(OutputType::Stderr, data); - }; - auto output_callback_stdout = [](llvm::StringRef data) { - g_dap.SendOutput(OutputType::Stdout, data); - }; - if (llvm::Error err = RedirectFd(stdoutfd, output_callback_stdout)) { - std::string error_message = llvm::toString(std::move(err)); - if (g_dap.log) - *g_dap.log << error_message << std::endl; - output_callback_stderr(error_message); - } - if (llvm::Error err = RedirectFd(fileno(stderr), output_callback_stderr)) { - std::string error_message = llvm::toString(std::move(err)); - if (g_dap.log) - *g_dap.log << error_message << std::endl; - output_callback_stderr(error_message); - } - - /// used only by TestVSCode_redirection_to_console.py - if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) - redirection_test(); - return new_stdout_fd; +/// Duplicates a file descriptor, setting FD_CLOEXEC if applicable. +static int DuplicateFileDescriptor(int fd) { +#if defined(F_DUPFD_CLOEXEC) + // Ensure FD_CLOEXEC is set. + return ::fcntl(fd, F_DUPFD_CLOEXEC, 0); +#else + return ::dup(fd); +#endif } int main(int argc, char *argv[]) { @@ -5003,7 +5162,6 @@ int main(int argc, char *argv[]) { llvm::SmallString<256> program_path(argv[0]); llvm::sys::fs::make_absolute(program_path); - g_dap.debug_adaptor_path = program_path.str().str(); LLDBDAPOptTable T; unsigned MAI, MAC; @@ -5015,15 +5173,16 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } + ReplMode default_repl_mode = ReplMode::Auto; if (input_args.hasArg(OPT_repl_mode)) { llvm::opt::Arg *repl_mode = input_args.getLastArg(OPT_repl_mode); llvm::StringRef repl_mode_value = repl_mode->getValue(); if (repl_mode_value == "auto") { - g_dap.repl_mode = ReplMode::Auto; + default_repl_mode = ReplMode::Auto; } else if (repl_mode_value == "variable") { - g_dap.repl_mode = ReplMode::Variable; + default_repl_mode = ReplMode::Variable; } else if (repl_mode_value == "command") { - g_dap.repl_mode = ReplMode::Command; + default_repl_mode = ReplMode::Command; } else { llvm::errs() << "'" << repl_mode_value @@ -5060,22 +5219,9 @@ int main(int argc, char *argv[]) { } } - // stdout/stderr redirection to the IDE's console - int new_stdout_fd = SetupStdoutStderrRedirection(); - - // Initialize LLDB first before we do anything. - lldb::SBDebugger::Initialize(); - - // Terminate the debugger before the C++ destructor chain kicks in. - auto terminate_debugger = - llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); }); - - RegisterRequestCallbacks(); - int portno = -1; - if (auto *arg = input_args.getLastArg(OPT_port)) { - auto optarg = arg->getValue(); + const auto *optarg = arg->getValue(); char *remainder; portno = strtol(optarg, &remainder, 0); if (remainder == optarg || *remainder != '\0') { @@ -5090,30 +5236,89 @@ int main(int argc, char *argv[]) { pause(); } #endif + + std::unique_ptr log = nullptr; + const char *log_file_path = getenv("LLDBDAP_LOG"); + if (log_file_path) + log = std::make_unique(log_file_path); + + // Initialize LLDB first before we do anything. + lldb::SBError error = lldb::SBDebugger::InitializeWithErrorHandling(); + if (error.Fail()) { + lldb::SBStream os; + error.GetDescription(os); + llvm::errs() << "lldb initialize failed: " << os.GetData() << "\n"; + return EXIT_FAILURE; + } + + // Terminate the debugger before the C++ destructor chain kicks in. + auto terminate_debugger = + llvm::make_scope_exit([] { lldb::SBDebugger::Terminate(); }); + + StreamDescriptor input; + StreamDescriptor output; + std::FILE *redirectOut = nullptr; + std::FILE *redirectErr = nullptr; if (portno != -1) { printf("Listening on port %i...\n", portno); - SOCKET socket_fd = AcceptConnection(portno); - if (socket_fd >= 0) { - g_dap.input.descriptor = StreamDescriptor::from_socket(socket_fd, true); - g_dap.output.descriptor = StreamDescriptor::from_socket(socket_fd, false); - } else { + SOCKET socket_fd = AcceptConnection(log.get(), portno); + if (socket_fd < 0) return EXIT_FAILURE; - } + + input = StreamDescriptor::from_socket(socket_fd, true); + output = StreamDescriptor::from_socket(socket_fd, false); } else { - g_dap.input.descriptor = StreamDescriptor::from_file(fileno(stdin), false); - g_dap.output.descriptor = StreamDescriptor::from_file(new_stdout_fd, false); +#if defined(_WIN32) + // Windows opens stdout and stdin in text mode which converts \n to 13,10 + // while the value is just 10 on Darwin/Linux. Setting the file mode to + // binary fixes this. + int result = _setmode(fileno(stdout), _O_BINARY); + assert(result); + result = _setmode(fileno(stdin), _O_BINARY); + UNUSED_IF_ASSERT_DISABLED(result); + assert(result); +#endif + + int stdout_fd = DuplicateFileDescriptor(fileno(stdout)); + if (stdout_fd == -1) { + llvm::logAllUnhandledErrors( + llvm::errorCodeToError(llvm::errnoAsErrorCode()), llvm::errs(), + "Failed to configure stdout redirect: "); + return EXIT_FAILURE; + } + + redirectOut = stdout; + redirectErr = stderr; + + input = StreamDescriptor::from_file(fileno(stdin), false); + output = StreamDescriptor::from_file(stdout_fd, false); + } + + DAP dap = DAP(program_path.str(), log.get(), default_repl_mode, + std::move(input), std::move(output)); + + // stdout/stderr redirection to the IDE's console + if (auto Err = dap.ConfigureIO(redirectOut, redirectErr)) { + llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), + "Failed to configure lldb-dap IO operations: "); + return EXIT_FAILURE; } + RegisterRequestCallbacks(dap); + for (const std::string &arg : input_args.getAllArgValues(OPT_pre_init_command)) { - g_dap.pre_init_commands.push_back(arg); + dap.pre_init_commands.push_back(arg); } + // used only by TestVSCode_redirection_to_console.py + if (getenv("LLDB_DAP_TEST_STDOUT_STDERR_REDIRECTION") != nullptr) + redirection_test(); + bool CleanExit = true; - if (auto Err = g_dap.Loop()) { - if (g_dap.log) - *g_dap.log << "Transport Error: " << llvm::toString(std::move(Err)) - << "\n"; + if (auto Err = dap.Loop()) { + if (log) + *log << "Transport Error: " << llvm::toString(std::move(Err)) << "\n"; CleanExit = false; } diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index 9155163c65ba5..5e9a7de9109ec 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -44,7 +44,7 @@ "format": "npx prettier './src-ts/' --write", "package": "vsce package --out ./out/lldb-dap.vsix", "publish": "vsce publish", - "vscode-uninstall": "code --uninstall-extension llvm.lldb-dap", + "vscode-uninstall": "code --uninstall-extension llvm-vs-code-extensions.lldb-dap", "vscode-install": "code --install-extension ./out/lldb-dap.vsix" }, "contributes": { @@ -513,4 +513,4 @@ } ] } -} \ No newline at end of file +}