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 70fd0b0c419db..8a0f6deb753cd 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 @@ -105,6 +105,36 @@ def dump_dap_log(log_file): print("========= END =========", file=sys.stderr) +class Source(object): + def __init__( + self, path: Optional[str] = None, source_reference: Optional[int] = None + ): + self._name = None + self._path = None + self._source_reference = None + + if path is not None: + self._name = os.path.basename(path) + self._path = path + elif source_reference is not None: + self._source_reference = source_reference + else: + raise ValueError("Either path or source_reference must be provided") + + def __str__(self): + return f"Source(name={self.name}, path={self.path}), source_reference={self.source_reference})" + + def as_dict(self): + source_dict = {} + if self._name is not None: + source_dict["name"] = self._name + if self._path is not None: + source_dict["path"] = self._path + if self._source_reference is not None: + source_dict["sourceReference"] = self._source_reference + return source_dict + + class DebugCommunication(object): def __init__( self, @@ -948,15 +978,13 @@ def request_scopes(self, frameId): command_dict = {"command": "scopes", "type": "request", "arguments": args_dict} return self.send_recv(command_dict) - def request_setBreakpoints(self, file_path, line_array, data=None): + def request_setBreakpoints(self, source: Source, line_array, data=None): """data is array of parameters for breakpoints in line_array. Each parameter object is 1:1 mapping with entries in line_entry. It contains optional location/hitCondition/logMessage parameters. """ - (dir, base) = os.path.split(file_path) - source_dict = {"name": base, "path": file_path} args_dict = { - "source": source_dict, + "source": source.as_dict(), "sourceModified": False, } if line_array is not None: @@ -1381,7 +1409,7 @@ def run_vscode(dbg, args, options): else: source_to_lines[path] = [int(line)] for source in source_to_lines: - dbg.request_setBreakpoints(source, source_to_lines[source]) + dbg.request_setBreakpoints(Source(source), source_to_lines[source]) if options.funcBreakpoints: dbg.request_setFunctionBreakpoints(options.funcBreakpoints) 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 afdc746ed0d0d..6ada001e937b7 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 @@ -4,6 +4,7 @@ import uuid import dap_server +from dap_server import Source from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbplatformutil import lldbgdbserverutils @@ -55,7 +56,9 @@ def set_source_breakpoints(self, source_path, lines, data=None): Each object in data is 1:1 mapping with the entry in lines. It contains optional location/hitCondition/logMessage parameters. """ - response = self.dap_server.request_setBreakpoints(source_path, lines, data) + response = self.dap_server.request_setBreakpoints( + Source(source_path), lines, data + ) if response is None or not response["success"]: return [] breakpoints = response["body"]["breakpoints"] @@ -64,6 +67,20 @@ def set_source_breakpoints(self, source_path, lines, data=None): breakpoint_ids.append("%i" % (breakpoint["id"])) return breakpoint_ids + def set_source_breakpoints_assembly(self, source_reference, lines, data=None): + response = self.dap_server.request_setBreakpoints( + Source(source_reference=source_reference), + lines, + data, + ) + if response is None: + return [] + breakpoints = response["body"]["breakpoints"] + breakpoint_ids = [] + for breakpoint in breakpoints: + breakpoint_ids.append("%i" % (breakpoint["id"])) + return breakpoint_ids + def set_function_breakpoints(self, functions, condition=None, hitCondition=None): """Sets breakpoints by function name given an array of function names and returns an array of strings containing the breakpoint IDs diff --git a/lldb/test/API/tools/lldb-dap/breakpoint-assembly/Makefile b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/Makefile new file mode 100644 index 0000000000000..10495940055b6 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-dap/breakpoint-assembly/TestDAP_breakpointAssembly.py b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/TestDAP_breakpointAssembly.py new file mode 100644 index 0000000000000..15d2305acd694 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/TestDAP_breakpointAssembly.py @@ -0,0 +1,83 @@ +""" +Test lldb-dap setBreakpoints request in assembly source references. +""" + + +from lldbsuite.test.decorators import * +from dap_server import Source +import lldbdap_testcase + + +# @skipIfWindows +class TestDAP_setBreakpointsAssembly(lldbdap_testcase.DAPTestCaseBase): + def test_can_break_in_source_references(self): + """Tests hitting assembly source breakpoints""" + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + assmebly_func_breakpoints = self.set_function_breakpoints(["assembly_func"]) + self.continue_to_breakpoints(assmebly_func_breakpoints) + + assembly_func_frame = self.get_stackFrames()[0] + self.assertIn( + "sourceReference", + assembly_func_frame.get("source"), + "Expected assembly source frame", + ) + + line = assembly_func_frame["line"] + + # Set an assembly breakpoint in the next line and check that it's hit + source_reference = assembly_func_frame["source"]["sourceReference"] + assembly_breakpoint_ids = self.set_source_breakpoints_assembly( + source_reference, [line + 1] + ) + self.continue_to_breakpoints(assembly_breakpoint_ids) + + # Continue again and verify it hits in the next function call + self.continue_to_breakpoints(assmebly_func_breakpoints) + self.continue_to_breakpoints(assembly_breakpoint_ids) + + # Clear the breakpoint and then check that the assembly breakpoint does not hit next time + self.set_source_breakpoints_assembly(source_reference, []) + self.continue_to_breakpoints(assmebly_func_breakpoints) + self.continue_to_exit() + + def test_break_on_invalid_source_reference(self): + """Tests hitting assembly source breakpoints""" + program = self.getBuildArtifact("a.out") + self.build_and_launch(program) + + # Verify that setting a breakpoint on an invalid source reference fails + response = self.dap_server.request_setBreakpoints( + Source(source_reference=-1), [1] + ) + self.assertIsNotNone(response) + breakpoints = response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 1) + breakpoint = breakpoints[0] + self.assertFalse( + breakpoint["verified"], "Expected breakpoint to not be verified" + ) + self.assertIn("message", breakpoint, "Expected message to be present") + self.assertEqual( + breakpoint["message"], + "Invalid sourceReference.", + ) + + # Verify that setting a breakpoint on a source reference without a symbol also fails + response = self.dap_server.request_setBreakpoints( + Source(source_reference=0), [1] + ) + self.assertIsNotNone(response) + breakpoints = response["body"]["breakpoints"] + self.assertEqual(len(breakpoints), 1) + breakpoint = breakpoints[0] + self.assertFalse( + breakpoint["verified"], "Expected breakpoint to not be verified" + ) + self.assertIn("message", breakpoint, "Expected message to be present") + self.assertEqual( + breakpoint["message"], + "Breakpoints in assembly without a valid symbol are not supported yet.", + ) diff --git a/lldb/test/API/tools/lldb-dap/breakpoint-assembly/main.c b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/main.c new file mode 100644 index 0000000000000..fdeadc2532a18 --- /dev/null +++ b/lldb/test/API/tools/lldb-dap/breakpoint-assembly/main.c @@ -0,0 +1,14 @@ +__attribute__((nodebug)) int assembly_func(int n) { + n += 1; + n += 2; + n += 3; + + return n; +} + +int main(int argc, char const *argv[]) { + assembly_func(10); + assembly_func(20); + assembly_func(30); + return 0; +} diff --git a/lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py b/lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py index d46fc31d797da..8f03244bc6572 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint-events/TestDAP_breakpointEvents.py @@ -2,7 +2,7 @@ Test lldb-dap setBreakpoints request """ -import dap_server +from dap_server import Source from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil @@ -58,7 +58,7 @@ def test_breakpoint_events(self): # Set breakpoints and verify that they got set correctly dap_breakpoint_ids = [] response = self.dap_server.request_setBreakpoints( - main_source_path, [main_bp_line] + Source(main_source_path), [main_bp_line] ) self.assertTrue(response["success"]) breakpoints = response["body"]["breakpoints"] @@ -70,7 +70,7 @@ def test_breakpoint_events(self): ) response = self.dap_server.request_setBreakpoints( - foo_source_path, [foo_bp1_line] + Source(foo_source_path), [foo_bp1_line] ) self.assertTrue(response["success"]) breakpoints = 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 aae1251b17c93..22470daac2887 100644 --- a/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py +++ b/lldb/test/API/tools/lldb-dap/breakpoint/TestDAP_setBreakpoints.py @@ -3,7 +3,7 @@ """ -import dap_server +from dap_server import Source import shutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * @@ -58,7 +58,9 @@ def test_source_map(self): self.launch(program, sourceMap=source_map) # breakpoint in main.cpp - response = self.dap_server.request_setBreakpoints(new_main_path, [main_line]) + response = self.dap_server.request_setBreakpoints( + Source(new_main_path), [main_line] + ) breakpoints = response["body"]["breakpoints"] self.assertEqual(len(breakpoints), 1) breakpoint = breakpoints[0] @@ -68,7 +70,9 @@ def test_source_map(self): self.assertEqual(new_main_path, breakpoint["source"]["path"]) # 2nd breakpoint, which is from a dynamically loaded library - response = self.dap_server.request_setBreakpoints(new_other_path, [other_line]) + response = self.dap_server.request_setBreakpoints( + Source(new_other_path), [other_line] + ) breakpoints = response["body"]["breakpoints"] breakpoint = breakpoints[0] self.assertEqual(breakpoint["line"], other_line) @@ -81,7 +85,9 @@ def test_source_map(self): self.verify_breakpoint_hit([other_breakpoint_id]) # 2nd breakpoint again, which should be valid at this point - response = self.dap_server.request_setBreakpoints(new_other_path, [other_line]) + response = self.dap_server.request_setBreakpoints( + Source(new_other_path), [other_line] + ) breakpoints = response["body"]["breakpoints"] breakpoint = breakpoints[0] self.assertEqual(breakpoint["line"], other_line) @@ -124,7 +130,7 @@ def test_set_and_clear(self): self.build_and_launch(program) # Set 3 breakpoints and verify that they got set correctly - response = self.dap_server.request_setBreakpoints(self.main_path, lines) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) line_to_id = {} breakpoints = response["body"]["breakpoints"] self.assertEqual( @@ -149,7 +155,7 @@ def test_set_and_clear(self): lines.remove(second_line) # 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) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) breakpoints = response["body"]["breakpoints"] self.assertEqual( len(breakpoints), @@ -194,7 +200,7 @@ def test_set_and_clear(self): # 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) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) breakpoints = response["body"]["breakpoints"] self.assertEqual( len(breakpoints), @@ -214,7 +220,7 @@ def test_set_and_clear(self): # Now set a breakpoint again in the same source file and verify it # was added. lines = [second_line] - response = self.dap_server.request_setBreakpoints(self.main_path, lines) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) if response: breakpoints = response["body"]["breakpoints"] self.assertEqual( @@ -265,7 +271,7 @@ def test_clear_breakpoints_unset_breakpoints(self): self.build_and_launch(program) # Set one breakpoint and verify that it got set correctly. - response = self.dap_server.request_setBreakpoints(self.main_path, lines) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) line_to_id = {} breakpoints = response["body"]["breakpoints"] self.assertEqual( @@ -281,7 +287,7 @@ def test_clear_breakpoints_unset_breakpoints(self): # Now clear all breakpoints for the source file by not setting the # lines array. lines = None - response = self.dap_server.request_setBreakpoints(self.main_path, lines) + response = self.dap_server.request_setBreakpoints(Source(self.main_path), lines) breakpoints = response["body"]["breakpoints"] self.assertEqual(len(breakpoints), 0, "expect no source breakpoints") @@ -357,7 +363,9 @@ def test_column_breakpoints(self): # 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) + Source(self.main_path), + [loop_line, loop_line], + list({"column": c} for c in columns), ) # Verify the breakpoints were set correctly diff --git a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py index 53b7df9e54af2..f503cc2cfb533 100644 --- a/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py +++ b/lldb/test/API/tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py @@ -1,4 +1,4 @@ -import dap_server +from dap_server import Source import shutil from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * @@ -33,7 +33,9 @@ def instruction_breakpoint_test(self): self.build_and_launch(program) # Set source breakpoint 1 - response = self.dap_server.request_setBreakpoints(self.main_path, [main_line]) + response = self.dap_server.request_setBreakpoints( + Source(self.main_path), [main_line] + ) breakpoints = response["body"]["breakpoints"] self.assertEqual(len(breakpoints), 1) breakpoint = breakpoints[0] diff --git a/lldb/tools/lldb-dap/Breakpoint.cpp b/lldb/tools/lldb-dap/Breakpoint.cpp index 26d633d1d172e..2d0fd9c9c3954 100644 --- a/lldb/tools/lldb-dap/Breakpoint.cpp +++ b/lldb/tools/lldb-dap/Breakpoint.cpp @@ -9,10 +9,12 @@ #include "Breakpoint.h" #include "DAP.h" #include "JSONUtils.h" +#include "LLDBUtils.h" #include "lldb/API/SBAddress.h" #include "lldb/API/SBBreakpointLocation.h" #include "lldb/API/SBLineEntry.h" #include "lldb/API/SBMutex.h" +#include "lldb/lldb-enumerations.h" #include "llvm/ADT/StringExtras.h" #include #include @@ -63,14 +65,31 @@ protocol::Breakpoint Breakpoint::ToProtocolBreakpoint() { std::string formatted_addr = "0x" + llvm::utohexstr(bp_addr.GetLoadAddress(m_bp.GetTarget())); breakpoint.instructionReference = formatted_addr; + + lldb::StopDisassemblyType stop_disassembly_display = + GetStopDisassemblyDisplay(m_dap.debugger); auto line_entry = bp_addr.GetLineEntry(); - const auto line = line_entry.GetLine(); - if (line != UINT32_MAX) - breakpoint.line = line; - const auto column = line_entry.GetColumn(); - if (column != 0) - breakpoint.column = column; - breakpoint.source = CreateSource(line_entry); + if (!ShouldDisplayAssemblySource(line_entry, stop_disassembly_display)) { + const auto line = line_entry.GetLine(); + if (line != LLDB_INVALID_LINE_NUMBER) + breakpoint.line = line; + const auto column = line_entry.GetColumn(); + if (column != LLDB_INVALID_COLUMN_NUMBER) + breakpoint.column = column; + breakpoint.source = CreateSource(line_entry); + } else { + // Assembly breakpoint. + auto symbol = bp_addr.GetSymbol(); + if (symbol.IsValid()) { + breakpoint.line = + m_bp.GetTarget() + .ReadInstructions(symbol.GetStartAddress(), bp_addr, nullptr) + .GetSize() + + 1; + + breakpoint.source = CreateAssemblySource(m_dap.target, bp_addr); + } + } } return breakpoint; diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp index 0d5eba6c40961..b7188843051e9 100644 --- a/lldb/tools/lldb-dap/DAP.cpp +++ b/lldb/tools/lldb-dap/DAP.cpp @@ -1620,6 +1620,89 @@ void DAP::EventThread() { } } +std::vector DAP::SetSourceBreakpoints( + const protocol::Source &source, + const std::optional> &breakpoints) { + std::vector response_breakpoints; + if (source.sourceReference) { + // Breakpoint set by assembly source. + auto &existing_breakpoints = + m_source_assembly_breakpoints[*source.sourceReference]; + response_breakpoints = + SetSourceBreakpoints(source, breakpoints, existing_breakpoints); + } else { + // Breakpoint set by a regular source file. + const auto path = source.path.value_or(""); + auto &existing_breakpoints = m_source_breakpoints[path]; + response_breakpoints = + SetSourceBreakpoints(source, breakpoints, existing_breakpoints); + } + + return response_breakpoints; +} + +std::vector DAP::SetSourceBreakpoints( + const protocol::Source &source, + const std::optional> &breakpoints, + SourceBreakpointMap &existing_breakpoints) { + std::vector response_breakpoints; + + SourceBreakpointMap request_breakpoints; + if (breakpoints) { + for (const auto &bp : *breakpoints) { + SourceBreakpoint src_bp(*this, bp); + std::pair bp_pos(src_bp.GetLine(), + src_bp.GetColumn()); + request_breakpoints.try_emplace(bp_pos, src_bp); + + const auto [iv, inserted] = + existing_breakpoints.try_emplace(bp_pos, src_bp); + // We check if this breakpoint already exists to update it. + if (inserted) { + if (llvm::Error error = iv->second.SetBreakpoint(source)) { + protocol::Breakpoint invalid_breakpoint; + invalid_breakpoint.message = llvm::toString(std::move(error)); + invalid_breakpoint.verified = false; + response_breakpoints.push_back(std::move(invalid_breakpoint)); + existing_breakpoints.erase(iv); + continue; + } + } else { + iv->second.UpdateBreakpoint(src_bp); + } + + protocol::Breakpoint response_breakpoint = + iv->second.ToProtocolBreakpoint(); + response_breakpoint.source = source; + + if (!response_breakpoint.line && + src_bp.GetLine() != LLDB_INVALID_LINE_NUMBER) + response_breakpoint.line = src_bp.GetLine(); + if (!response_breakpoint.column && + src_bp.GetColumn() != LLDB_INVALID_COLUMN_NUMBER) + response_breakpoint.column = src_bp.GetColumn(); + response_breakpoints.push_back(std::move(response_breakpoint)); + } + } + + // 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. + for (auto it = existing_breakpoints.begin(); + it != existing_breakpoints.end();) { + auto request_pos = request_breakpoints.find(it->first); + if (request_pos == request_breakpoints.end()) { + // This breakpoint no longer exists in this source file, delete it + target.BreakpointDelete(it->second.GetID()); + it = existing_breakpoints.erase(it); + } else { + ++it; + } + } + + return response_breakpoints; +} + void DAP::RegisterRequests() { RegisterRequest(); RegisterRequest(); diff --git a/lldb/tools/lldb-dap/DAP.h b/lldb/tools/lldb-dap/DAP.h index 8f24c6cf82924..62b35b4477db6 100644 --- a/lldb/tools/lldb-dap/DAP.h +++ b/lldb/tools/lldb-dap/DAP.h @@ -60,7 +60,7 @@ namespace lldb_dap { -typedef llvm::DenseMap, SourceBreakpoint> +typedef std::map, SourceBreakpoint> SourceBreakpointMap; typedef llvm::StringMap FunctionBreakpointMap; typedef llvm::DenseMap @@ -168,7 +168,6 @@ struct DAP { lldb::SBTarget target; Variables variables; lldb::SBBroadcaster broadcaster; - llvm::StringMap source_breakpoints; FunctionBreakpointMap function_breakpoints; InstructionBreakpointMap instruction_breakpoints; std::optional> exception_breakpoints; @@ -219,6 +218,9 @@ struct DAP { llvm::StringSet<> modules; /// @} + /// Number of lines of assembly code to show when no debug info is available. + static constexpr uint32_t k_number_of_assembly_lines_for_nodebug = 32; + /// Creates a new DAP sessions. /// /// \param[in] log @@ -423,7 +425,28 @@ struct DAP { void StartEventThread(); void StartProgressEventThread(); + /// Sets the given protocol `breakpoints` in the given `source`, while + /// removing any existing breakpoints in the given source if they are not in + /// `breakpoint`. + /// + /// \param[in] source + /// The relevant source of the breakpoints. + /// + /// \param[in] breakpoints + /// The breakpoints to set. + /// + /// \return a vector of the breakpoints that were set. + std::vector SetSourceBreakpoints( + const protocol::Source &source, + const std::optional> + &breakpoints); + private: + std::vector SetSourceBreakpoints( + const protocol::Source &source, + const std::optional> &breakpoints, + SourceBreakpointMap &existing_breakpoints); + /// Registration of request handler. /// @{ void RegisterRequests(); @@ -452,6 +475,9 @@ struct DAP { std::mutex m_active_request_mutex; const protocol::Request *m_active_request; + + llvm::StringMap m_source_breakpoints; + llvm::DenseMap m_source_assembly_breakpoints; }; } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp b/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp index 2ac886c3a5d2c..8a026f81b4329 100644 --- a/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/BreakpointLocationsHandler.cpp @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "DAP.h" -#include "JSONUtils.h" #include "RequestHandler.h" +#include #include namespace lldb_dap { @@ -19,19 +19,46 @@ namespace lldb_dap { llvm::Expected BreakpointLocationsRequestHandler::Run( const protocol::BreakpointLocationsArguments &args) const { - std::string path = args.source.path.value_or(""); uint32_t start_line = args.line; uint32_t start_column = args.column.value_or(LLDB_INVALID_COLUMN_NUMBER); uint32_t end_line = args.endLine.value_or(start_line); uint32_t end_column = args.endColumn.value_or(std::numeric_limits::max()); + // Find all relevant lines & columns. + std::vector> locations; + if (args.source.sourceReference) { + locations = GetAssemblyBreakpointLocations(*args.source.sourceReference, + start_line, end_line); + } else { + std::string path = args.source.path.value_or(""); + locations = GetSourceBreakpointLocations( + std::move(path), start_line, start_column, end_line, end_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(llvm::unique(locations), locations.end()); + + std::vector breakpoint_locations; + for (auto &l : locations) + breakpoint_locations.push_back( + {l.first, l.second, std::nullopt, std::nullopt}); + + return protocol::BreakpointLocationsResponseBody{ + /*breakpoints=*/std::move(breakpoint_locations)}; +} + +std::vector> +BreakpointLocationsRequestHandler::GetSourceBreakpointLocations( + std::string path, uint32_t start_line, uint32_t start_column, + uint32_t end_line, uint32_t end_column) const { + std::vector> locations; 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 = @@ -72,21 +99,31 @@ BreakpointLocationsRequestHandler::Run( } } - // 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(llvm::unique(locations), locations.end()); + return locations; +} - std::vector breakpoint_locations; - for (auto &l : locations) { - protocol::BreakpointLocation lc; - lc.line = l.first; - lc.column = l.second; - breakpoint_locations.push_back(std::move(lc)); +std::vector> +BreakpointLocationsRequestHandler::GetAssemblyBreakpointLocations( + int64_t source_reference, uint32_t start_line, uint32_t end_line) const { + std::vector> locations; + lldb::SBAddress address(source_reference, dap.target); + if (!address.IsValid()) + return locations; + + lldb::SBSymbol symbol = address.GetSymbol(); + if (!symbol.IsValid()) + return locations; + + // start_line is relative to the symbol's start address. + lldb::SBInstructionList insts = symbol.GetInstructions(dap.target); + if (insts.GetSize() > (start_line - 1)) + locations.reserve(insts.GetSize() - (start_line - 1)); + for (uint32_t i = start_line - 1; i < insts.GetSize() && i <= (end_line - 1); + ++i) { + locations.emplace_back(i, 1); } - return protocol::BreakpointLocationsResponseBody{ - /*breakpoints=*/std::move(breakpoint_locations)}; + return locations; } } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/Handler/RequestHandler.h b/lldb/tools/lldb-dap/Handler/RequestHandler.h index e6bccfe12f402..0a40b8eaed528 100644 --- a/lldb/tools/lldb-dap/Handler/RequestHandler.h +++ b/lldb/tools/lldb-dap/Handler/RequestHandler.h @@ -22,6 +22,7 @@ #include #include #include +#include template struct is_optional : std::false_type {}; @@ -232,6 +233,14 @@ class BreakpointLocationsRequestHandler } llvm::Expected Run(const protocol::BreakpointLocationsArguments &args) const override; + + std::vector> + GetSourceBreakpointLocations(std::string path, uint32_t start_line, + uint32_t start_column, uint32_t end_line, + uint32_t end_column) const; + std::vector> + GetAssemblyBreakpointLocations(int64_t source_reference, uint32_t start_line, + uint32_t end_line) const; }; class CompletionsRequestHandler : public LegacyRequestHandler { diff --git a/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp index 86e090b66afe9..0ff88f62f8f51 100644 --- a/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SetBreakpointsRequestHandler.cpp @@ -22,57 +22,8 @@ namespace lldb_dap { llvm::Expected SetBreakpointsRequestHandler::Run( const protocol::SetBreakpointsArguments &args) const { - const auto &source = args.source; - const auto path = source.path.value_or(""); - std::vector response_breakpoints; - - // Decode the source breakpoint infos for this "setBreakpoints" request - SourceBreakpointMap request_bps; - // "breakpoints" may be unset, in which case we treat it the same as being set - // to an empty array. - if (args.breakpoints) { - for (const auto &bp : *args.breakpoints) { - SourceBreakpoint src_bp(dap, bp); - std::pair bp_pos(src_bp.GetLine(), - src_bp.GetColumn()); - 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 - if (inserted) - iv->getSecond().SetBreakpoint(path.data()); - else - iv->getSecond().UpdateBreakpoint(src_bp); - - protocol::Breakpoint response_bp = iv->getSecond().ToProtocolBreakpoint(); - - // Use the path from the request if it is set - if (!path.empty()) - response_bp.source = CreateSource(path); - - if (!response_bp.line) - response_bp.line = src_bp.GetLine(); - if (!response_bp.column) - response_bp.column = src_bp.GetColumn(); - response_breakpoints.push_back(response_bp); - } - } - - // 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 = 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 - dap.target.BreakpointDelete(old_bp.second.GetID()); - old_src_bp_pos->second.erase(old_bp.first); - } - } - } - + const auto response_breakpoints = + dap.SetSourceBreakpoints(args.source, args.breakpoints); return protocol::SetBreakpointsResponseBody{std::move(response_breakpoints)}; } diff --git a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp index 0ddd87881a164..353d3365564f5 100644 --- a/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp +++ b/lldb/tools/lldb-dap/Handler/SourceRequestHandler.cpp @@ -11,6 +11,7 @@ #include "LLDBUtils.h" #include "Protocol/ProtocolRequests.h" #include "Protocol/ProtocolTypes.h" +#include "lldb/API/SBAddress.h" #include "lldb/API/SBExecutionContext.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBInstructionList.h" @@ -19,6 +20,7 @@ #include "lldb/API/SBSymbol.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" +#include "lldb/lldb-types.h" #include "llvm/Support/Error.h" namespace lldb_dap { @@ -34,26 +36,22 @@ SourceRequestHandler::Run(const protocol::SourceArguments &args) const { return llvm::make_error( "invalid arguments, expected source.sourceReference to be set"); - lldb::SBProcess process = dap.target.GetProcess(); - // Upper 32 bits is the thread index ID - lldb::SBThread thread = - process.GetThreadByIndexID(GetLLDBThreadIndexID(source)); - // Lower 32 bits is the frame index - lldb::SBFrame frame = thread.GetFrameAtIndex(GetLLDBFrameID(source)); - if (!frame.IsValid()) + lldb::SBAddress address(source, dap.target); + if (!address.IsValid()) return llvm::make_error("source not found"); + lldb::SBSymbol symbol = address.GetSymbol(); + lldb::SBStream stream; - lldb::SBExecutionContext exe_ctx(frame); - lldb::SBSymbol symbol = frame.GetSymbol(); + lldb::SBExecutionContext exe_ctx(dap.target); if (symbol.IsValid()) { lldb::SBInstructionList insts = symbol.GetInstructions(dap.target); insts.GetDescription(stream, exe_ctx); } else { // No valid symbol, just return the disassembly. - lldb::SBInstructionList insts = - dap.target.ReadInstructions(frame.GetPCAddress(), 32); + lldb::SBInstructionList insts = dap.target.ReadInstructions( + address, dap.k_number_of_assembly_lines_for_nodebug); insts.GetDescription(stream, exe_ctx); } diff --git a/lldb/tools/lldb-dap/JSONUtils.cpp b/lldb/tools/lldb-dap/JSONUtils.cpp index 714947a4d3b9c..dcc25c9212432 100644 --- a/lldb/tools/lldb-dap/JSONUtils.cpp +++ b/lldb/tools/lldb-dap/JSONUtils.cpp @@ -490,6 +490,13 @@ CreateExceptionBreakpointFilter(const ExceptionBreakpoint &bp) { return filter; } +static std::string GetLoadAddressString(const lldb::addr_t addr) { + std::string result; + llvm::raw_string_ostream os(result); + os << llvm::format_hex(addr, 18); + return result; +} + protocol::Source CreateSource(const lldb::SBFileSpec &file) { protocol::Source source; if (file.IsValid()) { @@ -516,6 +523,41 @@ protocol::Source CreateSource(llvm::StringRef source_path) { return source; } +protocol::Source CreateAssemblySource(const lldb::SBTarget &target, + lldb::SBAddress &address) { + protocol::Source source; + + auto symbol = address.GetSymbol(); + std::string name; + if (symbol.IsValid()) { + source.sourceReference = symbol.GetStartAddress().GetLoadAddress(target); + name = symbol.GetName(); + } else { + const auto load_addr = address.GetLoadAddress(target); + source.sourceReference = load_addr; + name = GetLoadAddressString(load_addr); + } + + lldb::SBModule module = address.GetModule(); + if (module.IsValid()) { + lldb::SBFileSpec file_spec = module.GetFileSpec(); + if (file_spec.IsValid()) { + std::string path = GetSBFileSpecPath(file_spec); + if (!path.empty()) + source.path = path + '`' + name; + } + } + + source.name = std::move(name); + + // Mark the source as deemphasized since users will only be able to view + // assembly for these frames. + source.presentationHint = + protocol::Source::PresentationHint::eSourcePresentationHintDeemphasize; + + return source; +} + bool ShouldDisplayAssemblySource( const lldb::SBLineEntry &line_entry, lldb::StopDisassemblyType stop_disassembly_display) { @@ -622,8 +664,7 @@ CreateStackFrame(lldb::SBFrame &frame, lldb::SBFormat &format, if (frame_name.empty()) { // If the function name is unavailable, display the pc address as a 16-digit // hex string, e.g. "0x0000000000012345" - llvm::raw_string_ostream os(frame_name); - os << llvm::format_hex(frame.GetPC(), 18); + frame_name = GetLoadAddressString(frame.GetPC()); } // We only include `[opt]` if a custom frame format is not specified. @@ -641,17 +682,10 @@ CreateStackFrame(lldb::SBFrame &frame, lldb::SBFormat &format, } else if (frame.GetSymbol().IsValid()) { // If no source is associated with the frame, use the DAPFrameID to track // the 'source' and generate assembly. - llvm::json::Object source; - EmplaceSafeString(source, "name", frame_name); - char buf[PATH_MAX] = {0}; - size_t size = frame.GetModule().GetFileSpec().GetPath(buf, PATH_MAX); - EmplaceSafeString(source, "path", - std::string(buf, size) + '`' + frame_name); - source.try_emplace("sourceReference", MakeDAPFrameID(frame)); - // Mark the source as deemphasized since users will only be able to view - // assembly for these frames. - EmplaceSafeString(source, "presentationHint", "deemphasize"); - object.try_emplace("source", std::move(source)); + auto frame_address = frame.GetPCAddress(); + object.try_emplace("source", CreateAssemblySource( + frame.GetThread().GetProcess().GetTarget(), + frame_address)); // Calculate the line of the current PC from the start of the current // symbol. @@ -665,12 +699,10 @@ CreateStackFrame(lldb::SBFrame &frame, lldb::SBFormat &format, object.try_emplace("column", 1); } else { // No valid line entry or symbol. - llvm::json::Object source; - EmplaceSafeString(source, "name", frame_name); - source.try_emplace("sourceReference", MakeDAPFrameID(frame)); - EmplaceSafeString(source, "presentationHint", "deemphasize"); - object.try_emplace("source", std::move(source)); - + auto frame_address = frame.GetPCAddress(); + object.try_emplace("source", CreateAssemblySource( + frame.GetThread().GetProcess().GetTarget(), + frame_address)); object.try_emplace("line", 1); object.try_emplace("column", 1); } diff --git a/lldb/tools/lldb-dap/JSONUtils.h b/lldb/tools/lldb-dap/JSONUtils.h index 783f291338d8c..ac9b39739104f 100644 --- a/lldb/tools/lldb-dap/JSONUtils.h +++ b/lldb/tools/lldb-dap/JSONUtils.h @@ -269,6 +269,20 @@ protocol::Source CreateSource(const lldb::SBLineEntry &line_entry); /// definition outlined by Microsoft. protocol::Source CreateSource(llvm::StringRef source_path); +/// Create a "Source" object for a given frame, using its assembly for source. +/// +/// \param[in] target +/// The relevant target. +/// +/// \param[in] address +/// The address to use when creating the "Source" object. +/// +/// \return +/// A "Source" JSON object that follows the formal JSON +/// definition outlined by Microsoft. +protocol::Source CreateAssemblySource(const lldb::SBTarget &target, + lldb::SBAddress &address); + /// Return true if the given line entry should be displayed as assembly. /// /// \param[in] line_entry diff --git a/lldb/tools/lldb-dap/LLDBUtils.cpp b/lldb/tools/lldb-dap/LLDBUtils.cpp index 0e7bb2b9058a7..fe0bcda19b4cd 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.cpp +++ b/lldb/tools/lldb-dap/LLDBUtils.cpp @@ -20,6 +20,7 @@ #include "llvm/Support/JSON.h" #include "llvm/Support/raw_ostream.h" +#include #include #include @@ -242,4 +243,13 @@ ScopeSyncMode::ScopeSyncMode(lldb::SBDebugger &debugger) ScopeSyncMode::~ScopeSyncMode() { m_debugger.SetAsync(m_async); } +std::string GetSBFileSpecPath(const lldb::SBFileSpec &file_spec) { + const auto directory_length = ::strlen(file_spec.GetDirectory()); + const auto file_name_length = ::strlen(file_spec.GetFilename()); + + std::string path(directory_length + file_name_length + 1, '\0'); + file_spec.GetPath(path.data(), path.length() + 1); + return path; +} + } // namespace lldb_dap diff --git a/lldb/tools/lldb-dap/LLDBUtils.h b/lldb/tools/lldb-dap/LLDBUtils.h index 711fc6051231c..7fdf4b859ee61 100644 --- a/lldb/tools/lldb-dap/LLDBUtils.h +++ b/lldb/tools/lldb-dap/LLDBUtils.h @@ -13,6 +13,7 @@ #include "lldb/API/SBDebugger.h" #include "lldb/API/SBEnvironment.h" #include "lldb/API/SBError.h" +#include "lldb/API/SBFileSpec.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -161,6 +162,15 @@ uint32_t GetLLDBFrameID(uint64_t dap_frame_id); lldb::SBEnvironment GetEnvironmentFromArguments(const llvm::json::Object &arguments); +/// Gets an SBFileSpec and returns its path as a string. +/// +/// \param[in] file_spec +/// The file spec. +/// +/// \return +/// The file path as a string. +std::string GetSBFileSpecPath(const lldb::SBFileSpec &file_spec); + /// Helper for sending telemetry to lldb server, if client-telemetry is enabled. class TelemetryDispatcher { public: diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.cpp b/lldb/tools/lldb-dap/SourceBreakpoint.cpp index 4581c995b4260..a4327ae18cf6c 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.cpp +++ b/lldb/tools/lldb-dap/SourceBreakpoint.cpp @@ -13,11 +13,14 @@ #include "lldb/API/SBBreakpoint.h" #include "lldb/API/SBFileSpecList.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBInstruction.h" #include "lldb/API/SBMutex.h" +#include "lldb/API/SBSymbol.h" #include "lldb/API/SBTarget.h" #include "lldb/API/SBThread.h" #include "lldb/API/SBValue.h" #include "lldb/lldb-enumerations.h" +#include "llvm/Support/Error.h" #include #include #include @@ -33,16 +36,51 @@ SourceBreakpoint::SourceBreakpoint(DAP &dap, m_line(breakpoint.line), m_column(breakpoint.column.value_or(LLDB_INVALID_COLUMN_NUMBER)) {} -void SourceBreakpoint::SetBreakpoint(const llvm::StringRef source_path) { +llvm::Error SourceBreakpoint::SetBreakpoint(const protocol::Source &source) { lldb::SBMutex lock = m_dap.GetAPIMutex(); std::lock_guard guard(lock); - lldb::SBFileSpecList module_list; - m_bp = m_dap.target.BreakpointCreateByLocation( - source_path.str().c_str(), m_line, m_column, 0, module_list); + if (m_line == 0) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid line number."); + + if (source.sourceReference) { + // Breakpoint set by assembly source. + lldb::SBAddress source_address(*source.sourceReference, m_dap.target); + if (!source_address.IsValid()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid sourceReference."); + + lldb::SBSymbol symbol = source_address.GetSymbol(); + if (!symbol.IsValid()) { + // FIXME: Support assembly breakpoints without a valid symbol. + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Breakpoints in assembly without a valid " + "symbol are not supported yet."); + } + + lldb::SBInstructionList inst_list = + m_dap.target.ReadInstructions(symbol.GetStartAddress(), m_line); + if (inst_list.GetSize() < m_line) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Invalid instruction list size."); + + lldb::SBAddress address = + inst_list.GetInstructionAtIndex(m_line - 1).GetAddress(); + + m_bp = m_dap.target.BreakpointCreateBySBAddress(address); + } else { + // Breakpoint set by a regular source file. + const auto source_path = source.path.value_or(""); + lldb::SBFileSpecList module_list; + m_bp = m_dap.target.BreakpointCreateByLocation(source_path.c_str(), m_line, + m_column, 0, module_list); + } + if (!m_log_message.empty()) SetLogMessage(); Breakpoint::SetBreakpoint(); + return llvm::Error::success(); } void SourceBreakpoint::UpdateBreakpoint(const SourceBreakpoint &request_bp) { diff --git a/lldb/tools/lldb-dap/SourceBreakpoint.h b/lldb/tools/lldb-dap/SourceBreakpoint.h index 5b15296f861c5..857ac4286d59d 100644 --- a/lldb/tools/lldb-dap/SourceBreakpoint.h +++ b/lldb/tools/lldb-dap/SourceBreakpoint.h @@ -14,6 +14,7 @@ #include "Protocol/ProtocolTypes.h" #include "lldb/API/SBError.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Error.h" #include #include #include @@ -25,7 +26,7 @@ class SourceBreakpoint : public Breakpoint { SourceBreakpoint(DAP &d, const protocol::SourceBreakpoint &breakpoint); // Set this breakpoint in LLDB as a new breakpoint - void SetBreakpoint(const llvm::StringRef source_path); + llvm::Error SetBreakpoint(const protocol::Source &source); void UpdateBreakpoint(const SourceBreakpoint &request_bp); void SetLogMessage(); diff --git a/lldb/tools/lldb-dap/package-lock.json b/lldb/tools/lldb-dap/package-lock.json index 0a2b9e764067e..af90a9573aee6 100644 --- a/lldb/tools/lldb-dap/package-lock.json +++ b/lldb/tools/lldb-dap/package-lock.json @@ -1,12 +1,12 @@ { "name": "lldb-dap", - "version": "0.2.13", + "version": "0.2.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lldb-dap", - "version": "0.2.13", + "version": "0.2.14", "license": "Apache 2.0 License with LLVM exceptions", "devDependencies": { "@types/node": "^18.19.41", diff --git a/lldb/tools/lldb-dap/package.json b/lldb/tools/lldb-dap/package.json index d5ca604798799..73e70cd961f4f 100644 --- a/lldb/tools/lldb-dap/package.json +++ b/lldb/tools/lldb-dap/package.json @@ -1,7 +1,7 @@ { "name": "lldb-dap", "displayName": "LLDB DAP", - "version": "0.2.13", + "version": "0.2.14", "publisher": "llvm-vs-code-extensions", "homepage": "https://lldb.llvm.org", "description": "Debugging with LLDB in Visual Studio Code", @@ -265,6 +265,9 @@ ] }, "breakpoints": [ + { + "language": "lldb.disassembly" + }, { "language": "ada" },