1 //===-- BreakpointResolverFileLine.cpp --------------------------*- C++ -*-===//
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/Utility/Log.h"
16 #include "lldb/Utility/StreamString.h"
17 
18 using namespace lldb;
19 using namespace lldb_private;
20 
21 //----------------------------------------------------------------------
22 // BreakpointResolverFileLine:
23 //----------------------------------------------------------------------
24 BreakpointResolverFileLine::BreakpointResolverFileLine(
25     Breakpoint *bkpt, const FileSpec &file_spec, uint32_t line_no,
26     uint32_t column, lldb::addr_t offset, bool check_inlines,
27     bool skip_prologue, bool exact_match)
28     : BreakpointResolver(bkpt, BreakpointResolver::FileLineResolver, offset),
29       m_file_spec(file_spec), m_line_number(line_no), m_column(column),
30       m_inlines(check_inlines), m_skip_prologue(skip_prologue),
31       m_exact_match(exact_match) {}
32 
33 BreakpointResolverFileLine::~BreakpointResolverFileLine() {}
34 
35 BreakpointResolver *BreakpointResolverFileLine::CreateFromStructuredData(
36     Breakpoint *bkpt, const StructuredData::Dictionary &options_dict,
37     Status &error) {
38   llvm::StringRef filename;
39   uint32_t line_no;
40   uint32_t column;
41   bool check_inlines;
42   bool skip_prologue;
43   bool exact_match;
44   bool success;
45 
46   lldb::addr_t offset = 0;
47 
48   success = options_dict.GetValueForKeyAsString(GetKey(OptionNames::FileName),
49                                                 filename);
50   if (!success) {
51     error.SetErrorString("BRFL::CFSD: Couldn't find filename entry.");
52     return nullptr;
53   }
54 
55   success = options_dict.GetValueForKeyAsInteger(
56       GetKey(OptionNames::LineNumber), line_no);
57   if (!success) {
58     error.SetErrorString("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.SetErrorString("BRFL::CFSD: Couldn't find check inlines entry.");
73     return nullptr;
74   }
75 
76   success = options_dict.GetValueForKeyAsBoolean(
77       GetKey(OptionNames::SkipPrologue), skip_prologue);
78   if (!success) {
79     error.SetErrorString("BRFL::CFSD: Couldn't find skip prologue entry.");
80     return nullptr;
81   }
82 
83   success = options_dict.GetValueForKeyAsBoolean(
84       GetKey(OptionNames::ExactMatch), exact_match);
85   if (!success) {
86     error.SetErrorString("BRFL::CFSD: Couldn't find exact match entry.");
87     return nullptr;
88   }
89 
90   FileSpec file_spec(filename);
91 
92   return new BreakpointResolverFileLine(bkpt, file_spec, line_no, column,
93                                         offset, check_inlines, skip_prologue,
94                                         exact_match);
95 }
96 
97 StructuredData::ObjectSP
98 BreakpointResolverFileLine::SerializeToStructuredData() {
99   StructuredData::DictionarySP options_dict_sp(
100       new StructuredData::Dictionary());
101 
102   options_dict_sp->AddStringItem(GetKey(OptionNames::FileName),
103                                  m_file_spec.GetPath());
104   options_dict_sp->AddIntegerItem(GetKey(OptionNames::LineNumber),
105                                   m_line_number);
106   options_dict_sp->AddIntegerItem(GetKey(OptionNames::Column),
107                                   m_column);
108   options_dict_sp->AddBooleanItem(GetKey(OptionNames::Inlines), m_inlines);
109   options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue),
110                                   m_skip_prologue);
111   options_dict_sp->AddBooleanItem(GetKey(OptionNames::ExactMatch),
112                                   m_exact_match);
113 
114   return WrapOptionsDict(options_dict_sp);
115 }
116 
117 // Filter the symbol context list to remove contexts where the line number was
118 // moved into a new function. We do this conservatively, so if e.g. we cannot
119 // resolve the function in the context (which can happen in case of line-table-
120 // only debug info), we leave the context as is. The trickiest part here is
121 // handling inlined functions -- in this case we need to make sure we look at
122 // the declaration line of the inlined function, NOT the function it was
123 // inlined into.
124 void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list,
125                                                 bool is_relative) {
126   if (m_exact_match)
127     return; // Nothing to do. Contexts are precise.
128 
129   llvm::StringRef relative_path;
130   if (is_relative)
131     relative_path = m_file_spec.GetDirectory().GetStringRef();
132 
133   Log * log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS);
134   for(uint32_t i = 0; i < sc_list.GetSize(); ++i) {
135     SymbolContext sc;
136     sc_list.GetContextAtIndex(i, sc);
137     if (is_relative) {
138       // If the path was relative, make sure any matches match as long as the
139       // relative parts of the path match the path from support files
140       auto sc_dir = sc.line_entry.file.GetDirectory().GetStringRef();
141       if (!sc_dir.endswith(relative_path)) {
142         // We had a relative path specified and the relative directory doesn't
143         // match so remove this one
144         LLDB_LOG(log, "removing not matching relative path {0} since it "
145                 "doesn't end with {1}", sc_dir, relative_path);
146         sc_list.RemoveContextAtIndex(i);
147         --i;
148         continue;
149       }
150     }
151 
152     if (!sc.block)
153       continue;
154 
155     FileSpec file;
156     uint32_t line;
157     const Block *inline_block = sc.block->GetContainingInlinedBlock();
158     if (inline_block) {
159       const Declaration &inline_declaration = inline_block->GetInlinedFunctionInfo()->GetDeclaration();
160       if (!inline_declaration.IsValid())
161         continue;
162       file = inline_declaration.GetFile();
163       line = inline_declaration.GetLine();
164     } else if (sc.function)
165       sc.function->GetStartLineSourceInfo(file, line);
166     else
167       continue;
168 
169     if (file != sc.line_entry.file) {
170       LLDB_LOG(log, "unexpected symbol context file {0}", sc.line_entry.file);
171       continue;
172     }
173 
174     // Compare the requested line number with the line of the function
175     // declaration. In case of a function declared as:
176     //
177     // int
178     // foo()
179     // {
180     //   ...
181     //
182     // the compiler will set the declaration line to the "foo" line, which is
183     // the reason why we have -1 here. This can fail in case of two inline
184     // functions defined back-to-back:
185     //
186     // inline int foo1() { ... }
187     // inline int foo2() { ... }
188     //
189     // but that's the best we can do for now.
190     // One complication, if the line number returned from GetStartLineSourceInfo
191     // is 0, then we can't do this calculation.  That can happen if
192     // GetStartLineSourceInfo gets an error, or if the first line number in
193     // the function really is 0 - which happens for some languages.
194     const int decl_line_is_too_late_fudge = 1;
195     if (line && m_line_number < line - decl_line_is_too_late_fudge) {
196       LLDB_LOG(log, "removing symbol context at {0}:{1}", file, line);
197       sc_list.RemoveContextAtIndex(i);
198       --i;
199     }
200   }
201 }
202 
203 Searcher::CallbackReturn
204 BreakpointResolverFileLine::SearchCallback(SearchFilter &filter,
205                                            SymbolContext &context,
206                                            Address *addr, bool containing) {
207   SymbolContextList sc_list;
208 
209   assert(m_breakpoint != NULL);
210 
211   // There is a tricky bit here.  You can have two compilation units that
212   // #include the same file, and in one of them the function at m_line_number
213   // is used (and so code and a line entry for it is generated) but in the
214   // other it isn't.  If we considered the CU's independently, then in the
215   // second inclusion, we'd move the breakpoint to the next function that
216   // actually generated code in the header file.  That would end up being
217   // confusing.  So instead, we do the CU iterations by hand here, then scan
218   // through the complete list of matches, and figure out the closest line
219   // number match, and only set breakpoints on that match.
220 
221   // Note also that if file_spec only had a file name and not a directory,
222   // there may be many different file spec's in the resultant list.  The
223   // closest line match for one will not be right for some totally different
224   // file.  So we go through the match list and pull out the sets that have the
225   // same file spec in their line_entry and treat each set separately.
226 
227   FileSpec search_file_spec = m_file_spec;
228   const bool is_relative = m_file_spec.IsRelative();
229   if (is_relative)
230     search_file_spec.GetDirectory().Clear();
231 
232   const size_t num_comp_units = context.module_sp->GetNumCompileUnits();
233   for (size_t i = 0; i < num_comp_units; i++) {
234     CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i));
235     if (cu_sp) {
236       if (filter.CompUnitPasses(*cu_sp))
237         cu_sp->ResolveSymbolContext(search_file_spec, m_line_number, m_inlines,
238                                     m_exact_match, eSymbolContextEverything,
239                                     sc_list);
240     }
241   }
242 
243   FilterContexts(sc_list, is_relative);
244 
245   StreamString s;
246   s.Printf("for %s:%d ", m_file_spec.GetFilename().AsCString("<Unknown>"),
247            m_line_number);
248 
249   SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString(),
250                      m_line_number, m_column);
251 
252   return Searcher::eCallbackReturnContinue;
253 }
254 
255 lldb::SearchDepth BreakpointResolverFileLine::GetDepth() {
256   return lldb::eSearchDepthModule;
257 }
258 
259 void BreakpointResolverFileLine::GetDescription(Stream *s) {
260   s->Printf("file = '%s', line = %u, ", m_file_spec.GetPath().c_str(),
261             m_line_number);
262   if (m_column)
263     s->Printf("column = %u, ", m_column);
264   s->Printf("exact_match = %d", m_exact_match);
265 }
266 
267 void BreakpointResolverFileLine::Dump(Stream *s) const {}
268 
269 lldb::BreakpointResolverSP
270 BreakpointResolverFileLine::CopyForBreakpoint(Breakpoint &breakpoint) {
271   lldb::BreakpointResolverSP ret_sp(new BreakpointResolverFileLine(
272       &breakpoint, m_file_spec, m_line_number, m_column, m_offset, m_inlines,
273       m_skip_prologue, m_exact_match));
274 
275   return ret_sp;
276 }
277