| //===-- 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/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/SectionLoadList.h" |
| #include "lldb/Target/StackFrame.h" |
| #include "lldb/Utility/FileSpec.h" |
| |
| 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() : Options() {} |
| |
| ~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.SetErrorStringWithFormat("invalid line number: '%s'", |
| option_arg.str().c_str()); |
| break; |
| |
| case 'e': |
| if (option_arg.getAsInteger(0, end_line)) |
| error.SetErrorStringWithFormat("invalid line number: '%s'", |
| option_arg.str().c_str()); |
| break; |
| |
| case 'c': |
| if (option_arg.getAsInteger(0, num_lines)) |
| error.SetErrorStringWithFormat("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::makeArrayRef(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), |
| m_options() {} |
| |
| ~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 = m_exe_ctx.GetTargetPtr(); |
| |
| uint32_t num_matches = 0; |
| // Dump all the line entries for the file in the list. |
| ConstString last_module_file_name; |
| uint32_t num_scs = sc_list.GetSize(); |
| for (uint32_t i = 0; i < num_scs; ++i) { |
| SymbolContext sc; |
| sc_list.GetContextAtIndex(i, sc); |
| 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.file)) |
| 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. |
| ConstString module_file_name = module->GetFileSpec().GetFilename(); |
| assert(module_file_name); |
| 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 = m_exe_ctx.GetTargetPtr(); |
| |
| uint32_t num_matches = 0; |
| assert(module); |
| if (cu) { |
| assert(file_spec.GetFilename().AsCString()); |
| bool has_path = (file_spec.GetDirectory().AsCString() != nullptr); |
| const FileSpecList &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. |
| ConstString file_spec_name = file_spec.GetFilename(); |
| ConstString 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.file); |
| 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 = m_exe_ctx.GetTargetPtr(); |
| if (target->GetSectionLoadList().IsEmpty()) { |
| // 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->GetSectionLoadList().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.c_str()); |
| SymbolContextList sc_list_lines; |
| Target *target = m_exe_ctx.GetTargetPtr(); |
| 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); |
| size_t num_symbol_matches = sc_list_symbols.GetSize(); |
| for (size_t i = 0; i < num_symbol_matches; i++) { |
| SymbolContext sc; |
| sc_list_symbols.GetContextAtIndex(i, sc); |
| 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\'.\n", |
| m_options.symbol_name.c_str()); |
| return false; |
| } |
| for (size_t i = 0; i < num_matches; i++) { |
| SymbolContext sc; |
| sc_list_funcs.GetContextAtIndex(i, sc); |
| 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.AppendWarningWithFormat("in symbol '%s': %s", |
| sc.GetFunctionName().AsCString(), |
| error_strm.GetData()); |
| else |
| context_found_for_symbol = true; |
| } |
| } |
| if (!context_found_for_symbol) |
| result.AppendWarningWithFormat("Unable to find line information" |
| " for matching symbol '%s'.\n", |
| sc.GetFunctionName().AsCString()); |
| } |
| if (sc_list_lines.GetSize() == 0) { |
| result.AppendErrorWithFormat("No line information could be found" |
| " for any symbols matching '%s'.\n", |
| name.AsCString()); |
| return false; |
| } |
| FileSpec file_spec; |
| if (!DumpLinesInSymbolContexts(result.GetOutputStream(), sc_list_lines, |
| module_list, file_spec)) { |
| result.AppendErrorWithFormat( |
| "Unable to dump line information for symbol '%s'.\n", |
| name.AsCString()); |
| return false; |
| } |
| return true; |
| } |
| |
| // Dump the line entries found for the address specified in the option. |
| bool DumpLinesForAddress(CommandReturnObject &result) { |
| Target *target = m_exe_ctx.GetTargetPtr(); |
| SymbolContextList sc_list; |
| |
| StreamString error_strm; |
| if (!GetSymbolContextsForAddress(target->GetImages(), m_options.address, |
| sc_list, error_strm)) { |
| result.AppendErrorWithFormat("%s.\n", 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 |
| ".\n", |
| 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 = m_exe_ctx.GetTargetPtr(); |
| 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'.\n", |
| 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; |
| } |
| |
| bool DoExecute(Args &command, CommandReturnObject &result) override { |
| const size_t argc = command.GetArgumentCount(); |
| |
| if (argc != 0) { |
| result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n", |
| GetCommandName().str().c_str()); |
| return false; |
| } |
| |
| Target *target = m_exe_ctx.GetTargetPtr(); |
| if (target == nullptr) { |
| target = GetDebugger().GetSelectedTarget().get(); |
| if (target == nullptr) { |
| result.AppendError("invalid target, create a debug target using the " |
| "'target create' command."); |
| return false; |
| } |
| } |
| |
| uint32_t addr_byte_size = target->GetArchitecture().GetAddressByteSize(); |
| result.GetOutputStream().SetAddressByteSize(addr_byte_size); |
| result.GetErrorStream().SetAddressByteSize(addr_byte_size); |
| |
| // 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.AppendWarningWithFormat("No module found for '%s'.\n", |
| m_options.modules[i].c_str()); |
| } |
| } |
| if (!m_module_list.GetSize()) { |
| result.AppendError("No modules match the input."); |
| return false; |
| } |
| } else if (target->GetImages().GetSize() == 0) { |
| result.AppendError("The target has no associated executable images."); |
| return false; |
| } |
| |
| // 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); |
| } |
| return result.Succeeded(); |
| } |
| |
| 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() : Options() {} |
| |
| ~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.SetErrorStringWithFormat("invalid line number: '%s'", |
| option_arg.str().c_str()); |
| break; |
| |
| case 'c': |
| if (option_arg.getAsInteger(0, num_lines)) |
| error.SetErrorStringWithFormat("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.SetErrorStringWithFormat( |
| "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::makeArrayRef(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), |
| m_options() {} |
| |
| ~CommandObjectSourceList() override = default; |
| |
| Options *GetOptions() override { return &m_options; } |
| |
| const char *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.c_str(); |
| |
| if (m_reverse_name.empty()) { |
| m_reverse_name = m_cmd_name; |
| m_reverse_name.append(" -r"); |
| } |
| return m_reverse_name.c_str(); |
| } |
| |
| protected: |
| struct SourceInfo { |
| ConstString function; |
| LineEntry line_entry; |
| |
| SourceInfo(ConstString name, const LineEntry &line_entry) |
| : function(name), line_entry(line_entry) {} |
| |
| SourceInfo() : function(), line_entry() {} |
| |
| bool IsValid() const { return (bool)function && line_entry.IsValid(); } |
| |
| bool operator==(const SourceInfo &rhs) const { |
| return function == rhs.function && |
| line_entry.original_file == rhs.line_entry.original_file && |
| line_entry.line == rhs.line_entry.line; |
| } |
| |
| bool operator!=(const SourceInfo &rhs) const { |
| return function != rhs.function || |
| line_entry.original_file != rhs.line_entry.original_file || |
| line_entry.line != rhs.line_entry.line; |
| } |
| |
| bool operator<(const SourceInfo &rhs) const { |
| if (function.GetCString() < rhs.function.GetCString()) |
| return true; |
| if (line_entry.file.GetDirectory().GetCString() < |
| rhs.line_entry.file.GetDirectory().GetCString()) |
| return true; |
| if (line_entry.file.GetFilename().GetCString() < |
| rhs.line_entry.file.GetFilename().GetCString()) |
| 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 = m_exe_ctx.GetTargetPtr(); |
| |
| FileSpec start_file; |
| uint32_t start_line; |
| uint32_t end_line; |
| FileSpec end_file; |
| |
| if (sc.block == nullptr) { |
| // Not an inlined function |
| sc.function->GetStartLineSourceInfo(start_file, start_line); |
| if (start_line == 0) { |
| result.AppendErrorWithFormat("Could not find line information for " |
| "start of function: \"%s\".\n", |
| source_info.function.GetCString()); |
| return 0; |
| } |
| sc.function->GetEndLineSourceInfo(end_file, end_line); |
| } else { |
| // We have an inlined function |
| start_file = source_info.line_entry.file; |
| 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, 0, show_inlines); |
| SearchFilterForUnconstrainedSearches target_search_filter( |
| m_exe_ctx.GetTargetSP()); |
| target_search_filter.Search(m_breakpoint_locations); |
| } |
| |
| result.AppendMessageWithFormat("File: %s\n", |
| start_file.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\".\n", |
| 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); |
| } |
| } |
| |
| bool DoExecute(Args &command, CommandReturnObject &result) override { |
| const size_t argc = command.GetArgumentCount(); |
| |
| if (argc != 0) { |
| result.AppendErrorWithFormat("'%s' takes no arguments, only flags.\n", |
| GetCommandName().str().c_str()); |
| return false; |
| } |
| |
| Target *target = m_exe_ctx.GetTargetPtr(); |
| |
| if (!m_options.symbol_name.empty()) { |
| SymbolContextList sc_list; |
| ConstString name(m_options.symbol_name.c_str()); |
| |
| // Displaying the source for a symbol. Search for function named name. |
| FindMatchingFunctions(target, name, sc_list); |
| size_t num_matches = sc_list.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; |
| FindMatchingFunctionSymbols(target, name, sc_list_symbols); |
| size_t num_symbol_matches = sc_list_symbols.GetSize(); |
| |
| for (size_t i = 0; i < num_symbol_matches; i++) { |
| SymbolContext sc; |
| sc_list_symbols.GetContextAtIndex(i, sc); |
| 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)); |
| num_matches++; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (num_matches == 0) { |
| result.AppendErrorWithFormat("Could not find function named: \"%s\".\n", |
| m_options.symbol_name.c_str()); |
| return false; |
| } |
| |
| if (num_matches > 1) { |
| std::set<SourceInfo> source_match_set; |
| |
| bool displayed_something = false; |
| for (size_t i = 0; i < num_matches; i++) { |
| SymbolContext sc; |
| sc_list.GetContextAtIndex(i, sc); |
| SourceInfo source_info(sc.GetFunctionName(), |
| sc.GetFunctionStartLineEntry()); |
| |
| if (source_info.IsValid()) { |
| if (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); |
| } else { |
| SymbolContext sc; |
| sc_list.GetContextAtIndex(0, sc); |
| SourceInfo source_info; |
| |
| if (DisplayFunctionSource(sc, source_info, result)) { |
| result.SetStatus(eReturnStatusSuccessFinishResult); |
| } else { |
| result.SetStatus(eReturnStatusFailed); |
| } |
| } |
| return result.Succeeded(); |
| } else if (m_options.address != LLDB_INVALID_ADDRESS) { |
| Address so_addr; |
| StreamString error_strm; |
| SymbolContextList sc_list; |
| |
| if (target->GetSectionLoadList().IsEmpty()) { |
| // 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 |
| ".\n", |
| m_options.address); |
| return false; |
| } |
| } else { |
| // The target has some things loaded, resolve this address to a compile |
| // unit + file + line and display |
| if (target->GetSectionLoadList().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.\n", |
| error_strm.GetData()); |
| return false; |
| } |
| } |
| } |
| |
| if (sc_list.GetSize() == 0) { |
| result.AppendErrorWithFormat( |
| "no modules contain load address 0x%" PRIx64 ".\n", |
| m_options.address); |
| return false; |
| } |
| } |
| uint32_t num_matches = sc_list.GetSize(); |
| for (uint32_t i = 0; i < num_matches; ++i) { |
| SymbolContext sc; |
| sc_list.GetContextAtIndex(i, sc); |
| 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->GetPrimaryFile(), 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 (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->GetFileSpec(), 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(); |
| |
| bool check_inlines = false; |
| 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.ResolveSymbolContextForFilePath( |
| filename, 0, check_inlines, |
| SymbolContextItem(eSymbolContextModule | |
| eSymbolContextCompUnit), |
| sc_list); |
| } |
| } |
| } else { |
| num_matches = target->GetImages().ResolveSymbolContextForFilePath( |
| filename, 0, check_inlines, |
| eSymbolContextModule | eSymbolContextCompUnit, sc_list); |
| } |
| |
| if (num_matches == 0) { |
| result.AppendErrorWithFormat("Could not find source file \"%s\".\n", |
| m_options.file_name.c_str()); |
| return false; |
| } |
| |
| if (num_matches > 1) { |
| bool got_multiple = false; |
| CompileUnit *test_cu = nullptr; |
| |
| for (unsigned i = 0; i < num_matches; i++) { |
| SymbolContext sc; |
| sc_list.GetContextAtIndex(i, sc); |
| 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.\"\n", |
| m_options.file_name.c_str()); |
| return false; |
| } |
| } |
| |
| 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; |
| target->GetSourceManager().DisplaySourceLinesWithLineNumbers( |
| sc.comp_unit->GetPrimaryFile(), 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.\"\n", |
| m_options.file_name.c_str()); |
| return false; |
| } |
| } |
| } |
| return result.Succeeded(); |
| } |
| |
| 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; |
| }; |
| |
| #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))); |
| } |
| |
| CommandObjectMultiwordSource::~CommandObjectMultiwordSource() = default; |