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 | |