| //===-- CommandObjectSource.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 "CommandObjectSource.h" | |
| #include "lldb/Core/Debugger.h" | |
| #include "lldb/Core/FileLineResolver.h" | |
| #include "lldb/Core/Module.h" | |
| #include "lldb/Core/ModuleSpec.h" | |
| #include "lldb/Core/SourceManager.h" | |
| #include "lldb/Host/OptionParser.h" | |
| #include "lldb/Interpreter/CommandOptionArgumentTable.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Interpreter/OptionArgParser.h" | |
| #include "lldb/Interpreter/OptionValueFileColonLine.h" | |
| #include "lldb/Interpreter/Options.h" | |
| #include "lldb/Symbol/CompileUnit.h" | |
| #include "lldb/Symbol/Function.h" | |
| #include "lldb/Symbol/Symbol.h" | |
| #include "lldb/Target/Process.h" | |
| #include "lldb/Target/SectionLoadList.h" | |
| #include "lldb/Target/StackFrame.h" | |
| #include "lldb/Utility/FileSpec.h" | |
| #include <optional> | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| #pragma mark CommandObjectSourceInfo | |
| // CommandObjectSourceInfo - debug line entries dumping command | |
| #define LLDB_OPTIONS_source_info | |
| #include "CommandOptions.inc" | |
| class CommandObjectSourceInfo : public CommandObjectParsed { | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = GetDefinitions()[option_idx].short_option; | |
| switch (short_option) { | |
| case 'l': | |
| if (option_arg.getAsInteger(0, start_line)) | |
| error = Status::FromErrorStringWithFormat("invalid line number: '%s'", | |
| option_arg.str().c_str()); | |
| break; | |
| case 'e': | |
| if (option_arg.getAsInteger(0, end_line)) | |
| error = Status::FromErrorStringWithFormat("invalid line number: '%s'", | |
| option_arg.str().c_str()); | |
| break; | |
| case 'c': | |
| if (option_arg.getAsInteger(0, num_lines)) | |
| error = Status::FromErrorStringWithFormat("invalid line count: '%s'", | |
| option_arg.str().c_str()); | |
| break; | |
| case 'f': | |
| file_name = std::string(option_arg); | |
| break; | |
| case 'n': | |
| symbol_name = std::string(option_arg); | |
| break; | |
| case 'a': { | |
| address = OptionArgParser::ToAddress(execution_context, option_arg, | |
| LLDB_INVALID_ADDRESS, &error); | |
| } break; | |
| case 's': | |
| modules.push_back(std::string(option_arg)); | |
| break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| file_spec.Clear(); | |
| file_name.clear(); | |
| symbol_name.clear(); | |
| address = LLDB_INVALID_ADDRESS; | |
| start_line = 0; | |
| end_line = 0; | |
| num_lines = 0; | |
| modules.clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_source_info_options); | |
| } | |
| // Instance variables to hold the values for command options. | |
| FileSpec file_spec; | |
| std::string file_name; | |
| std::string symbol_name; | |
| lldb::addr_t address; | |
| uint32_t start_line; | |
| uint32_t end_line; | |
| uint32_t num_lines; | |
| std::vector<std::string> modules; | |
| }; | |
| public: | |
| CommandObjectSourceInfo(CommandInterpreter &interpreter) | |
| : CommandObjectParsed( | |
| interpreter, "source info", | |
| "Display source line information for the current target " | |
| "process. Defaults to instruction pointer in current stack " | |
| "frame.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectSourceInfo() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| protected: | |
| // Dump the line entries in each symbol context. Return the number of entries | |
| // found. If module_list is set, only dump lines contained in one of the | |
| // modules. If file_spec is set, only dump lines in the file. If the | |
| // start_line option was specified, don't print lines less than start_line. | |
| // If the end_line option was specified, don't print lines greater than | |
| // end_line. If the num_lines option was specified, dont print more than | |
| // num_lines entries. | |
| uint32_t DumpLinesInSymbolContexts(Stream &strm, | |
| const SymbolContextList &sc_list, | |
| const ModuleList &module_list, | |
| const FileSpec &file_spec) { | |
| uint32_t start_line = m_options.start_line; | |
| uint32_t end_line = m_options.end_line; | |
| uint32_t num_lines = m_options.num_lines; | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_matches = 0; | |
| // Dump all the line entries for the file in the list. | |
| llvm::StringRef last_module_file_name; | |
| for (const SymbolContext &sc : sc_list) { | |
| if (sc.comp_unit) { | |
| Module *module = sc.module_sp.get(); | |
| CompileUnit *cu = sc.comp_unit; | |
| const LineEntry &line_entry = sc.line_entry; | |
| assert(module && cu); | |
| // Are we looking for specific modules, files or lines? | |
| if (module_list.GetSize() && | |
| module_list.GetIndexForModule(module) == LLDB_INVALID_INDEX32) | |
| continue; | |
| if (!FileSpec::Match(file_spec, line_entry.GetFile())) | |
| continue; | |
| if (start_line > 0 && line_entry.line < start_line) | |
| continue; | |
| if (end_line > 0 && line_entry.line > end_line) | |
| continue; | |
| if (num_lines > 0 && num_matches > num_lines) | |
| continue; | |
| // Print a new header if the module changed. | |
| llvm::StringRef module_file_name = module->GetFileSpec().GetFilename(); | |
| assert(!module_file_name.empty()); | |
| if (module_file_name != last_module_file_name) { | |
| if (num_matches > 0) | |
| strm << "\n\n"; | |
| strm << "Lines found in module `" << module_file_name << "\n"; | |
| } | |
| // Dump the line entry. | |
| line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu, | |
| target, /*show_address_only=*/false); | |
| strm << "\n"; | |
| last_module_file_name = module_file_name; | |
| num_matches++; | |
| } | |
| } | |
| return num_matches; | |
| } | |
| // Dump the requested line entries for the file in the compilation unit. | |
| // Return the number of entries found. If module_list is set, only dump lines | |
| // contained in one of the modules. If the start_line option was specified, | |
| // don't print lines less than start_line. If the end_line option was | |
| // specified, don't print lines greater than end_line. If the num_lines | |
| // option was specified, dont print more than num_lines entries. | |
| uint32_t DumpFileLinesInCompUnit(Stream &strm, Module *module, | |
| CompileUnit *cu, const FileSpec &file_spec) { | |
| uint32_t start_line = m_options.start_line; | |
| uint32_t end_line = m_options.end_line; | |
| uint32_t num_lines = m_options.num_lines; | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t num_matches = 0; | |
| assert(module); | |
| if (cu) { | |
| assert(!file_spec.GetFilename().empty()); | |
| bool has_path = !file_spec.GetDirectory().empty(); | |
| const SupportFileList &cu_file_list = cu->GetSupportFiles(); | |
| size_t file_idx = cu_file_list.FindFileIndex(0, file_spec, has_path); | |
| if (file_idx != UINT32_MAX) { | |
| // Update the file to how it appears in the CU. | |
| const FileSpec &cu_file_spec = | |
| cu_file_list.GetFileSpecAtIndex(file_idx); | |
| // Dump all matching lines at or above start_line for the file in the | |
| // CU. | |
| llvm::StringRef file_spec_name = file_spec.GetFilename(); | |
| llvm::StringRef module_file_name = module->GetFileSpec().GetFilename(); | |
| bool cu_header_printed = false; | |
| uint32_t line = start_line; | |
| while (true) { | |
| LineEntry line_entry; | |
| // Find the lowest index of a line entry with a line equal to or | |
| // higher than 'line'. | |
| uint32_t start_idx = 0; | |
| start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec, | |
| /*exact=*/false, &line_entry); | |
| if (start_idx == UINT32_MAX) | |
| // No more line entries for our file in this CU. | |
| break; | |
| if (end_line > 0 && line_entry.line > end_line) | |
| break; | |
| // Loop through to find any other entries for this line, dumping | |
| // each. | |
| line = line_entry.line; | |
| do { | |
| num_matches++; | |
| if (num_lines > 0 && num_matches > num_lines) | |
| break; | |
| assert(cu_file_spec == line_entry.GetFile()); | |
| if (!cu_header_printed) { | |
| if (num_matches > 0) | |
| strm << "\n\n"; | |
| strm << "Lines found for file " << file_spec_name | |
| << " in compilation unit " | |
| << cu->GetPrimaryFile().GetFilename() << " in `" | |
| << module_file_name << "\n"; | |
| cu_header_printed = true; | |
| } | |
| line_entry.GetDescription(&strm, lldb::eDescriptionLevelBrief, cu, | |
| target, /*show_address_only=*/false); | |
| strm << "\n"; | |
| // Anymore after this one? | |
| start_idx++; | |
| start_idx = cu->FindLineEntry(start_idx, line, &cu_file_spec, | |
| /*exact=*/true, &line_entry); | |
| } while (start_idx != UINT32_MAX); | |
| // Try the next higher line, starting over at start_idx 0. | |
| line++; | |
| } | |
| } | |
| } | |
| return num_matches; | |
| } | |
| // Dump the requested line entries for the file in the module. Return the | |
| // number of entries found. If module_list is set, only dump lines contained | |
| // in one of the modules. If the start_line option was specified, don't print | |
| // lines less than start_line. If the end_line option was specified, don't | |
| // print lines greater than end_line. If the num_lines option was specified, | |
| // dont print more than num_lines entries. | |
| uint32_t DumpFileLinesInModule(Stream &strm, Module *module, | |
| const FileSpec &file_spec) { | |
| uint32_t num_matches = 0; | |
| if (module) { | |
| // Look through all the compilation units (CUs) in this module for ones | |
| // that contain lines of code from this source file. | |
| for (size_t i = 0; i < module->GetNumCompileUnits(); i++) { | |
| // Look for a matching source file in this CU. | |
| CompUnitSP cu_sp(module->GetCompileUnitAtIndex(i)); | |
| if (cu_sp) { | |
| num_matches += | |
| DumpFileLinesInCompUnit(strm, module, cu_sp.get(), file_spec); | |
| } | |
| } | |
| } | |
| return num_matches; | |
| } | |
| // Given an address and a list of modules, append the symbol contexts of all | |
| // line entries containing the address found in the modules and return the | |
| // count of matches. If none is found, return an error in 'error_strm'. | |
| size_t GetSymbolContextsForAddress(const ModuleList &module_list, | |
| lldb::addr_t addr, | |
| SymbolContextList &sc_list, | |
| StreamString &error_strm) { | |
| Address so_addr; | |
| size_t num_matches = 0; | |
| assert(module_list.GetSize() > 0); | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| if (!target->HasLoadedSections()) { | |
| // The target isn't loaded yet, we need to lookup the file address in all | |
| // modules. Note: the module list option does not apply to addresses. | |
| const size_t num_modules = module_list.GetSize(); | |
| for (size_t i = 0; i < num_modules; ++i) { | |
| ModuleSP module_sp(module_list.GetModuleAtIndex(i)); | |
| if (!module_sp) | |
| continue; | |
| if (module_sp->ResolveFileAddress(addr, so_addr)) { | |
| SymbolContext sc; | |
| sc.Clear(true); | |
| if (module_sp->ResolveSymbolContextForAddress( | |
| so_addr, eSymbolContextEverything, sc) & | |
| eSymbolContextLineEntry) { | |
| sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false); | |
| ++num_matches; | |
| } | |
| } | |
| } | |
| if (num_matches == 0) | |
| error_strm.Printf("Source information for file address 0x%" PRIx64 | |
| " not found in any modules.\n", | |
| addr); | |
| } else { | |
| // The target has some things loaded, resolve this address to a compile | |
| // unit + file + line and display | |
| if (target->ResolveLoadAddress(addr, so_addr)) { | |
| ModuleSP module_sp(so_addr.GetModule()); | |
| // Check to make sure this module is in our list. | |
| if (module_sp && module_list.GetIndexForModule(module_sp.get()) != | |
| LLDB_INVALID_INDEX32) { | |
| SymbolContext sc; | |
| sc.Clear(true); | |
| if (module_sp->ResolveSymbolContextForAddress( | |
| so_addr, eSymbolContextEverything, sc) & | |
| eSymbolContextLineEntry) { | |
| sc_list.AppendIfUnique(sc, /*merge_symbol_into_function=*/false); | |
| ++num_matches; | |
| } else { | |
| StreamString addr_strm; | |
| so_addr.Dump(&addr_strm, nullptr, | |
| Address::DumpStyleModuleWithFileAddress); | |
| error_strm.Printf( | |
| "Address 0x%" PRIx64 " resolves to %s, but there is" | |
| " no source information available for this address.\n", | |
| addr, addr_strm.GetData()); | |
| } | |
| } else { | |
| StreamString addr_strm; | |
| so_addr.Dump(&addr_strm, nullptr, | |
| Address::DumpStyleModuleWithFileAddress); | |
| error_strm.Printf("Address 0x%" PRIx64 | |
| " resolves to %s, but it cannot" | |
| " be found in any modules.\n", | |
| addr, addr_strm.GetData()); | |
| } | |
| } else | |
| error_strm.Printf("Unable to resolve address 0x%" PRIx64 ".\n", addr); | |
| } | |
| return num_matches; | |
| } | |
| // Dump the line entries found in functions matching the name specified in | |
| // the option. | |
| bool DumpLinesInFunctions(CommandReturnObject &result) { | |
| SymbolContextList sc_list_funcs; | |
| ConstString name(m_options.symbol_name); | |
| SymbolContextList sc_list_lines; | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); | |
| ModuleFunctionSearchOptions function_options; | |
| function_options.include_symbols = false; | |
| function_options.include_inlines = true; | |
| // Note: module_list can't be const& because FindFunctionSymbols isn't | |
| // const. | |
| ModuleList module_list = | |
| (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages(); | |
| module_list.FindFunctions(name, eFunctionNameTypeAuto, function_options, | |
| sc_list_funcs); | |
| size_t num_matches = sc_list_funcs.GetSize(); | |
| if (!num_matches) { | |
| // If we didn't find any functions with that name, try searching for | |
| // symbols that line up exactly with function addresses. | |
| SymbolContextList sc_list_symbols; | |
| module_list.FindFunctionSymbols(name, eFunctionNameTypeAuto, | |
| sc_list_symbols); | |
| for (const SymbolContext &sc : sc_list_symbols) { | |
| if (sc.symbol && sc.symbol->ValueIsAddress()) { | |
| const Address &base_address = sc.symbol->GetAddressRef(); | |
| Function *function = base_address.CalculateSymbolContextFunction(); | |
| if (function) { | |
| sc_list_funcs.Append(SymbolContext(function)); | |
| num_matches++; | |
| } | |
| } | |
| } | |
| } | |
| if (num_matches == 0) { | |
| result.AppendErrorWithFormat("Could not find function named \'%s\'", | |
| m_options.symbol_name.c_str()); | |
| return false; | |
| } | |
| for (const SymbolContext &sc : sc_list_funcs) { | |
| bool context_found_for_symbol = false; | |
| // Loop through all the ranges in the function. | |
| AddressRange range; | |
| for (uint32_t r = 0; | |
| sc.GetAddressRange(eSymbolContextEverything, r, | |
| /*use_inline_block_range=*/true, range); | |
| ++r) { | |
| // Append the symbol contexts for each address in the range to | |
| // sc_list_lines. | |
| const Address &base_address = range.GetBaseAddress(); | |
| const addr_t size = range.GetByteSize(); | |
| lldb::addr_t start_addr = base_address.GetLoadAddress(target); | |
| if (start_addr == LLDB_INVALID_ADDRESS) | |
| start_addr = base_address.GetFileAddress(); | |
| lldb::addr_t end_addr = start_addr + size; | |
| for (lldb::addr_t addr = start_addr; addr < end_addr; | |
| addr += addr_byte_size) { | |
| StreamString error_strm; | |
| if (!GetSymbolContextsForAddress(module_list, addr, sc_list_lines, | |
| error_strm)) | |
| result.AppendWarningWithFormatv("in symbol '{0}': {1}", | |
| sc.GetFunctionName(), | |
| error_strm.GetData()); | |
| else | |
| context_found_for_symbol = true; | |
| } | |
| } | |
| if (!context_found_for_symbol) | |
| result.AppendWarningWithFormatv("unable to find line information" | |
| " for matching symbol '{0}'\n", | |
| sc.GetFunctionName()); | |
| } | |
| if (sc_list_lines.GetSize() == 0) { | |
| result.AppendErrorWithFormatv("No line information could be found" | |
| " for any symbols matching '{0}'.\n", | |
| name); | |
| return false; | |
| } | |
| FileSpec file_spec; | |
| if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list_lines, | |
| module_list, file_spec)) { | |
| result.AppendErrorWithFormatv( | |
| "Unable to dump line information for symbol '{0}'.\n", name); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Dump the line entries found for the address specified in the option. | |
| bool DumpLinesForAddress(CommandReturnObject &result) { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| SymbolContextList sc_list; | |
| StreamString error_strm; | |
| if (!GetSymbolContextsForAddress(target->GetImages(), m_options.address, | |
| sc_list, error_strm)) { | |
| result.AppendErrorWithFormat("%s", error_strm.GetData()); | |
| return false; | |
| } | |
| ModuleList module_list; | |
| FileSpec file_spec; | |
| if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list, | |
| module_list, file_spec)) { | |
| result.AppendErrorWithFormat("No modules contain load address 0x%" PRIx64, | |
| m_options.address); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Dump the line entries found in the file specified in the option. | |
| bool DumpLinesForFile(CommandReturnObject &result) { | |
| FileSpec file_spec(m_options.file_name); | |
| const char *filename = m_options.file_name.c_str(); | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| const ModuleList &module_list = | |
| (m_module_list.GetSize() > 0) ? m_module_list : target->GetImages(); | |
| bool displayed_something = false; | |
| const size_t num_modules = module_list.GetSize(); | |
| for (uint32_t i = 0; i < num_modules; ++i) { | |
| // Dump lines for this module. | |
| Module *module = module_list.GetModulePointerAtIndex(i); | |
| assert(module); | |
| if (DumpFileLinesInModule(result.GetOutputStream(), module, file_spec)) | |
| displayed_something = true; | |
| } | |
| if (!displayed_something) { | |
| result.AppendErrorWithFormat("no source filenames matched '%s'", | |
| filename); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Dump the line entries for the current frame. | |
| bool DumpLinesForFrame(CommandReturnObject &result) { | |
| StackFrame *cur_frame = m_exe_ctx.GetFramePtr(); | |
| if (cur_frame == nullptr) { | |
| result.AppendError( | |
| "No selected frame to use to find the default source."); | |
| return false; | |
| } else if (!cur_frame->HasDebugInformation()) { | |
| result.AppendError("no debug info for the selected frame"); | |
| return false; | |
| } else { | |
| const SymbolContext &sc = | |
| cur_frame->GetSymbolContext(eSymbolContextLineEntry); | |
| SymbolContextList sc_list; | |
| sc_list.Append(sc); | |
| ModuleList module_list; | |
| FileSpec file_spec; | |
| if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list, | |
| module_list, file_spec)) { | |
| result.AppendError( | |
| "No source line info available for the selected frame."); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| // Collect the list of modules to search. | |
| m_module_list.Clear(); | |
| if (!m_options.modules.empty()) { | |
| for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) { | |
| FileSpec module_file_spec(m_options.modules[i]); | |
| if (module_file_spec) { | |
| ModuleSpec module_spec(module_file_spec); | |
| target->GetImages().FindModules(module_spec, m_module_list); | |
| if (m_module_list.IsEmpty()) | |
| result.AppendWarningWithFormatv("no module found for '{0}'", | |
| m_options.modules[i]); | |
| } | |
| } | |
| if (!m_module_list.GetSize()) { | |
| result.AppendError("no modules match the input"); | |
| return; | |
| } | |
| } else if (target->GetImages().GetSize() == 0) { | |
| result.AppendError("the target has no associated executable images"); | |
| return; | |
| } | |
| // Check the arguments to see what lines we should dump. | |
| if (!m_options.symbol_name.empty()) { | |
| // Print lines for symbol. | |
| if (DumpLinesInFunctions(result)) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| } else if (m_options.address != LLDB_INVALID_ADDRESS) { | |
| // Print lines for an address. | |
| if (DumpLinesForAddress(result)) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| } else if (!m_options.file_name.empty()) { | |
| // Dump lines for a file. | |
| if (DumpLinesForFile(result)) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| } else { | |
| // Dump the line for the current frame. | |
| if (DumpLinesForFrame(result)) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| } | |
| } | |
| CommandOptions m_options; | |
| ModuleList m_module_list; | |
| }; | |
| #pragma mark CommandObjectSourceList | |
| // CommandObjectSourceList | |
| #define LLDB_OPTIONS_source_list | |
| #include "CommandOptions.inc" | |
| class CommandObjectSourceList : public CommandObjectParsed { | |
| class CommandOptions : public Options { | |
| public: | |
| CommandOptions() = default; | |
| ~CommandOptions() override = default; | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| const int short_option = GetDefinitions()[option_idx].short_option; | |
| switch (short_option) { | |
| case 'l': | |
| if (option_arg.getAsInteger(0, start_line)) | |
| error = Status::FromErrorStringWithFormat("invalid line number: '%s'", | |
| option_arg.str().c_str()); | |
| break; | |
| case 'c': | |
| if (option_arg.getAsInteger(0, num_lines)) | |
| error = Status::FromErrorStringWithFormat("invalid line count: '%s'", | |
| option_arg.str().c_str()); | |
| break; | |
| case 'f': | |
| file_name = std::string(option_arg); | |
| break; | |
| case 'n': | |
| symbol_name = std::string(option_arg); | |
| break; | |
| case 'a': { | |
| address = OptionArgParser::ToAddress(execution_context, option_arg, | |
| LLDB_INVALID_ADDRESS, &error); | |
| } break; | |
| case 's': | |
| modules.push_back(std::string(option_arg)); | |
| break; | |
| case 'b': | |
| show_bp_locs = true; | |
| break; | |
| case 'r': | |
| reverse = true; | |
| break; | |
| case 'y': | |
| { | |
| OptionValueFileColonLine value; | |
| Status fcl_err = value.SetValueFromString(option_arg); | |
| if (!fcl_err.Success()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "Invalid value for file:line specifier: %s", fcl_err.AsCString()); | |
| } else { | |
| file_name = value.GetFileSpec().GetPath(); | |
| start_line = value.GetLineNumber(); | |
| // I don't see anything useful to do with a column number, but I don't | |
| // want to complain since someone may well have cut and pasted a | |
| // listing from somewhere that included a column. | |
| } | |
| } break; | |
| default: | |
| llvm_unreachable("Unimplemented option"); | |
| } | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| file_spec.Clear(); | |
| file_name.clear(); | |
| symbol_name.clear(); | |
| address = LLDB_INVALID_ADDRESS; | |
| start_line = 0; | |
| num_lines = 0; | |
| show_bp_locs = false; | |
| reverse = false; | |
| modules.clear(); | |
| } | |
| llvm::ArrayRef<OptionDefinition> GetDefinitions() override { | |
| return llvm::ArrayRef(g_source_list_options); | |
| } | |
| // Instance variables to hold the values for command options. | |
| FileSpec file_spec; | |
| std::string file_name; | |
| std::string symbol_name; | |
| lldb::addr_t address; | |
| uint32_t start_line; | |
| uint32_t num_lines; | |
| std::vector<std::string> modules; | |
| bool show_bp_locs; | |
| bool reverse; | |
| }; | |
| public: | |
| CommandObjectSourceList(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "source list", | |
| "Display source code for the current target " | |
| "process as specified by options.", | |
| nullptr, eCommandRequiresTarget) {} | |
| ~CommandObjectSourceList() override = default; | |
| Options *GetOptions() override { return &m_options; } | |
| std::optional<std::string> GetRepeatCommand(Args ¤t_command_args, | |
| uint32_t index) override { | |
| // This is kind of gross, but the command hasn't been parsed yet so we | |
| // can't look at the option values for this invocation... I have to scan | |
| // the arguments directly. | |
| auto iter = | |
| llvm::find_if(current_command_args, [](const Args::ArgEntry &e) { | |
| return e.ref() == "-r" || e.ref() == "--reverse"; | |
| }); | |
| if (iter == current_command_args.end()) | |
| return m_cmd_name; | |
| if (m_reverse_name.empty()) { | |
| m_reverse_name = m_cmd_name; | |
| m_reverse_name.append(" -r"); | |
| } | |
| return m_reverse_name; | |
| } | |
| protected: | |
| struct SourceInfo { | |
| ConstString function; | |
| LineEntry line_entry; | |
| SourceInfo(ConstString name, const LineEntry &line_entry) | |
| : function(name), line_entry(line_entry) {} | |
| SourceInfo() = default; | |
| bool IsValid() const { return (bool)function && line_entry.IsValid(); } | |
| bool operator==(const SourceInfo &rhs) const { | |
| return function == rhs.function && | |
| line_entry.original_file_sp->Equal( | |
| *rhs.line_entry.original_file_sp, | |
| SupportFile::eEqualFileSpecAndChecksumIfSet) && | |
| line_entry.line == rhs.line_entry.line; | |
| } | |
| bool operator!=(const SourceInfo &rhs) const { | |
| return function != rhs.function || | |
| !line_entry.original_file_sp->Equal( | |
| *rhs.line_entry.original_file_sp, | |
| SupportFile::eEqualFileSpecAndChecksumIfSet) || | |
| line_entry.line != rhs.line_entry.line; | |
| } | |
| bool operator<(const SourceInfo &rhs) const { | |
| if (function.GetCString() < rhs.function.GetCString()) | |
| return true; | |
| if (line_entry.GetFile().GetDirectory() < | |
| rhs.line_entry.GetFile().GetDirectory()) | |
| return true; | |
| if (line_entry.GetFile().GetFilename() < | |
| rhs.line_entry.GetFile().GetFilename()) | |
| return true; | |
| if (line_entry.line < rhs.line_entry.line) | |
| return true; | |
| return false; | |
| } | |
| }; | |
| size_t DisplayFunctionSource(const SymbolContext &sc, SourceInfo &source_info, | |
| CommandReturnObject &result) { | |
| if (!source_info.IsValid()) { | |
| source_info.function = sc.GetFunctionName(); | |
| source_info.line_entry = sc.GetFunctionStartLineEntry(); | |
| } | |
| if (sc.function) { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| SupportFileNSP start_file = std::make_shared<SupportFile>(); | |
| uint32_t start_line; | |
| uint32_t end_line; | |
| FileSpec end_file; | |
| if (sc.block == nullptr) { | |
| // Not an inlined function | |
| auto expected_info = sc.function->GetSourceInfo(); | |
| if (!expected_info) { | |
| result.AppendError(llvm::toString(expected_info.takeError())); | |
| return 0; | |
| } | |
| start_file = expected_info->first; | |
| start_line = expected_info->second.GetRangeBase(); | |
| end_line = expected_info->second.GetRangeEnd(); | |
| } else { | |
| // We have an inlined function | |
| start_file = source_info.line_entry.file_sp; | |
| start_line = source_info.line_entry.line; | |
| end_line = start_line + m_options.num_lines; | |
| } | |
| // This is a little hacky, but the first line table entry for a function | |
| // points to the "{" that starts the function block. It would be nice to | |
| // actually get the function declaration in there too. So back up a bit, | |
| // but not further than what you're going to display. | |
| uint32_t extra_lines; | |
| if (m_options.num_lines >= 10) | |
| extra_lines = 5; | |
| else | |
| extra_lines = m_options.num_lines / 2; | |
| uint32_t line_no; | |
| if (start_line <= extra_lines) | |
| line_no = 1; | |
| else | |
| line_no = start_line - extra_lines; | |
| // For fun, if the function is shorter than the number of lines we're | |
| // supposed to display, only display the function... | |
| if (end_line != 0) { | |
| if (m_options.num_lines > end_line - line_no) | |
| m_options.num_lines = end_line - line_no + extra_lines; | |
| } | |
| m_breakpoint_locations.Clear(); | |
| if (m_options.show_bp_locs) { | |
| const bool show_inlines = true; | |
| m_breakpoint_locations.Reset(start_file->GetSpecOnly(), 0, | |
| show_inlines); | |
| SearchFilterForUnconstrainedSearches target_search_filter( | |
| m_exe_ctx.GetTargetSP()); | |
| target_search_filter.Search(m_breakpoint_locations); | |
| } | |
| result.AppendMessageWithFormatv( | |
| "File: {0}", start_file->GetSpecOnly().GetPath().c_str()); | |
| // We don't care about the column here. | |
| const uint32_t column = 0; | |
| return target->GetSourceManager().DisplaySourceLinesWithLineNumbers( | |
| start_file, line_no, column, 0, m_options.num_lines, "", | |
| &result.GetOutputStream(), GetBreakpointLocations()); | |
| } else { | |
| result.AppendErrorWithFormat("Could not find function info for: \"%s\"", | |
| m_options.symbol_name.c_str()); | |
| } | |
| return 0; | |
| } | |
| // From Jim: The FindMatchingFunctions / FindMatchingFunctionSymbols | |
| // functions "take a possibly empty vector of strings which are names of | |
| // modules, and run the two search functions on the subset of the full module | |
| // list that matches the strings in the input vector". If we wanted to put | |
| // these somewhere, there should probably be a module-filter-list that can be | |
| // passed to the various ModuleList::Find* calls, which would either be a | |
| // vector of string names or a ModuleSpecList. | |
| void FindMatchingFunctions(Target &target, ConstString name, | |
| SymbolContextList &sc_list) { | |
| // Displaying the source for a symbol: | |
| if (m_options.num_lines == 0) | |
| m_options.num_lines = 10; | |
| ModuleFunctionSearchOptions function_options; | |
| function_options.include_symbols = true; | |
| function_options.include_inlines = false; | |
| const size_t num_modules = m_options.modules.size(); | |
| if (num_modules > 0) { | |
| ModuleList matching_modules; | |
| for (size_t i = 0; i < num_modules; ++i) { | |
| FileSpec module_file_spec(m_options.modules[i]); | |
| if (module_file_spec) { | |
| ModuleSpec module_spec(module_file_spec); | |
| matching_modules.Clear(); | |
| target.GetImages().FindModules(module_spec, matching_modules); | |
| matching_modules.FindFunctions(name, eFunctionNameTypeAuto, | |
| function_options, sc_list); | |
| } | |
| } | |
| } else { | |
| target.GetImages().FindFunctions(name, eFunctionNameTypeAuto, | |
| function_options, sc_list); | |
| } | |
| } | |
| void FindMatchingFunctionSymbols(Target &target, ConstString name, | |
| SymbolContextList &sc_list) { | |
| const size_t num_modules = m_options.modules.size(); | |
| if (num_modules > 0) { | |
| ModuleList matching_modules; | |
| for (size_t i = 0; i < num_modules; ++i) { | |
| FileSpec module_file_spec(m_options.modules[i]); | |
| if (module_file_spec) { | |
| ModuleSpec module_spec(module_file_spec); | |
| matching_modules.Clear(); | |
| target.GetImages().FindModules(module_spec, matching_modules); | |
| matching_modules.FindFunctionSymbols(name, eFunctionNameTypeAuto, | |
| sc_list); | |
| } | |
| } | |
| } else { | |
| target.GetImages().FindFunctionSymbols(name, eFunctionNameTypeAuto, | |
| sc_list); | |
| } | |
| } | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| Target *target = GetTarget(); | |
| assert(target && "target guaranteed by eCommandRequiresTarget"); | |
| if (!m_options.symbol_name.empty()) { | |
| SymbolContextList sc_list; | |
| ConstString name(m_options.symbol_name); | |
| // Displaying the source for a symbol. Search for function named name. | |
| FindMatchingFunctions(*target, name, sc_list); | |
| if (sc_list.GetSize() == 0) { | |
| // If we didn't find any functions with that name, try searching for | |
| // symbols that line up exactly with function addresses. | |
| SymbolContextList sc_list_symbols; | |
| FindMatchingFunctionSymbols(*target, name, sc_list_symbols); | |
| for (const SymbolContext &sc : sc_list_symbols) { | |
| if (sc.symbol && sc.symbol->ValueIsAddress()) { | |
| const Address &base_address = sc.symbol->GetAddressRef(); | |
| Function *function = base_address.CalculateSymbolContextFunction(); | |
| if (function) { | |
| sc_list.Append(SymbolContext(function)); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if (sc_list.GetSize() == 0) { | |
| result.AppendErrorWithFormat("Could not find function named: \"%s\"", | |
| m_options.symbol_name.c_str()); | |
| return; | |
| } | |
| std::set<SourceInfo> source_match_set; | |
| bool displayed_something = false; | |
| for (const SymbolContext &sc : sc_list) { | |
| SourceInfo source_info(sc.GetFunctionName(), | |
| sc.GetFunctionStartLineEntry()); | |
| if (source_info.IsValid() && | |
| source_match_set.find(source_info) == source_match_set.end()) { | |
| source_match_set.insert(source_info); | |
| if (DisplayFunctionSource(sc, source_info, result)) | |
| displayed_something = true; | |
| } | |
| } | |
| if (displayed_something) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| else | |
| result.SetStatus(eReturnStatusFailed); | |
| return; | |
| } else if (m_options.address != LLDB_INVALID_ADDRESS) { | |
| Address so_addr; | |
| StreamString error_strm; | |
| SymbolContextList sc_list; | |
| if (!target->HasLoadedSections()) { | |
| // The target isn't loaded yet, we need to lookup the file address in | |
| // all modules | |
| const ModuleList &module_list = target->GetImages(); | |
| const size_t num_modules = module_list.GetSize(); | |
| for (size_t i = 0; i < num_modules; ++i) { | |
| ModuleSP module_sp(module_list.GetModuleAtIndex(i)); | |
| if (module_sp && | |
| module_sp->ResolveFileAddress(m_options.address, so_addr)) { | |
| SymbolContext sc; | |
| sc.Clear(true); | |
| if (module_sp->ResolveSymbolContextForAddress( | |
| so_addr, eSymbolContextEverything, sc) & | |
| eSymbolContextLineEntry) | |
| sc_list.Append(sc); | |
| } | |
| } | |
| if (sc_list.GetSize() == 0) { | |
| result.AppendErrorWithFormat( | |
| "no modules have source information for file address 0x%" PRIx64, | |
| m_options.address); | |
| return; | |
| } | |
| } else { | |
| // The target has some things loaded, resolve this address to a compile | |
| // unit + file + line and display | |
| if (target->ResolveLoadAddress(m_options.address, so_addr)) { | |
| ModuleSP module_sp(so_addr.GetModule()); | |
| if (module_sp) { | |
| SymbolContext sc; | |
| sc.Clear(true); | |
| if (module_sp->ResolveSymbolContextForAddress( | |
| so_addr, eSymbolContextEverything, sc) & | |
| eSymbolContextLineEntry) { | |
| sc_list.Append(sc); | |
| } else { | |
| so_addr.Dump(&error_strm, nullptr, | |
| Address::DumpStyleModuleWithFileAddress); | |
| result.AppendErrorWithFormat("address resolves to %s, but there " | |
| "is no line table information " | |
| "available for this address", | |
| error_strm.GetData()); | |
| return; | |
| } | |
| } | |
| } | |
| if (sc_list.GetSize() == 0) { | |
| result.AppendErrorWithFormat( | |
| "no modules contain load address 0x%" PRIx64, m_options.address); | |
| return; | |
| } | |
| } | |
| for (const SymbolContext &sc : sc_list) { | |
| if (sc.comp_unit) { | |
| if (m_options.show_bp_locs) { | |
| m_breakpoint_locations.Clear(); | |
| const bool show_inlines = true; | |
| m_breakpoint_locations.Reset(sc.comp_unit->GetPrimaryFile(), 0, | |
| show_inlines); | |
| SearchFilterForUnconstrainedSearches target_search_filter( | |
| target->shared_from_this()); | |
| target_search_filter.Search(m_breakpoint_locations); | |
| } | |
| bool show_fullpaths = true; | |
| bool show_module = true; | |
| bool show_inlined_frames = true; | |
| const bool show_function_arguments = true; | |
| const bool show_function_name = true; | |
| sc.DumpStopContext(&result.GetOutputStream(), | |
| m_exe_ctx.GetBestExecutionContextScope(), | |
| sc.line_entry.range.GetBaseAddress(), | |
| show_fullpaths, show_module, show_inlined_frames, | |
| show_function_arguments, show_function_name); | |
| result.GetOutputStream().EOL(); | |
| if (m_options.num_lines == 0) | |
| m_options.num_lines = 10; | |
| size_t lines_to_back_up = | |
| m_options.num_lines >= 10 ? 5 : m_options.num_lines / 2; | |
| const uint32_t column = | |
| (GetDebugger().GetStopShowColumn() != eStopShowColumnNone) | |
| ? sc.line_entry.column | |
| : 0; | |
| target->GetSourceManager().DisplaySourceLinesWithLineNumbers( | |
| sc.comp_unit->GetPrimarySupportFile(), sc.line_entry.line, column, | |
| lines_to_back_up, m_options.num_lines - lines_to_back_up, "->", | |
| &result.GetOutputStream(), GetBreakpointLocations()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| } | |
| } else if (m_options.file_name.empty()) { | |
| // Last valid source manager context, or the current frame if no valid | |
| // last context in source manager. One little trick here, if you type the | |
| // exact same list command twice in a row, it is more likely because you | |
| // typed it once, then typed it again | |
| if (m_options.start_line == 0) { | |
| if (target->GetSourceManager().DisplayMoreWithLineNumbers( | |
| &result.GetOutputStream(), m_options.num_lines, | |
| m_options.reverse, GetBreakpointLocations())) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| if (target->GetSourceManager().AtLastLine(m_options.reverse)) { | |
| result.AppendNoteWithFormatv( | |
| "Reached {0} of the file, no more to page", | |
| m_options.reverse ? "beginning" : "end"); | |
| } else { | |
| result.AppendNote("no source available"); | |
| } | |
| } | |
| } else { | |
| if (m_options.num_lines == 0) | |
| m_options.num_lines = 10; | |
| if (m_options.show_bp_locs) { | |
| SourceManager::FileSP last_file_sp( | |
| target->GetSourceManager().GetLastFile()); | |
| if (last_file_sp) { | |
| const bool show_inlines = true; | |
| m_breakpoint_locations.Reset( | |
| last_file_sp->GetSupportFile()->GetSpecOnly(), 0, show_inlines); | |
| SearchFilterForUnconstrainedSearches target_search_filter( | |
| target->shared_from_this()); | |
| target_search_filter.Search(m_breakpoint_locations); | |
| } | |
| } else | |
| m_breakpoint_locations.Clear(); | |
| const uint32_t column = 0; | |
| if (target->GetSourceManager() | |
| .DisplaySourceLinesWithLineNumbersUsingLastFile( | |
| m_options.start_line, // Line to display | |
| m_options.num_lines, // Lines after line to | |
| UINT32_MAX, // Don't mark "line" | |
| column, | |
| "", // Don't mark "line" | |
| &result.GetOutputStream(), GetBreakpointLocations())) { | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| } | |
| } else { | |
| // const char *filename = m_options.file_name.c_str(); | |
| FileSpec file_spec(m_options.file_name); | |
| bool check_inlines = false; | |
| const InlineStrategy inline_strategy = target->GetInlineStrategy(); | |
| if (inline_strategy == eInlineBreakpointsAlways || | |
| (inline_strategy == eInlineBreakpointsHeaders && | |
| !file_spec.IsSourceImplementationFile())) | |
| check_inlines = true; | |
| SymbolContextList sc_list; | |
| size_t num_matches = 0; | |
| if (!m_options.modules.empty()) { | |
| ModuleList matching_modules; | |
| for (size_t i = 0, e = m_options.modules.size(); i < e; ++i) { | |
| FileSpec module_file_spec(m_options.modules[i]); | |
| if (module_file_spec) { | |
| ModuleSpec module_spec(module_file_spec); | |
| matching_modules.Clear(); | |
| target->GetImages().FindModules(module_spec, matching_modules); | |
| num_matches += matching_modules.ResolveSymbolContextsForFileSpec( | |
| file_spec, 1, check_inlines, | |
| SymbolContextItem(eSymbolContextModule | | |
| eSymbolContextCompUnit | | |
| eSymbolContextLineEntry), | |
| sc_list); | |
| } | |
| } | |
| } else { | |
| num_matches = target->GetImages().ResolveSymbolContextsForFileSpec( | |
| file_spec, 1, check_inlines, | |
| eSymbolContextModule | eSymbolContextCompUnit | | |
| eSymbolContextLineEntry, | |
| sc_list); | |
| } | |
| if (num_matches == 0) { | |
| result.AppendErrorWithFormat("Could not find source file \"%s\"", | |
| m_options.file_name.c_str()); | |
| return; | |
| } | |
| if (num_matches > 1) { | |
| bool got_multiple = false; | |
| CompileUnit *test_cu = nullptr; | |
| for (const SymbolContext &sc : sc_list) { | |
| if (sc.comp_unit) { | |
| if (test_cu) { | |
| if (test_cu != sc.comp_unit) | |
| got_multiple = true; | |
| break; | |
| } else | |
| test_cu = sc.comp_unit; | |
| } | |
| } | |
| if (got_multiple) { | |
| result.AppendErrorWithFormat( | |
| "Multiple source files found matching: \"%s.\"", | |
| m_options.file_name.c_str()); | |
| return; | |
| } | |
| } | |
| SymbolContext sc; | |
| if (sc_list.GetContextAtIndex(0, sc)) { | |
| if (sc.comp_unit) { | |
| if (m_options.show_bp_locs) { | |
| const bool show_inlines = true; | |
| m_breakpoint_locations.Reset(sc.comp_unit->GetPrimaryFile(), 0, | |
| show_inlines); | |
| SearchFilterForUnconstrainedSearches target_search_filter( | |
| target->shared_from_this()); | |
| target_search_filter.Search(m_breakpoint_locations); | |
| } else | |
| m_breakpoint_locations.Clear(); | |
| if (m_options.num_lines == 0) | |
| m_options.num_lines = 10; | |
| const uint32_t column = 0; | |
| // Headers aren't always in the DWARF but if they have | |
| // executable code (eg., inlined-functions) then the callsite's | |
| // file(s) will be found and assigned to | |
| // sc.comp_unit->GetPrimarySupportFile, which is NOT what we want to | |
| // print. Instead, we want to print the one from the line entry. | |
| SupportFileNSP found_file_sp = sc.line_entry.file_sp; | |
| target->GetSourceManager().DisplaySourceLinesWithLineNumbers( | |
| found_file_sp, m_options.start_line, column, 0, | |
| m_options.num_lines, "", &result.GetOutputStream(), | |
| GetBreakpointLocations()); | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } else { | |
| result.AppendErrorWithFormat("No comp unit found for: \"%s.\"", | |
| m_options.file_name.c_str()); | |
| } | |
| } | |
| } | |
| if (result.GetStatus() != eReturnStatusFailed) | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| const SymbolContextList *GetBreakpointLocations() { | |
| if (m_breakpoint_locations.GetFileLineMatches().GetSize() > 0) | |
| return &m_breakpoint_locations.GetFileLineMatches(); | |
| return nullptr; | |
| } | |
| CommandOptions m_options; | |
| FileLineResolver m_breakpoint_locations; | |
| std::string m_reverse_name; | |
| }; | |
| class CommandObjectSourceCacheDump : public CommandObjectParsed { | |
| public: | |
| CommandObjectSourceCacheDump(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "source cache dump", | |
| "Dump the state of the source code cache. Intended " | |
| "to be used for debugging LLDB itself.", | |
| nullptr) {} | |
| ~CommandObjectSourceCacheDump() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| // Dump the debugger source cache. | |
| result.GetOutputStream() << "Debugger Source File Cache\n"; | |
| SourceManager::SourceFileCache &cache = GetDebugger().GetSourceFileCache(); | |
| cache.Dump(result.GetOutputStream()); | |
| // Dump the process source cache. | |
| if (ProcessSP process_sp = m_exe_ctx.GetProcessSP()) { | |
| result.GetOutputStream() << "\nProcess Source File Cache\n"; | |
| SourceManager::SourceFileCache &cache = process_sp->GetSourceFileCache(); | |
| cache.Dump(result.GetOutputStream()); | |
| } | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| }; | |
| class CommandObjectSourceCacheClear : public CommandObjectParsed { | |
| public: | |
| CommandObjectSourceCacheClear(CommandInterpreter &interpreter) | |
| : CommandObjectParsed(interpreter, "source cache clear", | |
| "Clear the source code cache.\n", nullptr) {} | |
| ~CommandObjectSourceCacheClear() override = default; | |
| protected: | |
| void DoExecute(Args &command, CommandReturnObject &result) override { | |
| // Clear the debugger cache. | |
| SourceManager::SourceFileCache &cache = GetDebugger().GetSourceFileCache(); | |
| cache.Clear(); | |
| // Clear the process cache. | |
| if (ProcessSP process_sp = m_exe_ctx.GetProcessSP()) | |
| process_sp->GetSourceFileCache().Clear(); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| }; | |
| class CommandObjectSourceCache : public CommandObjectMultiword { | |
| public: | |
| CommandObjectSourceCache(CommandInterpreter &interpreter) | |
| : CommandObjectMultiword(interpreter, "source cache", | |
| "Commands for managing the source code cache.", | |
| "source cache <sub-command>") { | |
| LoadSubCommand( | |
| "dump", CommandObjectSP(new CommandObjectSourceCacheDump(interpreter))); | |
| LoadSubCommand("clear", CommandObjectSP(new CommandObjectSourceCacheClear( | |
| interpreter))); | |
| } | |
| ~CommandObjectSourceCache() override = default; | |
| private: | |
| CommandObjectSourceCache(const CommandObjectSourceCache &) = delete; | |
| const CommandObjectSourceCache & | |
| operator=(const CommandObjectSourceCache &) = delete; | |
| }; | |
| #pragma mark CommandObjectMultiwordSource | |
| // CommandObjectMultiwordSource | |
| CommandObjectMultiwordSource::CommandObjectMultiwordSource( | |
| CommandInterpreter &interpreter) | |
| : CommandObjectMultiword(interpreter, "source", | |
| "Commands for examining " | |
| "source code described by " | |
| "debug information for the " | |
| "current target process.", | |
| "source <subcommand> [<subcommand-options>]") { | |
| LoadSubCommand("info", | |
| CommandObjectSP(new CommandObjectSourceInfo(interpreter))); | |
| LoadSubCommand("list", | |
| CommandObjectSP(new CommandObjectSourceList(interpreter))); | |
| LoadSubCommand("cache", | |
| CommandObjectSP(new CommandObjectSourceCache(interpreter))); | |
| } | |
| CommandObjectMultiwordSource::~CommandObjectMultiwordSource() = default; |