| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "WasmModule.h" |
| #include "Expressions.h" |
| #include "WasmVendorPlugins.h" |
| |
| #include "Plugins/SymbolFile/DWARF/DWARFCompileUnit.h" |
| #include "Plugins/SymbolFile/DWARF/LogChannelDWARF.h" |
| #include "lldb/Core/Address.h" |
| #include "lldb/Core/AddressRange.h" |
| #include "lldb/Core/Debugger.h" |
| #include "lldb/Core/FileSpecList.h" |
| #include "lldb/Core/Module.h" |
| #include "lldb/Core/Section.h" |
| #include "lldb/Symbol/Block.h" |
| #include "lldb/Symbol/CompileUnit.h" |
| #include "lldb/Symbol/CompilerType.h" |
| #include "lldb/Symbol/Function.h" |
| #include "lldb/Symbol/LineEntry.h" |
| #include "lldb/Symbol/LineTable.h" |
| #include "lldb/Symbol/ObjectFile.h" |
| #include "lldb/Symbol/SymbolContext.h" |
| #include "lldb/Symbol/SymbolContextScope.h" |
| #include "lldb/Symbol/Type.h" |
| #include "lldb/Symbol/TypeList.h" |
| #include "lldb/Symbol/TypeSystem.h" |
| #include "lldb/Symbol/Variable.h" |
| #include "lldb/Symbol/VariableList.h" |
| #include "lldb/Utility/ArchSpec.h" |
| #include "lldb/Utility/ConstString.h" |
| #include "lldb/Utility/FileSpec.h" |
| #include "lldb/Utility/Log.h" |
| #include "lldb/lldb-enumerations.h" |
| #include "lldb/lldb-forward.h" |
| #include "llvm/ADT/Optional.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/FileSystem.h" |
| #include "llvm/Support/Path.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cstddef> |
| #include <cstdint> |
| #include <functional> |
| #include <memory> |
| #include <tuple> |
| |
| #define DEBUG_TYPE "symbols-backend" |
| |
| namespace std { |
| template <> |
| struct less<symbols_backend::SourceLocation> { |
| bool operator()(const symbols_backend::SourceLocation& a, |
| const symbols_backend::SourceLocation& b) const { |
| return std::make_tuple(a.file, a.line, a.column) < |
| std::make_tuple(b.file, b.line, b.column); |
| } |
| }; |
| |
| template <> |
| struct less<symbols_backend::Variable> { |
| bool operator()(const symbols_backend::Variable& a, |
| const symbols_backend::Variable& b) const { |
| return a.name < b.name; |
| } |
| }; |
| } // namespace std |
| |
| namespace { |
| static llvm::StringRef GetDWOName(DWARFCompileUnit& dwarf_cu) { |
| return dwarf_cu.GetUnitDIEOnly().GetDIE()->GetAttributeValueAsString( |
| &dwarf_cu, lldb_private::dwarf::DW_AT_dwo_name, nullptr); |
| } |
| } // namespace |
| |
| namespace symbols_backend { |
| bool operator==(const SourceLocation& a, const SourceLocation& b) { |
| return std::make_tuple(a.file, a.line, a.column) == |
| std::make_tuple(b.file, b.line, b.column); |
| } |
| |
| bool operator==(const symbols_backend::Variable& a, |
| const symbols_backend::Variable& b) { |
| return std::make_tuple(a.name, a.type, a.scope) == |
| std::make_tuple(b.name, b.type, b.scope); |
| } |
| |
| llvm::Expected<std::pair<lldb::DebuggerSP, lldb::TargetSP>> GetTarget() { |
| static std::pair<lldb::DebuggerSP, lldb::TargetSP> instance = {}; |
| if (!instance.first) { |
| lldb::DebuggerSP dbg = lldb_private::Debugger::CreateInstance(); |
| lldb_private::ArchSpec arch("wasm32-unknown-unknown"); |
| lldb::TargetSP target; |
| lldb::PlatformSP platform = lldb_private::Platform::GetHostPlatform(); |
| dbg->GetPlatformList().SetSelectedPlatform(platform); |
| auto stat = dbg->GetTargetList().CreateTarget( |
| *dbg, {}, arch, lldb_private::eLoadDependentsNo, platform, target); |
| if (stat.Fail()) { |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), |
| stat.AsCString()); |
| } |
| target->SetPreloadSymbols(false); |
| instance = {dbg, target}; |
| } |
| return instance; |
| } |
| |
| llvm::Expected<std::unique_ptr<WasmModule>> WasmModule::CreateFromFile( |
| llvm::StringRef path) { |
| if (!llvm::sys::fs::exists(path)) { |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), |
| "Module file '%s' not found\n.", |
| path.str().c_str()); |
| } |
| LLVM_DEBUG(llvm::dbgs() << "Loading module from '" << path << "'\n"); |
| |
| auto instance = GetTarget(); |
| if (!instance) { |
| return instance.takeError(); |
| } |
| auto [debugger, target] = *instance; |
| auto module = target->GetOrCreateModule( |
| {lldb_private::FileSpec(path), |
| lldb_private::ArchSpec("wasm32-unknown-unknown-wasm")}, |
| false); |
| |
| return std::unique_ptr<WasmModule>(new WasmModule(target, module)); |
| } |
| |
| bool WasmModule::Valid() const { |
| return module_ && module_->GetNumCompileUnits() > 0; |
| } |
| |
| SourceInfo WasmModule::GetSourceScripts() const { |
| llvm::SmallSet<std::string, 1> compile_units; |
| llvm::SmallSet<std::string, 1> dwos; |
| llvm::SmallSet<std::pair<llvm::StringRef, llvm::StringRef>, 1> all_files; |
| for (size_t idx = 0; idx < module_->GetNumCompileUnits(); idx++) { |
| auto compile_unit = module_->GetCompileUnitAtIndex(idx); |
| for (auto f : compile_unit->GetSupportFiles()) { |
| auto dir = f.GetDirectory().GetStringRef(); |
| auto filename = f.GetFilename().GetStringRef(); |
| if (filename.empty()) { |
| continue; |
| } |
| if (!all_files.insert(std::make_pair(dir, filename)).second) { |
| continue; |
| } |
| compile_units.insert(f.GetPath()); |
| } |
| |
| // Cast user data to DwarfUnit |
| DWARFCompileUnit* dwarf_cu = |
| static_cast<DWARFCompileUnit*>(compile_unit->GetUserData()); |
| if (dwarf_cu && dwarf_cu->GetVersion() >= 5) { |
| // Might need to lazy load this .dwo (only works for DWARF5) |
| llvm::SmallVector<std::string, 2> missing_symbols; |
| auto dwo_name = GetDWOName(*dwarf_cu); |
| if (!dwo_name.empty()) { |
| dwos.insert(dwo_name.str()); |
| } |
| } |
| } |
| return {compile_units, dwos}; |
| } |
| |
| namespace { |
| uint32_t GetSymbolContextFromOffset(lldb_private::Module* module, |
| lldb::addr_t offset, |
| int inline_frame_index, |
| lldb::SymbolContextItem resolve_scope, |
| lldb_private::SymbolContext& out_sc, |
| lldb_private::Address& out_addr) { |
| if (!module->GetObjectFile() || !module->GetObjectFile()->GetSectionList()) { |
| return 0; |
| } |
| lldb_private::SymbolContext sc; |
| sc.module_sp = module->shared_from_this(); |
| lldb::SectionSP section = |
| module->GetObjectFile()->GetSectionList()->FindSectionByType( |
| lldb::eSectionTypeCode, false); |
| lldb_private::Address addr(section, offset); |
| |
| auto resolved = |
| module->ResolveSymbolContextForAddress(addr, resolve_scope, sc); |
| if (inline_frame_index == 0 && |
| (resolved & lldb::eSymbolContextBlock) != lldb::eSymbolContextBlock) { |
| out_sc = std::move(sc); |
| out_addr = std::move(addr); |
| return out_sc.GetResolvedMask(); |
| } |
| |
| for (int i = 0; i < inline_frame_index; i++) { |
| lldb_private::SymbolContext next_sc; |
| lldb_private::Address next_addr; |
| if (!sc.GetParentOfInlinedScope(addr, next_sc, next_addr)) { |
| return 0; |
| } |
| addr = std::move(next_addr); |
| sc = std::move(next_sc); |
| // Sometimes the inline block address range isn't properly clipped |
| // to the parent range; fix this. |
| if (addr.GetOffset() == 0 && sc.function) { |
| addr = sc.function->GetAddressRange().GetBaseAddress(); |
| } |
| } |
| out_sc = std::move(sc); |
| out_addr = std::move(addr); |
| return out_sc.GetResolvedMask(); |
| } |
| |
| void GetVariablesFromOffset(lldb_private::Module* module, |
| lldb::addr_t offset, |
| int inline_frame_index, |
| lldb_private::VariableList* var_list) { |
| lldb_private::SymbolContext sc; |
| lldb_private::Address addr; |
| auto resolved = GetSymbolContextFromOffset( |
| module, offset, inline_frame_index, lldb::eSymbolContextBlock, sc, addr); |
| if ((resolved & lldb::eSymbolContextBlock) == lldb::eSymbolContextBlock) { |
| sc.block->AppendVariables( |
| true, true, true, |
| [var_list](lldb_private::Variable* var) { |
| return (var_list->FindVariableIndex(lldb::VariableSP{ |
| var, [](lldb_private::Variable*) {}}) == UINT32_MAX); |
| }, |
| var_list); |
| } |
| resolved = GetSymbolContextFromOffset(module, offset, inline_frame_index, |
| lldb::eSymbolContextCompUnit, sc, addr); |
| if ((resolved & lldb::eSymbolContextCompUnit) == |
| lldb::eSymbolContextCompUnit) { |
| sc.comp_unit->GetVariableList(true)->AppendVariablesIfUnique(*var_list); |
| } |
| } |
| } // namespace |
| |
| lldb::VariableSP WasmModule::FindVariableAtOffset(lldb::addr_t offset, |
| int inline_frame_index, |
| llvm::StringRef name) const { |
| lldb_private::VariableList var_list; |
| GetVariablesFromOffset(&*module_, offset, inline_frame_index, &var_list); |
| // GetVariablesFromOffset fills the list with variables sorted from innermost |
| // scope to outermost scope, so the first hit in the list is the correct one. |
| for (auto var : var_list) { |
| // llvm::errs() << "var: " << var.get() << "\n"; |
| if (var->GetName().GetStringRef() == name) { |
| return var; |
| } |
| } |
| return {}; |
| } |
| |
| llvm::Optional<lldb_private::CompilerType> WasmModule::FindType( |
| llvm::StringRef name) const { |
| lldb_private::TypeList type_list; |
| llvm::DenseSet<lldb_private::SymbolFile*> searched_symbol_files; |
| module_->FindTypes(lldb_private::ConstString(name), true, 1, |
| searched_symbol_files, type_list); |
| if (!type_list.Empty()) { |
| return type_list.GetTypeAtIndex(0)->GetFullCompilerType(); |
| } |
| return llvm::None; |
| } |
| |
| llvm::SmallSet<SourceLocation, 1> WasmModule::GetSourceLocationFromOffset( |
| lldb::addr_t offset, |
| int inline_frame_index) const { |
| llvm::SmallSet<SourceLocation, 1> lines; |
| |
| lldb_private::Address addr; |
| lldb_private::SymbolContext sc; |
| auto resolved = GetSymbolContextFromOffset( |
| &*module_, offset, inline_frame_index, |
| lldb::eSymbolContextBlock | lldb::eSymbolContextLineEntry, sc, addr); |
| if ((resolved & lldb::eSymbolContextLineEntry) && sc.line_entry.IsValid() && |
| sc.line_entry.line > 0) { |
| lines.insert({sc.line_entry.file.GetPath(), sc.line_entry.line, |
| sc.line_entry.column}); |
| } |
| return lines; |
| } |
| |
| std::vector<int32_t> WasmModule::GetMappedLines( |
| llvm::StringRef file_path) const { |
| lldb_private::FileSpec::Style file_path_style = |
| lldb_private::FileSpec::GuessPathStyle(file_path).value_or( |
| lldb_private::FileSpec::Style::native); |
| lldb_private::FileSpec file_spec(file_path, file_path_style); |
| lldb_private::SymbolContextList line_entry_scs; |
| |
| // Get the comp unit symbol contexts for the file. |
| for (size_t idx = 0; idx < module_->GetNumCompileUnits(); idx++) { |
| auto compile_unit = module_->GetCompileUnitAtIndex(idx); |
| uint32_t file_idx = |
| compile_unit->GetSupportFiles().FindFileIndex(0, file_spec, true); |
| |
| // Gather all line entries for the compile_unit |
| lldb_private::LineTable* table = compile_unit->GetLineTable(); |
| while (file_idx != UINT32_MAX) { |
| table->FineLineEntriesForFileIndex(file_idx, true, line_entry_scs); |
| file_idx = compile_unit->GetSupportFiles().FindFileIndex(file_idx + 1, |
| file_spec, true); |
| } |
| } |
| |
| std::vector<int32_t> line_numbers; |
| for (const lldb_private::SymbolContext& line_sc : |
| line_entry_scs.SymbolContexts()) { |
| assert(line_sc.line_entry.IsValid()); |
| line_numbers.push_back(line_sc.line_entry.line); |
| } |
| |
| std::sort(line_numbers.begin(), line_numbers.end()); |
| auto end = std::unique(line_numbers.begin(), line_numbers.end()); |
| line_numbers.resize(end - line_numbers.begin()); |
| |
| return line_numbers; |
| } |
| |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> |
| WasmModule::GetOffsetFromSourceLocation( |
| const SourceLocation& source_loc) const { |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> locations; |
| std::vector<lldb_private::Address> output_local, output_extern; |
| |
| llvm::StringRef file_path(source_loc.file); |
| lldb_private::FileSpec::Style file_path_style = |
| lldb_private::FileSpec::GuessPathStyle(file_path).value_or( |
| lldb_private::FileSpec::Style::native); |
| lldb_private::FileSpec file_spec(file_path, file_path_style); |
| lldb_private::SymbolContextList list; |
| module_->ResolveSymbolContextsForFileSpec( |
| file_spec, source_loc.line, true, |
| lldb::eSymbolContextLineEntry | lldb::eSymbolContextCompUnit, list); |
| std::vector<lldb_private::AddressRange> ranges; |
| for (lldb_private::SymbolContext sc : list.SymbolContexts()) { |
| if (!sc.line_entry.IsValid()) { |
| continue; |
| } |
| |
| // Only return positions in the same line. |
| if (sc.line_entry.line != source_loc.line) { |
| continue; |
| } |
| |
| // Take into account the column number here to make in-line breakpoints |
| // work. |
| if (source_loc.column && sc.line_entry.column != source_loc.column) { |
| continue; |
| } |
| |
| // Check if the line entry range is already covered. |
| lldb_private::AddressRange range = sc.line_entry.range; |
| if (std::find_if(ranges.begin(), ranges.end(), |
| [&](const lldb_private::AddressRange& r) { |
| auto address = range.GetBaseAddress(); |
| return r.ContainsFileAddress(address); |
| }) != ranges.end()) { |
| continue; |
| } |
| |
| while (true) { |
| lldb_private::SymbolContext next_sc; |
| lldb_private::Address range_end(range.GetBaseAddress()); |
| range_end.Slide(range.GetByteSize()); |
| range_end.CalculateSymbolContext(&next_sc, lldb::eSymbolContextLineEntry); |
| |
| // Don't combine ranges across "start of statement" markers inserted |
| // by the compiler. |
| if (!next_sc.line_entry.IsValid() || |
| next_sc.line_entry.is_start_of_statement || |
| next_sc.line_entry.range.GetByteSize() == 0) { |
| break; |
| } |
| |
| // Include any line 0 entries, they indicate that this is compiler- |
| // generated code that does not correspond to user source code. |
| if (next_sc.line_entry.original_file != sc.line_entry.original_file || |
| (next_sc.line_entry.line != sc.line_entry.line && |
| next_sc.line_entry.line != 0)) { |
| break; |
| } |
| |
| // Try to extend our address range to cover this line entry. |
| if (!range.Extend(next_sc.line_entry.range)) { |
| break; |
| } |
| } |
| |
| ranges.push_back(range); |
| } |
| |
| for (auto const& range : ranges) { |
| locations.insert({range.GetBaseAddress().GetOffset(), range.GetByteSize()}); |
| } |
| return locations; |
| } |
| |
| std::set<Variable> WasmModule::GetVariablesInScope( |
| lldb::addr_t offset, |
| int inline_frame_index) const { |
| std::set<Variable> variables; |
| lldb_private::VariableList var_list; |
| GetVariablesFromOffset(&*module_, offset, inline_frame_index, &var_list); |
| LLVM_DEBUG(llvm::dbgs() << "Found " << var_list.GetSize() << " variables\n"); |
| for (auto var : var_list) { |
| var->GetSymbolContextScope()->CalculateSymbolContextCompileUnit(); |
| // var_list contains variables sorted from innermost scope to outermost |
| // scope. The set compares variables by name to preserve shadowing order. |
| auto type = var->GetType(); |
| variables.insert( |
| {var->GetName().GetStringRef(), var->GetScope(), |
| type ? type->GetQualifiedName().GetStringRef() : llvm::StringRef()}); |
| } |
| return variables; |
| } |
| |
| FunctionInfo WasmModule::GetFunctionInfo(lldb::addr_t offset) const { |
| llvm::SmallVector<std::string, 1> function_names; |
| |
| lldb_private::SymbolContext sc, old_sc; |
| lldb_private::Address addr, old_addr; |
| auto resolved = GetSymbolContextFromOffset( |
| &*module_, offset, 0, lldb::eSymbolContextBlock, sc, addr); |
| if ((resolved & lldb::eSymbolContextBlock) == lldb::eSymbolContextBlock) { |
| do { |
| auto name = sc.GetFunctionName(); |
| if (name.IsNull() || name.IsEmpty()) { |
| function_names.push_back(""); |
| } else { |
| function_names.push_back(name.GetCString()); |
| } |
| old_sc = std::move(sc); |
| old_addr = std::move(addr); |
| } while (old_sc.GetParentOfInlinedScope(old_addr, sc, addr)); |
| return {function_names, {}}; |
| } else if ((resolved & lldb::eSymbolContextCompUnit) == |
| lldb::eSymbolContextCompUnit) { |
| // Compile unit might be missing symbols? |
| |
| // Cast user data to DwarfUnit |
| DWARFCompileUnit* dwarf_cu = |
| static_cast<DWARFCompileUnit*>(sc.comp_unit->GetUserData()); |
| if (dwarf_cu && dwarf_cu == &dwarf_cu->GetNonSkeletonUnit()) { |
| // The skeleton unit is the only unit, but is there supposed to be a .dwo? |
| llvm::SmallVector<std::string, 2> missing_symbols; |
| auto dwo_name = GetDWOName(*dwarf_cu); |
| if (!dwo_name.empty()) { |
| missing_symbols.push_back(dwo_name.str()); |
| } |
| return {{}, missing_symbols}; |
| } |
| } |
| return {{}, {}}; |
| } |
| |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> |
| WasmModule::GetInlineFunctionAddressRanges(lldb::addr_t offset) const { |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> ranges; |
| lldb_private::SymbolContext sc; |
| lldb_private::Address addr; |
| auto resolved = GetSymbolContextFromOffset( |
| &*module_, offset, 0, lldb::eSymbolContextBlock, sc, addr); |
| if ((resolved & lldb::eSymbolContextBlock) == lldb::eSymbolContextBlock) { |
| lldb_private::Block* inline_block = sc.block->GetContainingInlinedBlock(); |
| if (inline_block) { |
| size_t count = inline_block->GetNumRanges(); |
| for (size_t i = 0; i < count; i++) { |
| lldb_private::AddressRange range; |
| if (inline_block->GetRangeAtIndex(i, range)) { |
| ranges.insert( |
| {range.GetBaseAddress().GetOffset(), range.GetByteSize()}); |
| } |
| } |
| } |
| } |
| return ranges; |
| } |
| |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> |
| WasmModule::GetChildInlineFunctionAddressRanges(lldb::addr_t offset) const { |
| llvm::SmallSet<std::pair<lldb::addr_t, lldb::addr_t>, 1> ranges; |
| lldb_private::SymbolContext sc; |
| lldb_private::Address addr; |
| auto resolved = GetSymbolContextFromOffset( |
| &*module_, offset, 0, lldb::eSymbolContextBlock, sc, addr); |
| if ((resolved & lldb::eSymbolContextBlock) == lldb::eSymbolContextBlock) { |
| // Find root block for function to search from |
| lldb_private::Block* root_block = sc.block; |
| while (!root_block->GetInlinedFunctionInfo()) { |
| lldb_private::Block* parent = root_block->GetParent(); |
| if (parent) { |
| root_block = parent; |
| } else { |
| break; |
| } |
| } |
| |
| // Traverse tree to find child inline blocks |
| lldb_private::Block* block = root_block->GetFirstChild(); |
| while (block) { |
| lldb_private::Block* next_block = nullptr; |
| if (block->GetInlinedFunctionInfo()) { |
| size_t count = block->GetNumRanges(); |
| for (size_t i = 0; i < count; i++) { |
| lldb_private::AddressRange range; |
| if (block->GetRangeAtIndex(i, range)) { |
| ranges.insert( |
| {range.GetBaseAddress().GetOffset(), range.GetByteSize()}); |
| } |
| } |
| } else { |
| // Only traverse children when not an inline block |
| next_block = block->GetFirstChild(); |
| } |
| // If we haven't found our next block, get a sibling |
| // or a parent's sibling |
| if (!next_block) { |
| while (true) { |
| next_block = block->GetSibling(); |
| if (next_block) { |
| break; |
| } |
| block = block->GetParent(); |
| if (!block || block == root_block) { |
| break; |
| } |
| } |
| } |
| block = next_block; |
| } |
| } |
| return ranges; |
| } |
| |
| llvm::Expected<ExpressionResult> WasmModule::InterpretExpression( |
| lldb::addr_t frame_offset, |
| uint32_t inline_frame_index, |
| llvm::StringRef expression, |
| const api::DebuggerProxy& proxy) const { |
| lldb_private::SymbolContext sc; |
| lldb_private::Address addr; |
| auto resolved = |
| GetSymbolContextFromOffset(&*module_, frame_offset, inline_frame_index, |
| lldb::eSymbolContextEverything, sc, addr); |
| |
| if ((resolved & |
| (lldb::eSymbolContextCompUnit | lldb::eSymbolContextFunction)) == 0) { |
| resolved = |
| GetSymbolContextFromOffset(&*module_, frame_offset, inline_frame_index, |
| lldb::eSymbolContextCompUnit, sc, addr); |
| if ((resolved & lldb::eSymbolContextCompUnit) == 0) { |
| return llvm::createStringError( |
| llvm::inconvertibleErrorCode(), |
| "Not in a valid symbol context at offset %zu", frame_offset); |
| } |
| } |
| auto type_system = |
| module_->GetTypeSystemForLanguage(sc.comp_unit->GetLanguage()); |
| if (!type_system) { |
| return type_system.takeError(); |
| } |
| return ::symbols_backend::InterpretExpression( |
| *this, **type_system, sc, frame_offset, inline_frame_index, addr, |
| expression, proxy); |
| } |
| } // namespace symbols_backend |