xref: /llvm-project/lldb/source/Breakpoint/BreakpointResolverFileLine.cpp (revision 39b2979a434e70a4ce76d4adf91572dcfc9662ff)
1 //===-- BreakpointResolverFileLine.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 "lldb/Breakpoint/BreakpointResolverFileLine.h"
10 
11 #include "lldb/Breakpoint/BreakpointLocation.h"
12 #include "lldb/Core/Module.h"
13 #include "lldb/Symbol/CompileUnit.h"
14 #include "lldb/Symbol/Function.h"
15 #include "lldb/Target/Target.h"
16 #include "lldb/Utility/LLDBLog.h"
17 #include "lldb/Utility/Log.h"
18 #include "lldb/Utility/RealpathPrefixes.h"
19 #include "lldb/Utility/StreamString.h"
20 #include <optional>
21 
22 using namespace lldb;
23 using namespace lldb_private;
24 
25 // BreakpointResolverFileLine:
26 BreakpointResolverFileLine::BreakpointResolverFileLine(
27     const BreakpointSP &bkpt, lldb::addr_t offset, bool skip_prologue,
28     const SourceLocationSpec &location_spec,
29     std::optional<llvm::StringRef> removed_prefix_opt)
30     : BreakpointResolver(bkpt, BreakpointResolver::FileLineResolver, offset),
31       m_location_spec(location_spec), m_skip_prologue(skip_prologue),
32       m_removed_prefix_opt(removed_prefix_opt) {}
33 
34 BreakpointResolverSP BreakpointResolverFileLine::CreateFromStructuredData(
35     const StructuredData::Dictionary &options_dict, Status &error) {
36   llvm::StringRef filename;
37   uint32_t line;
38   uint16_t column;
39   bool check_inlines;
40   bool skip_prologue;
41   bool exact_match;
42   bool success;
43 
44   lldb::addr_t offset = 0;
45 
46   success = options_dict.GetValueForKeyAsString(GetKey(OptionNames::FileName),
47                                                 filename);
48   if (!success) {
49     error =
50         Status::FromErrorString("BRFL::CFSD: Couldn't find filename entry.");
51     return nullptr;
52   }
53 
54   success = options_dict.GetValueForKeyAsInteger(
55       GetKey(OptionNames::LineNumber), line);
56   if (!success) {
57     error =
58         Status::FromErrorString("BRFL::CFSD: Couldn't find line number entry.");
59     return nullptr;
60   }
61 
62   success =
63       options_dict.GetValueForKeyAsInteger(GetKey(OptionNames::Column), column);
64   if (!success) {
65     // Backwards compatibility.
66     column = 0;
67   }
68 
69   success = options_dict.GetValueForKeyAsBoolean(GetKey(OptionNames::Inlines),
70                                                  check_inlines);
71   if (!success) {
72     error = Status::FromErrorString(
73         "BRFL::CFSD: Couldn't find check inlines entry.");
74     return nullptr;
75   }
76 
77   success = options_dict.GetValueForKeyAsBoolean(
78       GetKey(OptionNames::SkipPrologue), skip_prologue);
79   if (!success) {
80     error = Status::FromErrorString(
81         "BRFL::CFSD: Couldn't find skip prologue entry.");
82     return nullptr;
83   }
84 
85   success = options_dict.GetValueForKeyAsBoolean(
86       GetKey(OptionNames::ExactMatch), exact_match);
87   if (!success) {
88     error =
89         Status::FromErrorString("BRFL::CFSD: Couldn't find exact match entry.");
90     return nullptr;
91   }
92 
93   SourceLocationSpec location_spec(FileSpec(filename), line, column,
94                                    check_inlines, exact_match);
95   if (!location_spec)
96     return nullptr;
97 
98   return std::make_shared<BreakpointResolverFileLine>(
99       nullptr, offset, skip_prologue, location_spec);
100 }
101 
102 StructuredData::ObjectSP
103 BreakpointResolverFileLine::SerializeToStructuredData() {
104   StructuredData::DictionarySP options_dict_sp(
105       new StructuredData::Dictionary());
106 
107   options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue),
108                                   m_skip_prologue);
109   options_dict_sp->AddStringItem(GetKey(OptionNames::FileName),
110                                  m_location_spec.GetFileSpec().GetPath());
111   options_dict_sp->AddIntegerItem(GetKey(OptionNames::LineNumber),
112                                   m_location_spec.GetLine().value_or(0));
113   options_dict_sp->AddIntegerItem(
114       GetKey(OptionNames::Column),
115       m_location_spec.GetColumn().value_or(LLDB_INVALID_COLUMN_NUMBER));
116   options_dict_sp->AddBooleanItem(GetKey(OptionNames::Inlines),
117                                   m_location_spec.GetCheckInlines());
118   options_dict_sp->AddBooleanItem(GetKey(OptionNames::ExactMatch),
119                                   m_location_spec.GetExactMatch());
120 
121   return WrapOptionsDict(options_dict_sp);
122 }
123 
124 // Filter the symbol context list to remove contexts where the line number was
125 // moved into a new function. We do this conservatively, so if e.g. we cannot
126 // resolve the function in the context (which can happen in case of line-table-
127 // only debug info), we leave the context as is. The trickiest part here is
128 // handling inlined functions -- in this case we need to make sure we look at
129 // the declaration line of the inlined function, NOT the function it was
130 // inlined into.
131 void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list) {
132   if (m_location_spec.GetExactMatch())
133     return; // Nothing to do. Contexts are precise.
134 
135   Log *log = GetLog(LLDBLog::Breakpoints);
136   for(uint32_t i = 0; i < sc_list.GetSize(); ++i) {
137     SymbolContext sc;
138     sc_list.GetContextAtIndex(i, sc);
139     if (!sc.block)
140       continue;
141 
142     SupportFileSP file_sp;
143     uint32_t line;
144     const Block *inline_block = sc.block->GetContainingInlinedBlock();
145     if (inline_block) {
146       const Declaration &inline_declaration = inline_block->GetInlinedFunctionInfo()->GetDeclaration();
147       if (!inline_declaration.IsValid())
148         continue;
149       file_sp = std::make_shared<SupportFile>(inline_declaration.GetFile());
150       line = inline_declaration.GetLine();
151     } else if (sc.function)
152       sc.function->GetStartLineSourceInfo(file_sp, line);
153     else
154       continue;
155 
156     if (!file_sp ||
157         !file_sp->Equal(*sc.line_entry.file_sp,
158                         SupportFile::eEqualFileSpecAndChecksumIfSet)) {
159       LLDB_LOG(log, "unexpected symbol context file {0}",
160                sc.line_entry.GetFile());
161       continue;
162     }
163 
164     // Compare the requested line number with the line of the function
165     // declaration. In case of a function declared as:
166     //
167     // int
168     // foo()
169     // {
170     //   ...
171     //
172     // the compiler will set the declaration line to the "foo" line, which is
173     // the reason why we have -1 here. This can fail in case of two inline
174     // functions defined back-to-back:
175     //
176     // inline int foo1() { ... }
177     // inline int foo2() { ... }
178     //
179     // but that's the best we can do for now.
180     // One complication, if the line number returned from GetStartLineSourceInfo
181     // is 0, then we can't do this calculation.  That can happen if
182     // GetStartLineSourceInfo gets an error, or if the first line number in
183     // the function really is 0 - which happens for some languages.
184 
185     // But only do this calculation if the line number we found in the SC
186     // was different from the one requested in the source file.  If we actually
187     // found an exact match it must be valid.
188 
189     if (m_location_spec.GetLine() == sc.line_entry.line)
190       continue;
191 
192     const int decl_line_is_too_late_fudge = 1;
193     if (line &&
194         m_location_spec.GetLine() < line - decl_line_is_too_late_fudge) {
195       LLDB_LOG(log, "removing symbol context at {0}:{1}",
196                file_sp->GetSpecOnly(), line);
197       sc_list.RemoveContextAtIndex(i);
198       --i;
199     }
200   }
201 }
202 
203 void BreakpointResolverFileLine::DeduceSourceMapping(
204     const SymbolContextList &sc_list) {
205   Target &target = GetBreakpoint()->GetTarget();
206   if (!target.GetAutoSourceMapRelative())
207     return;
208 
209   Log *log = GetLog(LLDBLog::Breakpoints);
210   // Check if "b" is a suffix of "a".
211   // And return std::nullopt if not or the new path
212   // of "a" after consuming "b" from the back.
213   auto check_suffix =
214       [](llvm::StringRef a, llvm::StringRef b,
215          bool case_sensitive) -> std::optional<llvm::StringRef> {
216     if (case_sensitive ? a.consume_back(b) : a.consume_back_insensitive(b)) {
217       // Note sc_file_dir and request_file_dir below are normalized
218       // and always contain the path separator '/'.
219       if (a.empty() || a.ends_with("/")) {
220         return a;
221       }
222     }
223     return std::nullopt;
224   };
225 
226   FileSpec request_file = m_location_spec.GetFileSpec();
227 
228   // Only auto deduce source map if breakpoint is full path.
229   // Note: an existing source map reverse mapping (m_removed_prefix_opt has
230   // value) may make request_file relative.
231   if (!m_removed_prefix_opt.has_value() && request_file.IsRelative())
232     return;
233 
234   const bool case_sensitive = request_file.IsCaseSensitive();
235   for (const SymbolContext &sc : sc_list) {
236     FileSpec sc_file = sc.line_entry.GetFile();
237 
238     if (FileSpec::Equal(sc_file, request_file, /*full*/ true))
239       continue;
240 
241     llvm::StringRef sc_file_dir = sc_file.GetDirectory().GetStringRef();
242     llvm::StringRef request_file_dir =
243         request_file.GetDirectory().GetStringRef();
244 
245     llvm::StringRef new_mapping_from;
246     llvm::SmallString<256> new_mapping_to;
247 
248     // Adding back any potentially reverse mapping stripped prefix.
249     // for new_mapping_to.
250     if (m_removed_prefix_opt.has_value())
251       llvm::sys::path::append(new_mapping_to, *m_removed_prefix_opt);
252 
253     std::optional<llvm::StringRef> new_mapping_from_opt =
254         check_suffix(sc_file_dir, request_file_dir, case_sensitive);
255     if (new_mapping_from_opt) {
256       new_mapping_from = *new_mapping_from_opt;
257       if (new_mapping_to.empty())
258         new_mapping_to = ".";
259     } else {
260       std::optional<llvm::StringRef> new_mapping_to_opt =
261           check_suffix(request_file_dir, sc_file_dir, case_sensitive);
262       if (new_mapping_to_opt) {
263         new_mapping_from = ".";
264         llvm::sys::path::append(new_mapping_to, *new_mapping_to_opt);
265       }
266     }
267 
268     if (!new_mapping_from.empty() && !new_mapping_to.empty()) {
269       LLDB_LOG(log, "generating auto source map from {0} to {1}",
270                new_mapping_from, new_mapping_to);
271       if (target.GetSourcePathMap().AppendUnique(new_mapping_from,
272                                                  new_mapping_to,
273                                                  /*notify*/ true))
274         target.GetStatistics().IncreaseSourceMapDeduceCount();
275     }
276   }
277 }
278 
279 Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback(
280     SearchFilter &filter, SymbolContext &context, Address *addr) {
281   SymbolContextList sc_list;
282 
283   // There is a tricky bit here.  You can have two compilation units that
284   // #include the same file, and in one of them the function at m_line_number
285   // is used (and so code and a line entry for it is generated) but in the
286   // other it isn't.  If we considered the CU's independently, then in the
287   // second inclusion, we'd move the breakpoint to the next function that
288   // actually generated code in the header file.  That would end up being
289   // confusing.  So instead, we do the CU iterations by hand here, then scan
290   // through the complete list of matches, and figure out the closest line
291   // number match, and only set breakpoints on that match.
292 
293   // Note also that if file_spec only had a file name and not a directory,
294   // there may be many different file spec's in the resultant list.  The
295   // closest line match for one will not be right for some totally different
296   // file.  So we go through the match list and pull out the sets that have the
297   // same file spec in their line_entry and treat each set separately.
298 
299   const uint32_t line = m_location_spec.GetLine().value_or(0);
300   const std::optional<uint16_t> column = m_location_spec.GetColumn();
301 
302   Target &target = GetBreakpoint()->GetTarget();
303   RealpathPrefixes realpath_prefixes = target.GetSourceRealpathPrefixes();
304 
305   const size_t num_comp_units = context.module_sp->GetNumCompileUnits();
306   for (size_t i = 0; i < num_comp_units; i++) {
307     CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i));
308     if (cu_sp) {
309       if (filter.CompUnitPasses(*cu_sp))
310         cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything,
311                                     sc_list, &realpath_prefixes);
312     }
313   }
314 
315   // Gather stats into the Target
316   target.GetStatistics().IncreaseSourceRealpathAttemptCount(
317       realpath_prefixes.GetSourceRealpathAttemptCount());
318   target.GetStatistics().IncreaseSourceRealpathCompatibleCount(
319       realpath_prefixes.GetSourceRealpathCompatibleCount());
320 
321   FilterContexts(sc_list);
322 
323   DeduceSourceMapping(sc_list);
324 
325   StreamString s;
326   s.Printf("for %s:%d ",
327            m_location_spec.GetFileSpec().GetFilename().AsCString("<Unknown>"),
328            line);
329 
330   SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString(), line,
331                      column);
332 
333   return Searcher::eCallbackReturnContinue;
334 }
335 
336 lldb::SearchDepth BreakpointResolverFileLine::GetDepth() {
337   return lldb::eSearchDepthModule;
338 }
339 
340 void BreakpointResolverFileLine::GetDescription(Stream *s) {
341   s->Printf("file = '%s', line = %u, ",
342             m_location_spec.GetFileSpec().GetPath().c_str(),
343             m_location_spec.GetLine().value_or(0));
344   auto column = m_location_spec.GetColumn();
345   if (column)
346     s->Printf("column = %u, ", *column);
347   s->Printf("exact_match = %d", m_location_spec.GetExactMatch());
348 }
349 
350 void BreakpointResolverFileLine::Dump(Stream *s) const {}
351 
352 lldb::BreakpointResolverSP
353 BreakpointResolverFileLine::CopyForBreakpoint(BreakpointSP &breakpoint) {
354   lldb::BreakpointResolverSP ret_sp(new BreakpointResolverFileLine(
355       breakpoint, GetOffset(), m_skip_prologue, m_location_spec));
356 
357   return ret_sp;
358 }
359