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