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