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" 15bdd1243dSDimitry Andric #include "lldb/Target/Target.h" 1681ad6265SDimitry Andric #include "lldb/Utility/LLDBLog.h" 170b57cec5SDimitry Andric #include "lldb/Utility/Log.h" 180b57cec5SDimitry Andric #include "lldb/Utility/StreamString.h" 19bdd1243dSDimitry Andric #include <optional> 200b57cec5SDimitry Andric 210b57cec5SDimitry Andric using namespace lldb; 220b57cec5SDimitry Andric using namespace lldb_private; 230b57cec5SDimitry Andric 240b57cec5SDimitry Andric // BreakpointResolverFileLine: 250b57cec5SDimitry Andric BreakpointResolverFileLine::BreakpointResolverFileLine( 26fe6060f1SDimitry Andric const BreakpointSP &bkpt, lldb::addr_t offset, bool skip_prologue, 27bdd1243dSDimitry Andric const SourceLocationSpec &location_spec, 28bdd1243dSDimitry Andric std::optional<llvm::StringRef> removed_prefix_opt) 290b57cec5SDimitry Andric : BreakpointResolver(bkpt, BreakpointResolver::FileLineResolver, offset), 30bdd1243dSDimitry Andric m_location_spec(location_spec), m_skip_prologue(skip_prologue), 31bdd1243dSDimitry Andric m_removed_prefix_opt(removed_prefix_opt) {} 320b57cec5SDimitry Andric 330b57cec5SDimitry Andric BreakpointResolver *BreakpointResolverFileLine::CreateFromStructuredData( 345ffd83dbSDimitry Andric const BreakpointSP &bkpt, const StructuredData::Dictionary &options_dict, 350b57cec5SDimitry Andric Status &error) { 360b57cec5SDimitry Andric llvm::StringRef filename; 37fe6060f1SDimitry Andric uint32_t line; 38fe6060f1SDimitry Andric uint16_t column; 390b57cec5SDimitry Andric bool check_inlines; 400b57cec5SDimitry Andric bool skip_prologue; 410b57cec5SDimitry Andric bool exact_match; 420b57cec5SDimitry Andric bool success; 430b57cec5SDimitry Andric 440b57cec5SDimitry Andric lldb::addr_t offset = 0; 450b57cec5SDimitry Andric 460b57cec5SDimitry Andric success = options_dict.GetValueForKeyAsString(GetKey(OptionNames::FileName), 470b57cec5SDimitry Andric filename); 480b57cec5SDimitry Andric if (!success) { 490b57cec5SDimitry Andric error.SetErrorString("BRFL::CFSD: Couldn't find filename entry."); 500b57cec5SDimitry Andric return nullptr; 510b57cec5SDimitry Andric } 520b57cec5SDimitry Andric 530b57cec5SDimitry Andric success = options_dict.GetValueForKeyAsInteger( 54fe6060f1SDimitry Andric GetKey(OptionNames::LineNumber), line); 550b57cec5SDimitry Andric if (!success) { 560b57cec5SDimitry Andric error.SetErrorString("BRFL::CFSD: Couldn't find line number entry."); 570b57cec5SDimitry Andric return nullptr; 580b57cec5SDimitry Andric } 590b57cec5SDimitry Andric 600b57cec5SDimitry Andric success = 610b57cec5SDimitry Andric options_dict.GetValueForKeyAsInteger(GetKey(OptionNames::Column), column); 620b57cec5SDimitry Andric if (!success) { 630b57cec5SDimitry Andric // Backwards compatibility. 640b57cec5SDimitry Andric column = 0; 650b57cec5SDimitry Andric } 660b57cec5SDimitry Andric 670b57cec5SDimitry Andric success = options_dict.GetValueForKeyAsBoolean(GetKey(OptionNames::Inlines), 680b57cec5SDimitry Andric check_inlines); 690b57cec5SDimitry Andric if (!success) { 700b57cec5SDimitry Andric error.SetErrorString("BRFL::CFSD: Couldn't find check inlines entry."); 710b57cec5SDimitry Andric return nullptr; 720b57cec5SDimitry Andric } 730b57cec5SDimitry Andric 740b57cec5SDimitry Andric success = options_dict.GetValueForKeyAsBoolean( 750b57cec5SDimitry Andric GetKey(OptionNames::SkipPrologue), skip_prologue); 760b57cec5SDimitry Andric if (!success) { 770b57cec5SDimitry Andric error.SetErrorString("BRFL::CFSD: Couldn't find skip prologue entry."); 780b57cec5SDimitry Andric return nullptr; 790b57cec5SDimitry Andric } 800b57cec5SDimitry Andric 810b57cec5SDimitry Andric success = options_dict.GetValueForKeyAsBoolean( 820b57cec5SDimitry Andric GetKey(OptionNames::ExactMatch), exact_match); 830b57cec5SDimitry Andric if (!success) { 840b57cec5SDimitry Andric error.SetErrorString("BRFL::CFSD: Couldn't find exact match entry."); 850b57cec5SDimitry Andric return nullptr; 860b57cec5SDimitry Andric } 870b57cec5SDimitry Andric 88fe6060f1SDimitry Andric SourceLocationSpec location_spec(FileSpec(filename), line, column, 89fe6060f1SDimitry Andric check_inlines, exact_match); 90fe6060f1SDimitry Andric if (!location_spec) 91fe6060f1SDimitry Andric return nullptr; 920b57cec5SDimitry Andric 93fe6060f1SDimitry Andric return new BreakpointResolverFileLine(bkpt, offset, skip_prologue, 94fe6060f1SDimitry Andric location_spec); 950b57cec5SDimitry Andric } 960b57cec5SDimitry Andric 970b57cec5SDimitry Andric StructuredData::ObjectSP 980b57cec5SDimitry Andric BreakpointResolverFileLine::SerializeToStructuredData() { 990b57cec5SDimitry Andric StructuredData::DictionarySP options_dict_sp( 1000b57cec5SDimitry Andric new StructuredData::Dictionary()); 1010b57cec5SDimitry Andric 1020b57cec5SDimitry Andric options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue), 1030b57cec5SDimitry Andric m_skip_prologue); 104fe6060f1SDimitry Andric options_dict_sp->AddStringItem(GetKey(OptionNames::FileName), 105fe6060f1SDimitry Andric m_location_spec.GetFileSpec().GetPath()); 106fe6060f1SDimitry Andric options_dict_sp->AddIntegerItem(GetKey(OptionNames::LineNumber), 10781ad6265SDimitry Andric m_location_spec.GetLine().value_or(0)); 108fe6060f1SDimitry Andric options_dict_sp->AddIntegerItem( 109fe6060f1SDimitry Andric GetKey(OptionNames::Column), 11081ad6265SDimitry Andric m_location_spec.GetColumn().value_or(LLDB_INVALID_COLUMN_NUMBER)); 111fe6060f1SDimitry Andric options_dict_sp->AddBooleanItem(GetKey(OptionNames::Inlines), 112fe6060f1SDimitry Andric m_location_spec.GetCheckInlines()); 1130b57cec5SDimitry Andric options_dict_sp->AddBooleanItem(GetKey(OptionNames::ExactMatch), 114fe6060f1SDimitry Andric m_location_spec.GetExactMatch()); 1150b57cec5SDimitry Andric 1160b57cec5SDimitry Andric return WrapOptionsDict(options_dict_sp); 1170b57cec5SDimitry Andric } 1180b57cec5SDimitry Andric 1190b57cec5SDimitry Andric // Filter the symbol context list to remove contexts where the line number was 1200b57cec5SDimitry Andric // moved into a new function. We do this conservatively, so if e.g. we cannot 1210b57cec5SDimitry Andric // resolve the function in the context (which can happen in case of line-table- 1220b57cec5SDimitry Andric // only debug info), we leave the context as is. The trickiest part here is 1230b57cec5SDimitry Andric // handling inlined functions -- in this case we need to make sure we look at 1240b57cec5SDimitry Andric // the declaration line of the inlined function, NOT the function it was 1250b57cec5SDimitry Andric // inlined into. 126bdd1243dSDimitry Andric void BreakpointResolverFileLine::FilterContexts(SymbolContextList &sc_list) { 127fe6060f1SDimitry Andric if (m_location_spec.GetExactMatch()) 1280b57cec5SDimitry Andric return; // Nothing to do. Contexts are precise. 1290b57cec5SDimitry Andric 13081ad6265SDimitry Andric Log *log = GetLog(LLDBLog::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 (!sc.block) 1350b57cec5SDimitry Andric continue; 1360b57cec5SDimitry Andric 1370b57cec5SDimitry Andric FileSpec file; 1380b57cec5SDimitry Andric uint32_t line; 1390b57cec5SDimitry Andric const Block *inline_block = sc.block->GetContainingInlinedBlock(); 1400b57cec5SDimitry Andric if (inline_block) { 1410b57cec5SDimitry Andric const Declaration &inline_declaration = inline_block->GetInlinedFunctionInfo()->GetDeclaration(); 1420b57cec5SDimitry Andric if (!inline_declaration.IsValid()) 1430b57cec5SDimitry Andric continue; 1440b57cec5SDimitry Andric file = inline_declaration.GetFile(); 1450b57cec5SDimitry Andric line = inline_declaration.GetLine(); 1460b57cec5SDimitry Andric } else if (sc.function) 1470b57cec5SDimitry Andric sc.function->GetStartLineSourceInfo(file, line); 1480b57cec5SDimitry Andric else 1490b57cec5SDimitry Andric continue; 1500b57cec5SDimitry Andric 1510b57cec5SDimitry Andric if (file != sc.line_entry.file) { 1520b57cec5SDimitry Andric LLDB_LOG(log, "unexpected symbol context file {0}", sc.line_entry.file); 1530b57cec5SDimitry Andric continue; 1540b57cec5SDimitry Andric } 1550b57cec5SDimitry Andric 1560b57cec5SDimitry Andric // Compare the requested line number with the line of the function 1570b57cec5SDimitry Andric // declaration. In case of a function declared as: 1580b57cec5SDimitry Andric // 1590b57cec5SDimitry Andric // int 1600b57cec5SDimitry Andric // foo() 1610b57cec5SDimitry Andric // { 1620b57cec5SDimitry Andric // ... 1630b57cec5SDimitry Andric // 1640b57cec5SDimitry Andric // the compiler will set the declaration line to the "foo" line, which is 1650b57cec5SDimitry Andric // the reason why we have -1 here. This can fail in case of two inline 1660b57cec5SDimitry Andric // functions defined back-to-back: 1670b57cec5SDimitry Andric // 1680b57cec5SDimitry Andric // inline int foo1() { ... } 1690b57cec5SDimitry Andric // inline int foo2() { ... } 1700b57cec5SDimitry Andric // 1710b57cec5SDimitry Andric // but that's the best we can do for now. 1720b57cec5SDimitry Andric // One complication, if the line number returned from GetStartLineSourceInfo 1730b57cec5SDimitry Andric // is 0, then we can't do this calculation. That can happen if 1740b57cec5SDimitry Andric // GetStartLineSourceInfo gets an error, or if the first line number in 1750b57cec5SDimitry Andric // the function really is 0 - which happens for some languages. 176e8d8bef9SDimitry Andric 177e8d8bef9SDimitry Andric // But only do this calculation if the line number we found in the SC 178e8d8bef9SDimitry Andric // was different from the one requested in the source file. If we actually 179e8d8bef9SDimitry Andric // found an exact match it must be valid. 180e8d8bef9SDimitry Andric 181fe6060f1SDimitry Andric if (m_location_spec.GetLine() == sc.line_entry.line) 182e8d8bef9SDimitry Andric continue; 183e8d8bef9SDimitry Andric 1840b57cec5SDimitry Andric const int decl_line_is_too_late_fudge = 1; 185fe6060f1SDimitry Andric if (line && 186fe6060f1SDimitry Andric m_location_spec.GetLine() < line - decl_line_is_too_late_fudge) { 1870b57cec5SDimitry Andric LLDB_LOG(log, "removing symbol context at {0}:{1}", file, line); 1880b57cec5SDimitry Andric sc_list.RemoveContextAtIndex(i); 1890b57cec5SDimitry Andric --i; 1900b57cec5SDimitry Andric } 1910b57cec5SDimitry Andric } 1920b57cec5SDimitry Andric } 1930b57cec5SDimitry Andric 194bdd1243dSDimitry Andric void BreakpointResolverFileLine::DeduceSourceMapping( 195*fe013be4SDimitry Andric const SymbolContextList &sc_list) { 196bdd1243dSDimitry Andric Target &target = GetBreakpoint()->GetTarget(); 197bdd1243dSDimitry Andric if (!target.GetAutoSourceMapRelative()) 198bdd1243dSDimitry Andric return; 199bdd1243dSDimitry Andric 200bdd1243dSDimitry Andric Log *log = GetLog(LLDBLog::Breakpoints); 201bdd1243dSDimitry Andric const llvm::StringRef path_separator = llvm::sys::path::get_separator( 202bdd1243dSDimitry Andric m_location_spec.GetFileSpec().GetPathStyle()); 203bdd1243dSDimitry Andric // Check if "b" is a suffix of "a". 204bdd1243dSDimitry Andric // And return std::nullopt if not or the new path 205bdd1243dSDimitry Andric // of "a" after consuming "b" from the back. 206bdd1243dSDimitry Andric auto check_suffix = 207bdd1243dSDimitry Andric [path_separator](llvm::StringRef a, llvm::StringRef b, 208bdd1243dSDimitry Andric bool case_sensitive) -> std::optional<llvm::StringRef> { 209bdd1243dSDimitry Andric if (case_sensitive ? a.consume_back(b) : a.consume_back_insensitive(b)) { 210bdd1243dSDimitry Andric if (a.empty() || a.endswith(path_separator)) { 211bdd1243dSDimitry Andric return a; 212bdd1243dSDimitry Andric } 213bdd1243dSDimitry Andric } 214bdd1243dSDimitry Andric return std::nullopt; 215bdd1243dSDimitry Andric }; 216bdd1243dSDimitry Andric 217bdd1243dSDimitry Andric FileSpec request_file = m_location_spec.GetFileSpec(); 218bdd1243dSDimitry Andric 219bdd1243dSDimitry Andric // Only auto deduce source map if breakpoint is full path. 220bdd1243dSDimitry Andric // Note: an existing source map reverse mapping (m_removed_prefix_opt has 221bdd1243dSDimitry Andric // value) may make request_file relative. 222bdd1243dSDimitry Andric if (!m_removed_prefix_opt.has_value() && request_file.IsRelative()) 223bdd1243dSDimitry Andric return; 224bdd1243dSDimitry Andric 225bdd1243dSDimitry Andric const bool case_sensitive = request_file.IsCaseSensitive(); 226*fe013be4SDimitry Andric for (const SymbolContext &sc : sc_list) { 227bdd1243dSDimitry Andric FileSpec sc_file = sc.line_entry.file; 228bdd1243dSDimitry Andric 229bdd1243dSDimitry Andric if (FileSpec::Equal(sc_file, request_file, /*full*/ true)) 230bdd1243dSDimitry Andric continue; 231bdd1243dSDimitry Andric 232bdd1243dSDimitry Andric llvm::StringRef sc_file_dir = sc_file.GetDirectory().GetStringRef(); 233bdd1243dSDimitry Andric llvm::StringRef request_file_dir = 234bdd1243dSDimitry Andric request_file.GetDirectory().GetStringRef(); 235bdd1243dSDimitry Andric 236bdd1243dSDimitry Andric llvm::StringRef new_mapping_from; 237bdd1243dSDimitry Andric llvm::SmallString<256> new_mapping_to; 238bdd1243dSDimitry Andric 239bdd1243dSDimitry Andric // Adding back any potentially reverse mapping stripped prefix. 240bdd1243dSDimitry Andric // for new_mapping_to. 241bdd1243dSDimitry Andric if (m_removed_prefix_opt.has_value()) 242bdd1243dSDimitry Andric llvm::sys::path::append(new_mapping_to, *m_removed_prefix_opt); 243bdd1243dSDimitry Andric 244bdd1243dSDimitry Andric std::optional<llvm::StringRef> new_mapping_from_opt = 245bdd1243dSDimitry Andric check_suffix(sc_file_dir, request_file_dir, case_sensitive); 246bdd1243dSDimitry Andric if (new_mapping_from_opt) { 247bdd1243dSDimitry Andric new_mapping_from = *new_mapping_from_opt; 248bdd1243dSDimitry Andric if (new_mapping_to.empty()) 249bdd1243dSDimitry Andric new_mapping_to = "."; 250bdd1243dSDimitry Andric } else { 251bdd1243dSDimitry Andric std::optional<llvm::StringRef> new_mapping_to_opt = 252bdd1243dSDimitry Andric check_suffix(request_file_dir, sc_file_dir, case_sensitive); 253bdd1243dSDimitry Andric if (new_mapping_to_opt) { 254bdd1243dSDimitry Andric new_mapping_from = "."; 255bdd1243dSDimitry Andric llvm::sys::path::append(new_mapping_to, *new_mapping_to_opt); 256bdd1243dSDimitry Andric } 257bdd1243dSDimitry Andric } 258bdd1243dSDimitry Andric 259bdd1243dSDimitry Andric if (!new_mapping_from.empty() && !new_mapping_to.empty()) { 260bdd1243dSDimitry Andric LLDB_LOG(log, "generating auto source map from {0} to {1}", 261bdd1243dSDimitry Andric new_mapping_from, new_mapping_to); 262bdd1243dSDimitry Andric if (target.GetSourcePathMap().AppendUnique(new_mapping_from, 263bdd1243dSDimitry Andric new_mapping_to, 264bdd1243dSDimitry Andric /*notify*/ true)) 265bdd1243dSDimitry Andric target.GetStatistics().IncreaseSourceMapDeduceCount(); 266bdd1243dSDimitry Andric } 267bdd1243dSDimitry Andric } 268bdd1243dSDimitry Andric } 269bdd1243dSDimitry Andric 2709dba64beSDimitry Andric Searcher::CallbackReturn BreakpointResolverFileLine::SearchCallback( 2719dba64beSDimitry Andric SearchFilter &filter, SymbolContext &context, Address *addr) { 2720b57cec5SDimitry Andric SymbolContextList sc_list; 2730b57cec5SDimitry Andric 2740b57cec5SDimitry Andric // There is a tricky bit here. You can have two compilation units that 2750b57cec5SDimitry Andric // #include the same file, and in one of them the function at m_line_number 2760b57cec5SDimitry Andric // is used (and so code and a line entry for it is generated) but in the 2770b57cec5SDimitry Andric // other it isn't. If we considered the CU's independently, then in the 2780b57cec5SDimitry Andric // second inclusion, we'd move the breakpoint to the next function that 2790b57cec5SDimitry Andric // actually generated code in the header file. That would end up being 2800b57cec5SDimitry Andric // confusing. So instead, we do the CU iterations by hand here, then scan 2810b57cec5SDimitry Andric // through the complete list of matches, and figure out the closest line 2820b57cec5SDimitry Andric // number match, and only set breakpoints on that match. 2830b57cec5SDimitry Andric 2840b57cec5SDimitry Andric // Note also that if file_spec only had a file name and not a directory, 2850b57cec5SDimitry Andric // there may be many different file spec's in the resultant list. The 2860b57cec5SDimitry Andric // closest line match for one will not be right for some totally different 2870b57cec5SDimitry Andric // file. So we go through the match list and pull out the sets that have the 2880b57cec5SDimitry Andric // same file spec in their line_entry and treat each set separately. 2890b57cec5SDimitry Andric 29081ad6265SDimitry Andric const uint32_t line = m_location_spec.GetLine().value_or(0); 291bdd1243dSDimitry Andric const std::optional<uint16_t> column = m_location_spec.GetColumn(); 2920b57cec5SDimitry Andric 2930b57cec5SDimitry Andric const size_t num_comp_units = context.module_sp->GetNumCompileUnits(); 2940b57cec5SDimitry Andric for (size_t i = 0; i < num_comp_units; i++) { 2950b57cec5SDimitry Andric CompUnitSP cu_sp(context.module_sp->GetCompileUnitAtIndex(i)); 2960b57cec5SDimitry Andric if (cu_sp) { 2970b57cec5SDimitry Andric if (filter.CompUnitPasses(*cu_sp)) 298bdd1243dSDimitry Andric cu_sp->ResolveSymbolContext(m_location_spec, eSymbolContextEverything, 299bdd1243dSDimitry Andric sc_list); 3000b57cec5SDimitry Andric } 3010b57cec5SDimitry Andric } 3020b57cec5SDimitry Andric 303bdd1243dSDimitry Andric FilterContexts(sc_list); 304bdd1243dSDimitry Andric 305bdd1243dSDimitry Andric DeduceSourceMapping(sc_list); 3060b57cec5SDimitry Andric 3070b57cec5SDimitry Andric StreamString s; 308fe6060f1SDimitry Andric s.Printf("for %s:%d ", 309fe6060f1SDimitry Andric m_location_spec.GetFileSpec().GetFilename().AsCString("<Unknown>"), 310fe6060f1SDimitry Andric line); 3110b57cec5SDimitry Andric 312fe6060f1SDimitry Andric SetSCMatchesByLine(filter, sc_list, m_skip_prologue, s.GetString(), line, 313fe6060f1SDimitry Andric column); 3140b57cec5SDimitry Andric 3150b57cec5SDimitry Andric return Searcher::eCallbackReturnContinue; 3160b57cec5SDimitry Andric } 3170b57cec5SDimitry Andric 3180b57cec5SDimitry Andric lldb::SearchDepth BreakpointResolverFileLine::GetDepth() { 3190b57cec5SDimitry Andric return lldb::eSearchDepthModule; 3200b57cec5SDimitry Andric } 3210b57cec5SDimitry Andric 3220b57cec5SDimitry Andric void BreakpointResolverFileLine::GetDescription(Stream *s) { 323fe6060f1SDimitry Andric s->Printf("file = '%s', line = %u, ", 324fe6060f1SDimitry Andric m_location_spec.GetFileSpec().GetPath().c_str(), 32581ad6265SDimitry Andric m_location_spec.GetLine().value_or(0)); 326fe6060f1SDimitry Andric auto column = m_location_spec.GetColumn(); 327fe6060f1SDimitry Andric if (column) 328fe6060f1SDimitry Andric s->Printf("column = %u, ", *column); 329fe6060f1SDimitry Andric s->Printf("exact_match = %d", m_location_spec.GetExactMatch()); 3300b57cec5SDimitry Andric } 3310b57cec5SDimitry Andric 3320b57cec5SDimitry Andric void BreakpointResolverFileLine::Dump(Stream *s) const {} 3330b57cec5SDimitry Andric 3340b57cec5SDimitry Andric lldb::BreakpointResolverSP 3355ffd83dbSDimitry Andric BreakpointResolverFileLine::CopyForBreakpoint(BreakpointSP &breakpoint) { 3360b57cec5SDimitry Andric lldb::BreakpointResolverSP ret_sp(new BreakpointResolverFileLine( 337fe6060f1SDimitry Andric breakpoint, GetOffset(), m_skip_prologue, m_location_spec)); 3380b57cec5SDimitry Andric 3390b57cec5SDimitry Andric return ret_sp; 3400b57cec5SDimitry Andric } 341