| //===-- BreakpointResolver.cpp --------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Breakpoint/BreakpointResolver.h" | |
| #include "lldb/Breakpoint/Breakpoint.h" | |
| #include "lldb/Breakpoint/BreakpointLocation.h" | |
| // Have to include the other breakpoint resolver types here so the static | |
| // create from StructuredData can call them. | |
| #include "lldb/Breakpoint/BreakpointResolverAddress.h" | |
| #include "lldb/Breakpoint/BreakpointResolverFileLine.h" | |
| #include "lldb/Breakpoint/BreakpointResolverFileRegex.h" | |
| #include "lldb/Breakpoint/BreakpointResolverName.h" | |
| #include "lldb/Breakpoint/BreakpointResolverScripted.h" | |
| #include "lldb/Core/Address.h" | |
| #include "lldb/Core/ModuleList.h" | |
| #include "lldb/Core/SearchFilter.h" | |
| #include "lldb/Symbol/CompileUnit.h" | |
| #include "lldb/Symbol/Function.h" | |
| #include "lldb/Symbol/SymbolContext.h" | |
| #include "lldb/Target/Language.h" | |
| #include "lldb/Target/Target.h" | |
| #include "lldb/Utility/LLDBLog.h" | |
| #include "lldb/Utility/Log.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/Utility/StreamString.h" | |
| #include <optional> | |
| using namespace lldb_private; | |
| using namespace lldb; | |
| // BreakpointResolver: | |
| const char *BreakpointResolver::g_ty_to_name[] = {"FileAndLine", "Address", | |
| "SymbolName", "SourceRegex", | |
| "Python", "Exception", | |
| "Unknown"}; | |
| const char *BreakpointResolver::g_option_names[static_cast<uint32_t>( | |
| BreakpointResolver::OptionNames::LastOptionName)] = { | |
| "AddressOffset", "Exact", "FileName", "Inlines", "Language", | |
| "LineNumber", "Column", "ModuleName", "NameMask", "Offset", | |
| "PythonClass", "Regex", "ScriptArgs", "SectionName", "SearchDepth", | |
| "SkipPrologue", "SymbolNames"}; | |
| const char *BreakpointResolver::ResolverTyToName(enum ResolverTy type) { | |
| if (type > LastKnownResolverType) | |
| return g_ty_to_name[UnknownResolver]; | |
| return g_ty_to_name[type]; | |
| } | |
| BreakpointResolver::ResolverTy | |
| BreakpointResolver::NameToResolverTy(llvm::StringRef name) { | |
| for (size_t i = 0; i < LastKnownResolverType; i++) { | |
| if (name == g_ty_to_name[i]) | |
| return (ResolverTy)i; | |
| } | |
| return UnknownResolver; | |
| } | |
| bool BreakpointResolver::ResolverTyInMask(uint64_t mask) { | |
| if (mask == eResolverUnknown) | |
| return false; | |
| return (mask & MaskForResolverTy()) != 0; | |
| } | |
| uint64_t BreakpointResolver::MaskForResolverTy() { | |
| ResolverTy thisID = GetResolverTy(); | |
| switch (thisID) { | |
| case FileLineResolver: | |
| return eResolverFileAndLine; | |
| case AddressResolver: | |
| return eResolverAddress; | |
| case NameResolver: | |
| return eResolverName; | |
| case FileRegexResolver: | |
| return eResolverFileRegex; | |
| case PythonResolver: | |
| return eResolverPython; | |
| case ExceptionResolver: | |
| return eResolverException; | |
| default: | |
| return eResolverUnknown; | |
| } | |
| } | |
| std::string BreakpointResolver::DescribeMask(uint64_t mask) { | |
| std::string result; | |
| if (mask & eResolverFileAndLine) | |
| result.push_back('F'); | |
| if (mask & eResolverAddress) | |
| result.push_back('A'); | |
| if (mask & eResolverName) | |
| result.push_back('N'); | |
| if (mask & eResolverFileRegex) | |
| result.push_back('S'); | |
| if (mask & eResolverPython) | |
| result.push_back('P'); | |
| if (mask & eResolverException) | |
| result.push_back('E'); | |
| return result; | |
| } | |
| bool BreakpointResolver::TypeMaskIsValid(uint64_t mask) { | |
| if (mask == 0) | |
| return false; | |
| return (mask & BreakpointResolverAllResolversMask) != 0; | |
| } | |
| BreakpointResolver::BreakpointResolver(const BreakpointSP &bkpt, | |
| const unsigned char resolverTy, | |
| lldb::addr_t offset, | |
| bool offset_is_insn_count) | |
| : m_breakpoint(bkpt), m_offset(offset), | |
| m_offset_is_insn_count(offset_is_insn_count), SubclassID(resolverTy) {} | |
| BreakpointResolver::~BreakpointResolver() = default; | |
| BreakpointResolverSP BreakpointResolver::CreateFromStructuredData( | |
| const StructuredData::Dictionary &resolver_dict, Status &error) { | |
| BreakpointResolverSP result_sp; | |
| if (!resolver_dict.IsValid()) { | |
| error = Status::FromErrorString( | |
| "Can't deserialize from an invalid data object."); | |
| return result_sp; | |
| } | |
| llvm::StringRef subclass_name; | |
| bool success = resolver_dict.GetValueForKeyAsString( | |
| GetSerializationSubclassKey(), subclass_name); | |
| if (!success) { | |
| error = | |
| Status::FromErrorString("Resolver data missing subclass resolver key"); | |
| return result_sp; | |
| } | |
| ResolverTy resolver_type = NameToResolverTy(subclass_name); | |
| if (resolver_type == UnknownResolver) { | |
| error = Status::FromErrorStringWithFormatv("Unknown resolver type: {0}.", | |
| subclass_name); | |
| return result_sp; | |
| } | |
| StructuredData::Dictionary *subclass_options = nullptr; | |
| success = resolver_dict.GetValueForKeyAsDictionary( | |
| GetSerializationSubclassOptionsKey(), subclass_options); | |
| if (!success || !subclass_options || !subclass_options->IsValid()) { | |
| error = | |
| Status::FromErrorString("Resolver data missing subclass options key."); | |
| return result_sp; | |
| } | |
| lldb::offset_t offset; | |
| success = subclass_options->GetValueForKeyAsInteger( | |
| GetKey(OptionNames::Offset), offset); | |
| if (!success) { | |
| error = | |
| Status::FromErrorString("Resolver data missing offset options key."); | |
| return result_sp; | |
| } | |
| switch (resolver_type) { | |
| case FileLineResolver: | |
| result_sp = BreakpointResolverFileLine::CreateFromStructuredData( | |
| *subclass_options, error); | |
| break; | |
| case AddressResolver: | |
| result_sp = BreakpointResolverAddress::CreateFromStructuredData( | |
| *subclass_options, error); | |
| break; | |
| case NameResolver: | |
| result_sp = BreakpointResolverName::CreateFromStructuredData( | |
| *subclass_options, error); | |
| break; | |
| case FileRegexResolver: | |
| result_sp = BreakpointResolverFileRegex::CreateFromStructuredData( | |
| *subclass_options, error); | |
| break; | |
| case PythonResolver: | |
| result_sp = BreakpointResolverScripted::CreateFromStructuredData( | |
| *subclass_options, error); | |
| break; | |
| case ExceptionResolver: | |
| error = Status::FromErrorString("Exception resolvers are hard."); | |
| break; | |
| default: | |
| llvm_unreachable("Should never get an unresolvable resolver type."); | |
| } | |
| if (error.Fail() || !result_sp) | |
| return {}; | |
| // Add on the global offset option: | |
| result_sp->SetOffset(offset); | |
| return result_sp; | |
| } | |
| StructuredData::DictionarySP BreakpointResolver::WrapOptionsDict( | |
| StructuredData::DictionarySP options_dict_sp) { | |
| if (!options_dict_sp || !options_dict_sp->IsValid()) | |
| return StructuredData::DictionarySP(); | |
| StructuredData::DictionarySP type_dict_sp(new StructuredData::Dictionary()); | |
| type_dict_sp->AddStringItem(GetSerializationSubclassKey(), GetResolverName()); | |
| type_dict_sp->AddItem(GetSerializationSubclassOptionsKey(), options_dict_sp); | |
| // Add the m_offset to the dictionary: | |
| options_dict_sp->AddIntegerItem(GetKey(OptionNames::Offset), m_offset); | |
| return type_dict_sp; | |
| } | |
| void BreakpointResolver::SetBreakpoint(const BreakpointSP &bkpt) { | |
| assert(bkpt); | |
| m_breakpoint = bkpt; | |
| NotifyBreakpointSet(); | |
| } | |
| void BreakpointResolver::ResolveBreakpointInModules(SearchFilter &filter, | |
| ModuleList &modules) { | |
| filter.SearchInModuleList(*this, modules); | |
| } | |
| void BreakpointResolver::ResolveBreakpoint(SearchFilter &filter) { | |
| filter.Search(*this); | |
| } | |
| namespace { | |
| struct SourceLoc { | |
| uint32_t line = UINT32_MAX; | |
| uint16_t column; | |
| SourceLoc(uint32_t l, std::optional<uint16_t> c) | |
| : line(l), column(c ? *c : LLDB_INVALID_COLUMN_NUMBER) {} | |
| SourceLoc(const SymbolContext &sc) | |
| : line(sc.line_entry.line), | |
| column(sc.line_entry.column ? sc.line_entry.column | |
| : LLDB_INVALID_COLUMN_NUMBER) {} | |
| }; | |
| bool operator<(const SourceLoc lhs, const SourceLoc rhs) { | |
| if (lhs.line < rhs.line) | |
| return true; | |
| if (lhs.line > rhs.line) | |
| return false; | |
| // uint32_t a_col = lhs.column ? lhs.column : LLDB_INVALID_COLUMN_NUMBER; | |
| // uint32_t b_col = rhs.column ? rhs.column : LLDB_INVALID_COLUMN_NUMBER; | |
| return lhs.column < rhs.column; | |
| } | |
| } // namespace | |
| void BreakpointResolver::SetSCMatchesByLine( | |
| SearchFilter &filter, SymbolContextList &sc_list, bool skip_prologue, | |
| llvm::StringRef log_ident, uint32_t line, std::optional<uint16_t> column) { | |
| llvm::SmallVector<SymbolContext, 16> all_scs(sc_list.begin(), sc_list.end()); | |
| // Let the language plugin filter `sc_list`. Because all symbol contexts in | |
| // sc_list are assumed to belong to the same File, Line and CU, the code below | |
| // assumes they have the same language. | |
| if (!sc_list.IsEmpty() && Language::GetGlobalLanguageProperties() | |
| .GetEnableFilterForLineBreakpoints()) | |
| if (Language *lang = Language::FindPlugin(sc_list[0].GetLanguage())) | |
| lang->FilterForLineBreakpoints(all_scs); | |
| while (all_scs.size()) { | |
| uint32_t closest_line = UINT32_MAX; | |
| // Move all the elements with a matching file spec to the end. | |
| auto &match = all_scs[0]; | |
| auto worklist_begin = std::partition( | |
| all_scs.begin(), all_scs.end(), [&](const SymbolContext &sc) { | |
| if (sc.line_entry.GetFile() == match.line_entry.GetFile() || | |
| sc.line_entry.original_file_sp->Equal( | |
| *match.line_entry.original_file_sp, | |
| SupportFile::eEqualFileSpecAndChecksumIfSet)) { | |
| // When a match is found, keep track of the smallest line number. | |
| closest_line = std::min(closest_line, sc.line_entry.line); | |
| return false; | |
| } | |
| return true; | |
| }); | |
| // (worklist_begin, worklist_end) now contains all entries for one filespec. | |
| auto worklist_end = all_scs.end(); | |
| if (column) { | |
| // If a column was requested, do a more precise match and only | |
| // return the first location that comes before or at the | |
| // requested location. | |
| SourceLoc requested(line, *column); | |
| // First, filter out all entries left of the requested column. | |
| worklist_end = std::remove_if( | |
| worklist_begin, worklist_end, | |
| [&](const SymbolContext &sc) { return requested < SourceLoc(sc); }); | |
| // Sort the remaining entries by (line, column). | |
| llvm::sort(worklist_begin, worklist_end, | |
| [](const SymbolContext &a, const SymbolContext &b) { | |
| return SourceLoc(a) < SourceLoc(b); | |
| }); | |
| // Filter out all locations with a source location after the closest match. | |
| if (worklist_begin != worklist_end) | |
| worklist_end = std::remove_if( | |
| worklist_begin, worklist_end, [&](const SymbolContext &sc) { | |
| return SourceLoc(*worklist_begin) < SourceLoc(sc); | |
| }); | |
| } else { | |
| // Remove all entries with a larger line number. | |
| // ResolveSymbolContext will always return a number that is >= | |
| // the line number you pass in. So the smaller line number is | |
| // always better. | |
| worklist_end = std::remove_if(worklist_begin, worklist_end, | |
| [&](const SymbolContext &sc) { | |
| return closest_line != sc.line_entry.line; | |
| }); | |
| } | |
| // Sort by file address. | |
| llvm::sort(worklist_begin, worklist_end, | |
| [](const SymbolContext &a, const SymbolContext &b) { | |
| return a.line_entry.range.GetBaseAddress().GetFileAddress() < | |
| b.line_entry.range.GetBaseAddress().GetFileAddress(); | |
| }); | |
| // Go through and see if there are line table entries that are | |
| // contiguous, and if so keep only the first of the contiguous range. | |
| // We do this by picking the first location in each lexical block. | |
| llvm::SmallDenseSet<Block *, 8> blocks_with_breakpoints; | |
| for (auto first = worklist_begin; first != worklist_end; ++first) { | |
| assert(!blocks_with_breakpoints.count(first->block)); | |
| blocks_with_breakpoints.insert(first->block); | |
| worklist_end = | |
| std::remove_if(std::next(first), worklist_end, | |
| [&](const SymbolContext &sc) { | |
| return blocks_with_breakpoints.count(sc.block); | |
| }); | |
| } | |
| // Make breakpoints out of the closest line number match. | |
| for (auto &sc : llvm::make_range(worklist_begin, worklist_end)) | |
| AddLocation(filter, sc, skip_prologue, log_ident); | |
| // Remove all contexts processed by this iteration. | |
| all_scs.erase(worklist_begin, all_scs.end()); | |
| } | |
| } | |
| void BreakpointResolver::AddLocation(SearchFilter &filter, | |
| const SymbolContext &sc, | |
| bool skip_prologue, | |
| llvm::StringRef log_ident) { | |
| Log *log = GetLog(LLDBLog::Breakpoints); | |
| Address line_start = sc.line_entry.range.GetBaseAddress(); | |
| if (!line_start.IsValid()) { | |
| LLDB_LOGF(log, | |
| "error: Unable to set breakpoint %s at file address " | |
| "0x%" PRIx64 "\n", | |
| log_ident.str().c_str(), line_start.GetFileAddress()); | |
| return; | |
| } | |
| if (!filter.AddressPasses(line_start)) { | |
| LLDB_LOGF(log, | |
| "Breakpoint %s at file address 0x%" PRIx64 | |
| " didn't pass the filter.\n", | |
| log_ident.str().c_str(), line_start.GetFileAddress()); | |
| } | |
| // If the line number is before the prologue end, move it there... | |
| bool skipped_prologue = false; | |
| if (skip_prologue && sc.function) { | |
| Address prologue_addr = sc.function->GetAddress(); | |
| if (prologue_addr.IsValid() && (line_start == prologue_addr)) { | |
| const uint32_t prologue_byte_size = sc.function->GetPrologueByteSize(); | |
| if (prologue_byte_size) { | |
| prologue_addr.Slide(prologue_byte_size); | |
| if (filter.AddressPasses(prologue_addr)) { | |
| skipped_prologue = true; | |
| line_start = prologue_addr; | |
| } | |
| } | |
| } | |
| } | |
| BreakpointLocationSP bp_loc_sp(AddLocation(line_start)); | |
| // If the address that we resolved the location to returns a different | |
| // LineEntry from the one in the incoming SC, we're probably dealing with an | |
| // inlined call site, so set that as the preferred LineEntry: | |
| LineEntry resolved_entry; | |
| if (!skipped_prologue && bp_loc_sp && | |
| line_start.CalculateSymbolContextLineEntry(resolved_entry) && | |
| LineEntry::Compare(resolved_entry, sc.line_entry)) { | |
| // FIXME: The function name will also be wrong here. Do we need to record | |
| // that as well, or can we figure that out again when we report this | |
| // breakpoint location. | |
| if (!bp_loc_sp->SetPreferredLineEntry(sc.line_entry)) { | |
| LLDB_LOG(log, "Tried to add a preferred line entry that didn't have the " | |
| "same address as this location's address."); | |
| } | |
| } | |
| if (log && bp_loc_sp && !GetBreakpoint()->IsInternal()) { | |
| StreamString s; | |
| bp_loc_sp->GetDescription(&s, lldb::eDescriptionLevelVerbose); | |
| LLDB_LOGF(log, "Added location (skipped prologue: %s): %s \n", | |
| skipped_prologue ? "yes" : "no", s.GetData()); | |
| } | |
| } | |
| BreakpointLocationSP BreakpointResolver::AddLocation(Address loc_addr, | |
| bool *new_location) { | |
| if (m_offset_is_insn_count) { | |
| Target &target = GetBreakpoint()->GetTarget(); | |
| llvm::Expected<DisassemblerSP> expected_instructions = | |
| target.ReadInstructions(loc_addr, m_offset); | |
| if (!expected_instructions) { | |
| LLDB_LOG_ERROR(GetLog(LLDBLog::Breakpoints), | |
| expected_instructions.takeError(), | |
| "error: Unable to read instructions at address {1:x}: {0}", | |
| loc_addr.GetLoadAddress(&target)); | |
| return BreakpointLocationSP(); | |
| } | |
| const DisassemblerSP instructions = *expected_instructions; | |
| if (!instructions || | |
| instructions->GetInstructionList().GetSize() != m_offset) { | |
| LLDB_LOG(GetLog(LLDBLog::Breakpoints), | |
| "error: Unable to read {0} instructions at address {1:x}", | |
| m_offset, loc_addr.GetLoadAddress(&target)); | |
| return BreakpointLocationSP(); | |
| } | |
| loc_addr.Slide(instructions->GetInstructionList().GetTotalByteSize()); | |
| } else { | |
| loc_addr.Slide(m_offset); | |
| } | |
| return GetBreakpoint()->AddLocation(loc_addr, new_location); | |
| } | |
| void BreakpointResolver::SetOffset(lldb::addr_t offset) { | |
| // There may already be an offset, so we are actually adjusting location | |
| // addresses by the difference. | |
| // lldb::addr_t slide = offset - m_offset; | |
| // FIXME: We should go fix up all the already set locations for the new | |
| // slide. | |
| m_offset = offset; | |
| } |