407 lines · cpp
1//===-- BreakpointResolver.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/BreakpointResolver.h"10 11#include "lldb/Breakpoint/Breakpoint.h"12#include "lldb/Breakpoint/BreakpointLocation.h"13// Have to include the other breakpoint resolver types here so the static14// create from StructuredData can call them.15#include "lldb/Breakpoint/BreakpointResolverAddress.h"16#include "lldb/Breakpoint/BreakpointResolverFileLine.h"17#include "lldb/Breakpoint/BreakpointResolverFileRegex.h"18#include "lldb/Breakpoint/BreakpointResolverName.h"19#include "lldb/Breakpoint/BreakpointResolverScripted.h"20#include "lldb/Core/Address.h"21#include "lldb/Core/ModuleList.h"22#include "lldb/Core/SearchFilter.h"23#include "lldb/Symbol/CompileUnit.h"24#include "lldb/Symbol/Function.h"25#include "lldb/Symbol/SymbolContext.h"26#include "lldb/Target/Language.h"27#include "lldb/Target/Target.h"28#include "lldb/Utility/LLDBLog.h"29#include "lldb/Utility/Log.h"30#include "lldb/Utility/Stream.h"31#include "lldb/Utility/StreamString.h"32#include <optional>33 34using namespace lldb_private;35using namespace lldb;36 37// BreakpointResolver:38const char *BreakpointResolver::g_ty_to_name[] = {"FileAndLine", "Address",39 "SymbolName", "SourceRegex",40 "Python", "Exception",41 "Unknown"};42 43const char *BreakpointResolver::g_option_names[static_cast<uint32_t>(44 BreakpointResolver::OptionNames::LastOptionName)] = {45 "AddressOffset", "Exact", "FileName", "Inlines", "Language",46 "LineNumber", "Column", "ModuleName", "NameMask", "Offset",47 "PythonClass", "Regex", "ScriptArgs", "SectionName", "SearchDepth",48 "SkipPrologue", "SymbolNames"};49 50const char *BreakpointResolver::ResolverTyToName(enum ResolverTy type) {51 if (type > LastKnownResolverType)52 return g_ty_to_name[UnknownResolver];53 54 return g_ty_to_name[type];55}56 57BreakpointResolver::ResolverTy58BreakpointResolver::NameToResolverTy(llvm::StringRef name) {59 for (size_t i = 0; i < LastKnownResolverType; i++) {60 if (name == g_ty_to_name[i])61 return (ResolverTy)i;62 }63 return UnknownResolver;64}65 66BreakpointResolver::BreakpointResolver(const BreakpointSP &bkpt,67 const unsigned char resolverTy,68 lldb::addr_t offset,69 bool offset_is_insn_count)70 : m_breakpoint(bkpt), m_offset(offset),71 m_offset_is_insn_count(offset_is_insn_count), SubclassID(resolverTy) {}72 73BreakpointResolver::~BreakpointResolver() = default;74 75BreakpointResolverSP BreakpointResolver::CreateFromStructuredData(76 const StructuredData::Dictionary &resolver_dict, Status &error) {77 BreakpointResolverSP result_sp;78 if (!resolver_dict.IsValid()) {79 error = Status::FromErrorString(80 "Can't deserialize from an invalid data object.");81 return result_sp;82 }83 84 llvm::StringRef subclass_name;85 86 bool success = resolver_dict.GetValueForKeyAsString(87 GetSerializationSubclassKey(), subclass_name);88 89 if (!success) {90 error =91 Status::FromErrorString("Resolver data missing subclass resolver key");92 return result_sp;93 }94 95 ResolverTy resolver_type = NameToResolverTy(subclass_name);96 if (resolver_type == UnknownResolver) {97 error = Status::FromErrorStringWithFormatv("Unknown resolver type: {0}.",98 subclass_name);99 return result_sp;100 }101 102 StructuredData::Dictionary *subclass_options = nullptr;103 success = resolver_dict.GetValueForKeyAsDictionary(104 GetSerializationSubclassOptionsKey(), subclass_options);105 if (!success || !subclass_options || !subclass_options->IsValid()) {106 error =107 Status::FromErrorString("Resolver data missing subclass options key.");108 return result_sp;109 }110 111 lldb::offset_t offset;112 success = subclass_options->GetValueForKeyAsInteger(113 GetKey(OptionNames::Offset), offset);114 if (!success) {115 error =116 Status::FromErrorString("Resolver data missing offset options key.");117 return result_sp;118 }119 120 switch (resolver_type) {121 case FileLineResolver:122 result_sp = BreakpointResolverFileLine::CreateFromStructuredData(123 *subclass_options, error);124 break;125 case AddressResolver:126 result_sp = BreakpointResolverAddress::CreateFromStructuredData(127 *subclass_options, error);128 break;129 case NameResolver:130 result_sp = BreakpointResolverName::CreateFromStructuredData(131 *subclass_options, error);132 break;133 case FileRegexResolver:134 result_sp = BreakpointResolverFileRegex::CreateFromStructuredData(135 *subclass_options, error);136 break;137 case PythonResolver:138 result_sp = BreakpointResolverScripted::CreateFromStructuredData(139 *subclass_options, error);140 break;141 case ExceptionResolver:142 error = Status::FromErrorString("Exception resolvers are hard.");143 break;144 default:145 llvm_unreachable("Should never get an unresolvable resolver type.");146 }147 148 if (error.Fail() || !result_sp)149 return {};150 151 // Add on the global offset option:152 result_sp->SetOffset(offset);153 return result_sp;154}155 156StructuredData::DictionarySP BreakpointResolver::WrapOptionsDict(157 StructuredData::DictionarySP options_dict_sp) {158 if (!options_dict_sp || !options_dict_sp->IsValid())159 return StructuredData::DictionarySP();160 161 StructuredData::DictionarySP type_dict_sp(new StructuredData::Dictionary());162 type_dict_sp->AddStringItem(GetSerializationSubclassKey(), GetResolverName());163 type_dict_sp->AddItem(GetSerializationSubclassOptionsKey(), options_dict_sp);164 165 // Add the m_offset to the dictionary:166 options_dict_sp->AddIntegerItem(GetKey(OptionNames::Offset), m_offset);167 168 return type_dict_sp;169}170 171void BreakpointResolver::SetBreakpoint(const BreakpointSP &bkpt) {172 assert(bkpt);173 m_breakpoint = bkpt;174 NotifyBreakpointSet();175}176 177void BreakpointResolver::ResolveBreakpointInModules(SearchFilter &filter,178 ModuleList &modules) {179 filter.SearchInModuleList(*this, modules);180}181 182void BreakpointResolver::ResolveBreakpoint(SearchFilter &filter) {183 filter.Search(*this);184}185 186namespace {187struct SourceLoc {188 uint32_t line = UINT32_MAX;189 uint16_t column;190 SourceLoc(uint32_t l, std::optional<uint16_t> c)191 : line(l), column(c ? *c : LLDB_INVALID_COLUMN_NUMBER) {}192 SourceLoc(const SymbolContext &sc)193 : line(sc.line_entry.line),194 column(sc.line_entry.column ? sc.line_entry.column195 : LLDB_INVALID_COLUMN_NUMBER) {}196};197 198bool operator<(const SourceLoc lhs, const SourceLoc rhs) {199 if (lhs.line < rhs.line)200 return true;201 if (lhs.line > rhs.line)202 return false;203 // uint32_t a_col = lhs.column ? lhs.column : LLDB_INVALID_COLUMN_NUMBER;204 // uint32_t b_col = rhs.column ? rhs.column : LLDB_INVALID_COLUMN_NUMBER;205 return lhs.column < rhs.column;206}207} // namespace208 209void BreakpointResolver::SetSCMatchesByLine(210 SearchFilter &filter, SymbolContextList &sc_list, bool skip_prologue,211 llvm::StringRef log_ident, uint32_t line, std::optional<uint16_t> column) {212 llvm::SmallVector<SymbolContext, 16> all_scs(sc_list.begin(), sc_list.end());213 214 // Let the language plugin filter `sc_list`. Because all symbol contexts in215 // sc_list are assumed to belong to the same File, Line and CU, the code below216 // assumes they have the same language.217 if (!sc_list.IsEmpty() && Language::GetGlobalLanguageProperties()218 .GetEnableFilterForLineBreakpoints())219 if (Language *lang = Language::FindPlugin(sc_list[0].GetLanguage()))220 lang->FilterForLineBreakpoints(all_scs);221 222 while (all_scs.size()) {223 uint32_t closest_line = UINT32_MAX;224 225 // Move all the elements with a matching file spec to the end.226 auto &match = all_scs[0];227 auto worklist_begin = std::partition(228 all_scs.begin(), all_scs.end(), [&](const SymbolContext &sc) {229 if (sc.line_entry.GetFile() == match.line_entry.GetFile() ||230 sc.line_entry.original_file_sp->Equal(231 *match.line_entry.original_file_sp,232 SupportFile::eEqualFileSpecAndChecksumIfSet)) {233 // When a match is found, keep track of the smallest line number.234 closest_line = std::min(closest_line, sc.line_entry.line);235 return false;236 }237 return true;238 });239 240 // (worklist_begin, worklist_end) now contains all entries for one filespec.241 auto worklist_end = all_scs.end();242 243 if (column) {244 // If a column was requested, do a more precise match and only245 // return the first location that comes before or at the246 // requested location.247 SourceLoc requested(line, *column);248 // First, filter out all entries left of the requested column.249 worklist_end = std::remove_if(250 worklist_begin, worklist_end,251 [&](const SymbolContext &sc) { return requested < SourceLoc(sc); });252 // Sort the remaining entries by (line, column).253 llvm::sort(worklist_begin, worklist_end,254 [](const SymbolContext &a, const SymbolContext &b) {255 return SourceLoc(a) < SourceLoc(b);256 });257 258 // Filter out all locations with a source location after the closest match.259 if (worklist_begin != worklist_end)260 worklist_end = std::remove_if(261 worklist_begin, worklist_end, [&](const SymbolContext &sc) {262 return SourceLoc(*worklist_begin) < SourceLoc(sc);263 });264 } else {265 // Remove all entries with a larger line number.266 // ResolveSymbolContext will always return a number that is >=267 // the line number you pass in. So the smaller line number is268 // always better.269 worklist_end = std::remove_if(worklist_begin, worklist_end,270 [&](const SymbolContext &sc) {271 return closest_line != sc.line_entry.line;272 });273 }274 275 // Sort by file address.276 llvm::sort(worklist_begin, worklist_end,277 [](const SymbolContext &a, const SymbolContext &b) {278 return a.line_entry.range.GetBaseAddress().GetFileAddress() <279 b.line_entry.range.GetBaseAddress().GetFileAddress();280 });281 282 // Go through and see if there are line table entries that are283 // contiguous, and if so keep only the first of the contiguous range.284 // We do this by picking the first location in each lexical block.285 llvm::SmallDenseSet<Block *, 8> blocks_with_breakpoints;286 for (auto first = worklist_begin; first != worklist_end; ++first) {287 assert(!blocks_with_breakpoints.count(first->block));288 blocks_with_breakpoints.insert(first->block);289 worklist_end =290 std::remove_if(std::next(first), worklist_end,291 [&](const SymbolContext &sc) {292 return blocks_with_breakpoints.count(sc.block);293 });294 }295 296 // Make breakpoints out of the closest line number match.297 for (auto &sc : llvm::make_range(worklist_begin, worklist_end))298 AddLocation(filter, sc, skip_prologue, log_ident);299 300 // Remove all contexts processed by this iteration.301 all_scs.erase(worklist_begin, all_scs.end());302 }303}304 305void BreakpointResolver::AddLocation(SearchFilter &filter,306 const SymbolContext &sc,307 bool skip_prologue,308 llvm::StringRef log_ident) {309 Log *log = GetLog(LLDBLog::Breakpoints);310 Address line_start = sc.line_entry.range.GetBaseAddress();311 if (!line_start.IsValid()) {312 LLDB_LOGF(log,313 "error: Unable to set breakpoint %s at file address "314 "0x%" PRIx64 "\n",315 log_ident.str().c_str(), line_start.GetFileAddress());316 return;317 }318 319 if (!filter.AddressPasses(line_start)) {320 LLDB_LOGF(log,321 "Breakpoint %s at file address 0x%" PRIx64322 " didn't pass the filter.\n",323 log_ident.str().c_str(), line_start.GetFileAddress());324 }325 326 // If the line number is before the prologue end, move it there...327 bool skipped_prologue = false;328 if (skip_prologue && sc.function) {329 Address prologue_addr = sc.function->GetAddress();330 if (prologue_addr.IsValid() && (line_start == prologue_addr)) {331 const uint32_t prologue_byte_size = sc.function->GetPrologueByteSize();332 if (prologue_byte_size) {333 prologue_addr.Slide(prologue_byte_size);334 335 if (filter.AddressPasses(prologue_addr)) {336 skipped_prologue = true;337 line_start = prologue_addr;338 }339 }340 }341 }342 343 BreakpointLocationSP bp_loc_sp(AddLocation(line_start));344 // If the address that we resolved the location to returns a different345 // LineEntry from the one in the incoming SC, we're probably dealing with an346 // inlined call site, so set that as the preferred LineEntry:347 LineEntry resolved_entry;348 if (!skipped_prologue && bp_loc_sp &&349 line_start.CalculateSymbolContextLineEntry(resolved_entry) &&350 LineEntry::Compare(resolved_entry, sc.line_entry)) {351 // FIXME: The function name will also be wrong here. Do we need to record352 // that as well, or can we figure that out again when we report this353 // breakpoint location.354 if (!bp_loc_sp->SetPreferredLineEntry(sc.line_entry)) {355 LLDB_LOG(log, "Tried to add a preferred line entry that didn't have the "356 "same address as this location's address.");357 }358 }359 if (log && bp_loc_sp && !GetBreakpoint()->IsInternal()) {360 StreamString s;361 bp_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose);362 LLDB_LOGF(log, "Added location (skipped prologue: %s): %s \n",363 skipped_prologue ? "yes" : "no", s.GetData());364 }365}366 367BreakpointLocationSP BreakpointResolver::AddLocation(Address loc_addr,368 bool *new_location) {369 if (m_offset_is_insn_count) {370 Target &target = GetBreakpoint()->GetTarget();371 llvm::Expected<DisassemblerSP> expected_instructions =372 target.ReadInstructions(loc_addr, m_offset);373 if (!expected_instructions) {374 LLDB_LOG_ERROR(GetLog(LLDBLog::Breakpoints),375 expected_instructions.takeError(),376 "error: Unable to read instructions at address 0x{0:x}",377 loc_addr.GetLoadAddress(&target));378 return BreakpointLocationSP();379 }380 381 const DisassemblerSP instructions = *expected_instructions;382 if (!instructions ||383 instructions->GetInstructionList().GetSize() != m_offset) {384 LLDB_LOG(GetLog(LLDBLog::Breakpoints),385 "error: Unable to read {0} instructions at address 0x{1:x}",386 m_offset, loc_addr.GetLoadAddress(&target));387 return BreakpointLocationSP();388 }389 390 loc_addr.Slide(instructions->GetInstructionList().GetTotalByteSize());391 } else {392 loc_addr.Slide(m_offset);393 }394 395 return GetBreakpoint()->AddLocation(loc_addr, new_location);396}397 398void BreakpointResolver::SetOffset(lldb::addr_t offset) {399 // There may already be an offset, so we are actually adjusting location400 // addresses by the difference.401 // lldb::addr_t slide = offset - m_offset;402 // FIXME: We should go fix up all the already set locations for the new403 // slide.404 405 m_offset = offset;406}407