15ffd83dbSDimitry Andric //===-- BreakpointResolverFileLine.cpp ------------------------------------===//
20b57cec5SDimitry Andric //
30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
60b57cec5SDimitry Andric //
70b57cec5SDimitry Andric //===----------------------------------------------------------------------===//
80b57cec5SDimitry Andric 
90b57cec5SDimitry Andric #include "lldb/Breakpoint/BreakpointResolverFileLine.h"
100b57cec5SDimitry Andric 
110b57cec5SDimitry Andric #include "lldb/Breakpoint/BreakpointLocation.h"
120b57cec5SDimitry Andric #include "lldb/Core/Module.h"
130b57cec5SDimitry Andric #include "lldb/Symbol/CompileUnit.h"
140b57cec5SDimitry Andric #include "lldb/Symbol/Function.h"
150b57cec5SDimitry Andric #include "lldb/Utility/Log.h"
160b57cec5SDimitry Andric #include "lldb/Utility/StreamString.h"
170b57cec5SDimitry Andric 
180b57cec5SDimitry Andric using namespace lldb;
190b57cec5SDimitry Andric using namespace lldb_private;
200b57cec5SDimitry Andric 
210b57cec5SDimitry Andric // BreakpointResolverFileLine:
BreakpointResolverFileLine(const BreakpointSP & bkpt,lldb::addr_t offset,bool skip_prologue,const SourceLocationSpec & location_spec)220b57cec5SDimitry Andric BreakpointResolverFileLine::BreakpointResolverFileLine(
23*5f7ddb14SDimitry Andric     const BreakpointSP &bkpt, lldb::addr_t offset, bool skip_prologue,
24*5f7ddb14SDimitry Andric     const SourceLocationSpec &location_spec)
250b57cec5SDimitry Andric     : BreakpointResolver(bkpt, BreakpointResolver::FileLineResolver, offset),
26*5f7ddb14SDimitry Andric       m_location_spec(location_spec), m_skip_prologue(skip_prologue) {}
270b57cec5SDimitry Andric 
CreateFromStructuredData(const BreakpointSP & bkpt,const StructuredData::Dictionary & options_dict,Status & error)280b57cec5SDimitry Andric BreakpointResolver *BreakpointResolverFileLine::CreateFromStructuredData(
295ffd83dbSDimitry Andric     const BreakpointSP &bkpt, const StructuredData::Dictionary &options_dict,
300b57cec5SDimitry Andric     Status &error) {
310b57cec5SDimitry Andric   llvm::StringRef filename;
32*5f7ddb14SDimitry Andric   uint32_t line;
33*5f7ddb14SDimitry Andric   uint16_t column;
340b57cec5SDimitry Andric   bool check_inlines;
350b57cec5SDimitry Andric   bool skip_prologue;
360b57cec5SDimitry Andric   bool exact_match;
370b57cec5SDimitry Andric   bool success;
380b57cec5SDimitry Andric 
390b57cec5SDimitry Andric   lldb::addr_t offset = 0;
400b57cec5SDimitry Andric 
410b57cec5SDimitry Andric   success = options_dict.GetValueForKeyAsString(GetKey(OptionNames::FileName),
420b57cec5SDimitry Andric                                                 filename);
430b57cec5SDimitry Andric   if (!success) {
440b57cec5SDimitry Andric     error.SetErrorString("BRFL::CFSD: Couldn't find filename entry.");
450b57cec5SDimitry Andric     return nullptr;
460b57cec5SDimitry Andric   }
470b57cec5SDimitry Andric 
480b57cec5SDimitry Andric   success = options_dict.GetValueForKeyAsInteger(
49*5f7ddb14SDimitry Andric       GetKey(OptionNames::LineNumber), line);
500b57cec5SDimitry Andric   if (!success) {
510b57cec5SDimitry Andric     error.SetErrorString("BRFL::CFSD: Couldn't find line number entry.");
520b57cec5SDimitry Andric     return nullptr;
530b57cec5SDimitry Andric   }
540b57cec5SDimitry Andric 
550b57cec5SDimitry Andric   success =
560b57cec5SDimitry Andric       options_dict.GetValueForKeyAsInteger(GetKey(OptionNames::Column), column);
570b57cec5SDimitry Andric   if (!success) {
580b57cec5SDimitry Andric     // Backwards compatibility.
590b57cec5SDimitry Andric     column = 0;
600b57cec5SDimitry Andric   }
610b57cec5SDimitry Andric 
620b57cec5SDimitry Andric   success = options_dict.GetValueForKeyAsBoolean(GetKey(OptionNames::Inlines),
630b57cec5SDimitry Andric                                                  check_inlines);
640b57cec5SDimitry Andric   if (!success) {
650b57cec5SDimitry Andric     error.SetErrorString("BRFL::CFSD: Couldn't find check inlines entry.");
660b57cec5SDimitry Andric     return nullptr;
670b57cec5SDimitry Andric   }
680b57cec5SDimitry Andric 
690b57cec5SDimitry Andric   success = options_dict.GetValueForKeyAsBoolean(
700b57cec5SDimitry Andric       GetKey(OptionNames::SkipPrologue), skip_prologue);
710b57cec5SDimitry Andric   if (!success) {
720b57cec5SDimitry Andric     error.SetErrorString("BRFL::CFSD: Couldn't find skip prologue entry.");
730b57cec5SDimitry Andric     return nullptr;
740b57cec5SDimitry Andric   }
750b57cec5SDimitry Andric 
760b57cec5SDimitry Andric   success = options_dict.GetValueForKeyAsBoolean(
770b57cec5SDimitry Andric       GetKey(OptionNames::ExactMatch), exact_match);
780b57cec5SDimitry Andric   if (!success) {
790b57cec5SDimitry Andric     error.SetErrorString("BRFL::CFSD: Couldn't find exact match entry.");
800b57cec5SDimitry Andric     return nullptr;
810b57cec5SDimitry Andric   }
820b57cec5SDimitry Andric 
83*5f7ddb14SDimitry Andric   SourceLocationSpec location_spec(FileSpec(filename), line, column,
84*5f7ddb14SDimitry Andric                                    check_inlines, exact_match);
85*5f7ddb14SDimitry Andric   if (!location_spec)
86*5f7ddb14SDimitry Andric     return nullptr;
870b57cec5SDimitry Andric 
88*5f7ddb14SDimitry Andric   return new BreakpointResolverFileLine(bkpt, offset, skip_prologue,
89*5f7ddb14SDimitry Andric                                         location_spec);
900b57cec5SDimitry Andric }
910b57cec5SDimitry Andric 
920b57cec5SDimitry Andric StructuredData::ObjectSP
SerializeToStructuredData()930b57cec5SDimitry Andric BreakpointResolverFileLine::SerializeToStructuredData() {
940b57cec5SDimitry Andric   StructuredData::DictionarySP options_dict_sp(
950b57cec5SDimitry Andric       new StructuredData::Dictionary());
960b57cec5SDimitry Andric 
970b57cec5SDimitry Andric   options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue),
980b57cec5SDimitry Andric                                   m_skip_prologue);
99*5f7ddb14SDimitry Andric   options_dict_sp->AddStringItem(GetKey(OptionNames::FileName),
100*5f7ddb14SDimitry Andric                                  m_location_spec.GetFileSpec().GetPath());
101*5f7ddb14SDimitry Andric   options_dict_sp->AddIntegerItem(GetKey(OptionNames::LineNumber),
102*5f7ddb14SDimitry Andric                                   m_location_spec.GetLine().getValueOr(0));
103*5f7ddb14SDimitry Andric   options_dict_sp->AddIntegerItem(
104*5f7ddb14SDimitry Andric       GetKey(OptionNames::Column),
105*5f7ddb14SDimitry Andric       m_location_spec.GetColumn().getValueOr(LLDB_INVALID_COLUMN_NUMBER));
106*5f7ddb14SDimitry Andric   options_dict_sp->AddBooleanItem(GetKey(OptionNames::Inlines),
107*5f7ddb14SDimitry Andric                                   m_location_spec.GetCheckInlines());
1080b57cec5SDimitry Andric   options_dict_sp->AddBooleanItem(GetKey(OptionNames::ExactMatch),
109*5f7ddb14SDimitry Andric                                   m_location_spec.GetExactMatch());
1100b57cec5SDimitry Andric 
1110b57cec5SDimitry Andric   return WrapOptionsDict(options_dict_sp);
1120b57cec5SDimitry Andric }
1130b57cec5SDimitry Andric 
1140b57cec5SDimitry Andric // Filter the symbol context list to remove contexts where the line number was
1150b57cec5SDimitry Andric // moved into a new function. We do this conservatively, so if e.g. we cannot
1160b57cec5SDimitry Andric // resolve the function in the context (which can happen in case of line-table-
1170b57cec5SDimitry Andric // only debug info), we leave the context as is. The trickiest part here is
1180b57cec5SDimitry Andric // handling inlined functions -- in this case we need to make sure we look at
1190b57cec5SDimitry Andric // the declaration line of the inlined function, NOT the function it was
1200b57cec5SDimitry Andric // inlined into.
FilterContexts(SymbolContextList & sc_list,bool is_relative)1210b57cec5SDimitry Andric void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list,
1220b57cec5SDimitry Andric                                                 bool is_relative) {
123*5f7ddb14SDimitry Andric   if (m_location_spec.GetExactMatch())
1240b57cec5SDimitry Andric     return; // Nothing to do. Contexts are precise.
1250b57cec5SDimitry Andric 
1260b57cec5SDimitry Andric   llvm::StringRef relative_path;
1270b57cec5SDimitry Andric   if (is_relative)
128*5f7ddb14SDimitry Andric     relative_path = m_location_spec.GetFileSpec().GetDirectory().GetStringRef();
1290b57cec5SDimitry Andric 
1300b57cec5SDimitry Andric   Log * log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_BREAKPOINTS);
1310b57cec5SDimitry Andric   for(uint32_t i = 0; i < sc_list.GetSize(); ++i) {
1320b57cec5SDimitry Andric     SymbolContext sc;
1330b57cec5SDimitry Andric     sc_list.GetContextAtIndex(i, sc);
1340b57cec5SDimitry Andric     if (is_relative) {
1350b57cec5SDimitry Andric       // If the path was relative, make sure any matches match as long as the
1360b57cec5SDimitry Andric       // relative parts of the path match the path from support files
1370b57cec5SDimitry Andric       auto sc_dir = sc.line_entry.file.GetDirectory().GetStringRef();
1380b57cec5SDimitry Andric       if (!sc_dir.endswith(relative_path)) {
1390b57cec5SDimitry Andric         // We had a relative path specified and the relative directory doesn't
1400b57cec5SDimitry Andric         // match so remove this one
1410b57cec5SDimitry Andric         LLDB_LOG(log, "removing not matching relative path {0} since it "
1420b57cec5SDimitry Andric                 "doesn't end with {1}", sc_dir, relative_path);
1430b57cec5SDimitry Andric         sc_list.RemoveContextAtIndex(i);
1440b57cec5SDimitry Andric         --i;
1450b57cec5SDimitry Andric         continue;
1460b57cec5SDimitry Andric       }
1470b57cec5SDimitry Andric     }
1480b57cec5SDimitry Andric 
1490b57cec5SDimitry Andric     if (!sc.block)
1500b57cec5SDimitry Andric       continue;
1510b57cec5SDimitry Andric 
1520b57cec5SDimitry Andric     FileSpec file;
1530b57cec5SDimitry Andric     uint32_t line;
1540b57cec5SDimitry Andric     const Block *inline_block = sc.block->GetContainingInlinedBlock();
1550b57cec5SDimitry Andric     if (inline_block) {
1560b57cec5SDimitry Andric       const Declaration &inline_declaration = inline_block->GetInlinedFunctionInfo()->GetDeclaration();
1570b57cec5SDimitry Andric       if (!inline_declaration.IsValid())
1580b57cec5SDimitry Andric         continue;
1590b57cec5SDimitry Andric       file = inline_declaration.GetFile();
1600b57cec5SDimitry Andric       line = inline_declaration.GetLine();
1610b57cec5SDimitry Andric     } else if (sc.function)
1620b57cec5SDimitry Andric       sc.function->GetStartLineSourceInfo(file, line);
1630b57cec5SDimitry Andric     else
1640b57cec5SDimitry Andric       continue;
1650b57cec5SDimitry Andric 
1660b57cec5SDimitry Andric     if (file != sc.line_entry.file) {
1670b57cec5SDimitry Andric       LLDB_LOG(log, "unexpected symbol context file {0}", sc.line_entry.file);
1680b57cec5SDimitry Andric       continue;
1690b57cec5SDimitry Andric     }
1700b57cec5SDimitry Andric 
1710b57cec5SDimitry Andric     // Compare the requested line number with the line of the function
1720b57cec5SDimitry Andric     // declaration. In case of a function declared as:
1730b57cec5SDimitry Andric     //
1740b57cec5SDimitry Andric     // int
1750b57cec5SDimitry Andric     // foo()
1760b57cec5SDimitry Andric     // {
1770b57cec5SDimitry Andric     //   ...
1780b57cec5SDimitry Andric     //
1790b57cec5SDimitry Andric     // the compiler will set the declaration line to the "foo" line, which is
1800b57cec5SDimitry Andric     // the reason why we have -1 here. This can fail in case of two inline
1810b57cec5SDimitry Andric     // functions defined back-to-back:
1820b57cec5SDimitry Andric     //
1830b57cec5SDimitry Andric     // inline int foo1() { ... }
1840b57cec5SDimitry Andric     // inline int foo2() { ... }
1850b57cec5SDimitry Andric     //
1860b57cec5SDimitry Andric     // but that's the best we can do for now.
1870b57cec5SDimitry Andric     // One complication, if the line number returned from GetStartLineSourceInfo
1880b57cec5SDimitry Andric     // is 0, then we can't do this calculation.  That can happen if
1890b57cec5SDimitry Andric     // GetStartLineSourceInfo gets an error, or if the first line number in
1900b57cec5SDimitry Andric     // the function really is 0 - which happens for some languages.
191af732203SDimitry Andric 
192af732203SDimitry Andric     // But only do this calculation if the line number we found in the SC
193af732203SDimitry Andric     // was different from the one requested in the source file.  If we actually
194af732203SDimitry Andric     // found an exact match it must be valid.
195af732203SDimitry Andric 
196*5f7ddb14SDimitry Andric     if (m_location_spec.GetLine() == sc.line_entry.line)
197af732203SDimitry Andric       continue;
198af732203SDimitry Andric 
1990b57cec5SDimitry Andric     const int decl_line_is_too_late_fudge = 1;
200*5f7ddb14SDimitry Andric     if (line &&
201*5f7ddb14SDimitry Andric         m_location_spec.GetLine() < line - decl_line_is_too_late_fudge) {
2020b57cec5SDimitry Andric       LLDB_LOG(log, "removing symbol context at {0}:{1}", file, line);
2030b57cec5SDimitry Andric       sc_list.RemoveContextAtIndex(i);
2040b57cec5SDimitry Andric       --i;
2050b57cec5SDimitry Andric     }
2060b57cec5SDimitry Andric   }
2070b57cec5SDimitry Andric }
2080b57cec5SDimitry Andric 
SearchCallback(SearchFilter & filter,SymbolContext & context,Address * addr)2099dba64beSDimitry Andric Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback(
2109dba64beSDimitry Andric     SearchFilter &filter, SymbolContext &context, Address *addr) {
2110b57cec5SDimitry Andric   SymbolContextList sc_list;
2120b57cec5SDimitry Andric 
2130b57cec5SDimitry Andric   // There is a tricky bit here.  You can have two compilation units that
2140b57cec5SDimitry Andric   // #include the same file, and in one of them the function at m_line_number
2150b57cec5SDimitry Andric   // is used (and so code and a line entry for it is generated) but in the
2160b57cec5SDimitry Andric   // other it isn't.  If we considered the CU's independently, then in the
2170b57cec5SDimitry Andric   // second inclusion, we'd move the breakpoint to the next function that
2180b57cec5SDimitry Andric   // actually generated code in the header file.  That would end up being
2190b57cec5SDimitry Andric   // confusing.  So instead, we do the CU iterations by hand here, then scan
2200b57cec5SDimitry Andric   // through the complete list of matches, and figure out the closest line
2210b57cec5SDimitry Andric   // number match, and only set breakpoints on that match.
2220b57cec5SDimitry Andric 
2230b57cec5SDimitry Andric   // Note also that if file_spec only had a file name and not a directory,
2240b57cec5SDimitry Andric   // there may be many different file spec's in the resultant list.  The
2250b57cec5SDimitry Andric   // closest line match for one will not be right for some totally different
2260b57cec5SDimitry Andric   // file.  So we go through the match list and pull out the sets that have the
2270b57cec5SDimitry Andric   // same file spec in their line_entry and treat each set separately.
2280b57cec5SDimitry Andric 
229*5f7ddb14SDimitry Andric   const uint32_t line = m_location_spec.GetLine().getValueOr(0);
230*5f7ddb14SDimitry Andric   const llvm::Optional<uint16_t> column = m_location_spec.GetColumn();
231*5f7ddb14SDimitry Andric 
232*5f7ddb14SDimitry Andric   FileSpec search_file_spec = m_location_spec.GetFileSpec();
233*5f7ddb14SDimitry Andric   const bool is_relative = search_file_spec.IsRelative();
2340b57cec5SDimitry Andric   if (is_relative)
2350b57cec5SDimitry Andric     search_file_spec.GetDirectory().Clear();
2360b57cec5SDimitry Andric 
2370b57cec5SDimitry Andric   const size_t num_comp_units = context.module_sp->GetNumCompileUnits();
2380b57cec5SDimitry Andric   for (size_t i = 0; i < num_comp_units; i++) {
2390b57cec5SDimitry Andric     CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i));
2400b57cec5SDimitry Andric     if (cu_sp) {
2410b57cec5SDimitry Andric       if (filter.CompUnitPasses(*cu_sp))
242*5f7ddb14SDimitry Andric         cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything,
2430b57cec5SDimitry Andric                                     sc_list);
2440b57cec5SDimitry Andric     }
2450b57cec5SDimitry Andric   }
2460b57cec5SDimitry Andric 
2470b57cec5SDimitry Andric   FilterContexts(sc_list, is_relative);
2480b57cec5SDimitry Andric 
2490b57cec5SDimitry Andric   StreamString s;
250*5f7ddb14SDimitry Andric   s.Printf("for %s:%d ",
251*5f7ddb14SDimitry Andric            m_location_spec.GetFileSpec().GetFilename().AsCString("<Unknown>"),
252*5f7ddb14SDimitry Andric            line);
2530b57cec5SDimitry Andric 
254*5f7ddb14SDimitry Andric   SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString(), line,
255*5f7ddb14SDimitry Andric                      column);
2560b57cec5SDimitry Andric 
2570b57cec5SDimitry Andric   return Searcher::eCallbackReturnContinue;
2580b57cec5SDimitry Andric }
2590b57cec5SDimitry Andric 
GetDepth()2600b57cec5SDimitry Andric lldb::SearchDepth BreakpointResolverFileLine::GetDepth() {
2610b57cec5SDimitry Andric   return lldb::eSearchDepthModule;
2620b57cec5SDimitry Andric }
2630b57cec5SDimitry Andric 
GetDescription(Stream * s)2640b57cec5SDimitry Andric void BreakpointResolverFileLine::GetDescription(Stream *s) {
265*5f7ddb14SDimitry Andric   s->Printf("file = '%s', line = %u, ",
266*5f7ddb14SDimitry Andric             m_location_spec.GetFileSpec().GetPath().c_str(),
267*5f7ddb14SDimitry Andric             m_location_spec.GetLine().getValueOr(0));
268*5f7ddb14SDimitry Andric   auto column = m_location_spec.GetColumn();
269*5f7ddb14SDimitry Andric   if (column)
270*5f7ddb14SDimitry Andric     s->Printf("column = %u, ", *column);
271*5f7ddb14SDimitry Andric   s->Printf("exact_match = %d", m_location_spec.GetExactMatch());
2720b57cec5SDimitry Andric }
2730b57cec5SDimitry Andric 
Dump(Stream * s) const2740b57cec5SDimitry Andric void BreakpointResolverFileLine::Dump(Stream *s) const {}
2750b57cec5SDimitry Andric 
2760b57cec5SDimitry Andric lldb::BreakpointResolverSP
CopyForBreakpoint(BreakpointSP & breakpoint)2775ffd83dbSDimitry Andric BreakpointResolverFileLine::CopyForBreakpoint(BreakpointSP &breakpoint) {
2780b57cec5SDimitry Andric   lldb::BreakpointResolverSP ret_sp(new BreakpointResolverFileLine(
279*5f7ddb14SDimitry Andric       breakpoint, GetOffset(), m_skip_prologue, m_location_spec));
2800b57cec5SDimitry Andric 
2810b57cec5SDimitry Andric   return ret_sp;
2820b57cec5SDimitry Andric }
283