359 lines · cpp
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-exception6//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 22using namespace lldb;23using namespace lldb_private;24 25// BreakpointResolverFileLine:26BreakpointResolverFileLine::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 34BreakpointResolverSP 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 102StructuredData::ObjectSP103BreakpointResolverFileLine::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 was125// moved into a new function. We do this conservatively, so if e.g. we cannot126// 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 is128// handling inlined functions -- in this case we need to make sure we look at129// the declaration line of the inlined function, NOT the function it was130// inlined into.131void 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 SupportFileNSP file_sp = std::make_shared<SupportFile>();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 else154 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 function165 // declaration. In case of a function declared as:166 //167 // int168 // foo()169 // {170 // ...171 //172 // the compiler will set the declaration line to the "foo" line, which is173 // the reason why we have -1 here. This can fail in case of two inline174 // 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 GetStartLineSourceInfo181 // is 0, then we can't do this calculation. That can happen if182 // GetStartLineSourceInfo gets an error, or if the first line number in183 // 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 SC186 // was different from the one requested in the source file. If we actually187 // 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 203void 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 path212 // 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 normalized218 // 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 has230 // 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 279Searcher::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 that284 // #include the same file, and in one of them the function at m_line_number285 // is used (and so code and a line entry for it is generated) but in the286 // other it isn't. If we considered the CU's independently, then in the287 // second inclusion, we'd move the breakpoint to the next function that288 // actually generated code in the header file. That would end up being289 // confusing. So instead, we do the CU iterations by hand here, then scan290 // through the complete list of matches, and figure out the closest line291 // 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. The295 // closest line match for one will not be right for some totally different296 // file. So we go through the match list and pull out the sets that have the297 // 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 Target316 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 336lldb::SearchDepth BreakpointResolverFileLine::GetDepth() {337 return lldb::eSearchDepthModule;338}339 340void 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 350void BreakpointResolverFileLine::Dump(Stream *s) const {}351 352lldb::BreakpointResolverSP353BreakpointResolverFileLine::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