| 1 | //===-- BreakpointLocationsHandler..cpp -----------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "DAP.h" |
| 10 | #include "RequestHandler.h" |
| 11 | #include <optional> |
| 12 | #include <vector> |
| 13 | |
| 14 | namespace lldb_dap { |
| 15 | |
| 16 | /// The `breakpointLocations` request returns all possible locations for source |
| 17 | /// breakpoints in a given range. Clients should only call this request if the |
| 18 | /// corresponding capability `supportsBreakpointLocationsRequest` is true. |
| 19 | llvm::Expected<protocol::BreakpointLocationsResponseBody> |
| 20 | BreakpointLocationsRequestHandler::Run( |
| 21 | const protocol::BreakpointLocationsArguments &args) const { |
| 22 | uint32_t start_line = args.line; |
| 23 | uint32_t start_column = args.column.value_or(LLDB_INVALID_COLUMN_NUMBER); |
| 24 | uint32_t end_line = args.endLine.value_or(u&: start_line); |
| 25 | uint32_t end_column = |
| 26 | args.endColumn.value_or(u: std::numeric_limits<uint32_t>::max()); |
| 27 | |
| 28 | // Find all relevant lines & columns. |
| 29 | std::vector<std::pair<uint32_t, uint32_t>> locations; |
| 30 | if (args.source.sourceReference) { |
| 31 | locations = GetAssemblyBreakpointLocations(source_reference: *args.source.sourceReference, |
| 32 | start_line, end_line); |
| 33 | } else { |
| 34 | std::string path = args.source.path.value_or(u: "" ); |
| 35 | locations = GetSourceBreakpointLocations( |
| 36 | path: std::move(path), start_line, start_column, end_line, end_column); |
| 37 | } |
| 38 | |
| 39 | // The line entries are sorted by addresses, but we must return the list |
| 40 | // ordered by line / column position. |
| 41 | std::sort(first: locations.begin(), last: locations.end()); |
| 42 | locations.erase(first: llvm::unique(R&: locations), last: locations.end()); |
| 43 | |
| 44 | std::vector<protocol::BreakpointLocation> breakpoint_locations; |
| 45 | for (auto &l : locations) |
| 46 | breakpoint_locations.push_back( |
| 47 | x: {.line: l.first, .column: l.second, .endLine: std::nullopt, .endColumn: std::nullopt}); |
| 48 | |
| 49 | return protocol::BreakpointLocationsResponseBody{ |
| 50 | /*breakpoints=*/std::move(breakpoint_locations)}; |
| 51 | } |
| 52 | |
| 53 | std::vector<std::pair<uint32_t, uint32_t>> |
| 54 | BreakpointLocationsRequestHandler::GetSourceBreakpointLocations( |
| 55 | std::string path, uint32_t start_line, uint32_t start_column, |
| 56 | uint32_t end_line, uint32_t end_column) const { |
| 57 | std::vector<std::pair<uint32_t, uint32_t>> locations; |
| 58 | lldb::SBFileSpec file_spec(path.c_str(), true); |
| 59 | lldb::SBSymbolContextList compile_units = |
| 60 | dap.target.FindCompileUnits(sb_file_spec: file_spec); |
| 61 | |
| 62 | for (uint32_t c_idx = 0, c_limit = compile_units.GetSize(); c_idx < c_limit; |
| 63 | ++c_idx) { |
| 64 | const lldb::SBCompileUnit &compile_unit = |
| 65 | compile_units.GetContextAtIndex(idx: c_idx).GetCompileUnit(); |
| 66 | if (!compile_unit.IsValid()) |
| 67 | continue; |
| 68 | lldb::SBFileSpec primary_file_spec = compile_unit.GetFileSpec(); |
| 69 | |
| 70 | // Go through the line table and find all matching lines / columns |
| 71 | for (uint32_t l_idx = 0, l_limit = compile_unit.GetNumLineEntries(); |
| 72 | l_idx < l_limit; ++l_idx) { |
| 73 | lldb::SBLineEntry line_entry = compile_unit.GetLineEntryAtIndex(idx: l_idx); |
| 74 | |
| 75 | // Filter by line / column |
| 76 | uint32_t line = line_entry.GetLine(); |
| 77 | if (line < start_line || line > end_line) |
| 78 | continue; |
| 79 | uint32_t column = line_entry.GetColumn(); |
| 80 | if (column == LLDB_INVALID_COLUMN_NUMBER) |
| 81 | continue; |
| 82 | if (line == start_line && column < start_column) |
| 83 | continue; |
| 84 | if (line == end_line && column > end_column) |
| 85 | continue; |
| 86 | |
| 87 | // Make sure we are in the right file. |
| 88 | // We might have a match on line & column range and still |
| 89 | // be in the wrong file, e.g. for included files. |
| 90 | // Given that the involved pointers point into LLDB's string pool, |
| 91 | // we can directly compare the `const char*` pointers. |
| 92 | if (line_entry.GetFileSpec().GetFilename() != |
| 93 | primary_file_spec.GetFilename() || |
| 94 | line_entry.GetFileSpec().GetDirectory() != |
| 95 | primary_file_spec.GetDirectory()) |
| 96 | continue; |
| 97 | |
| 98 | locations.emplace_back(args&: line, args&: column); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | return locations; |
| 103 | } |
| 104 | |
| 105 | std::vector<std::pair<uint32_t, uint32_t>> |
| 106 | BreakpointLocationsRequestHandler::GetAssemblyBreakpointLocations( |
| 107 | int64_t source_reference, uint32_t start_line, uint32_t end_line) const { |
| 108 | std::vector<std::pair<uint32_t, uint32_t>> locations; |
| 109 | lldb::SBAddress address(source_reference, dap.target); |
| 110 | if (!address.IsValid()) |
| 111 | return locations; |
| 112 | |
| 113 | lldb::SBSymbol symbol = address.GetSymbol(); |
| 114 | if (!symbol.IsValid()) |
| 115 | return locations; |
| 116 | |
| 117 | // start_line is relative to the symbol's start address. |
| 118 | lldb::SBInstructionList insts = symbol.GetInstructions(target: dap.target); |
| 119 | if (insts.GetSize() > (start_line - 1)) |
| 120 | locations.reserve(n: insts.GetSize() - (start_line - 1)); |
| 121 | for (uint32_t i = start_line - 1; i < insts.GetSize() && i <= (end_line - 1); |
| 122 | ++i) { |
| 123 | locations.emplace_back(args&: i, args: 1); |
| 124 | } |
| 125 | |
| 126 | return locations; |
| 127 | } |
| 128 | |
| 129 | } // namespace lldb_dap |
| 130 | |