From 64d78d8b3cd09dff32c97fbefa56bcfc8b676406 Mon Sep 17 00:00:00 2001 From: Tom Yang Date: Thu, 12 Oct 2023 11:21:53 -0700 Subject: [PATCH] Add `target modules dump separate-debug-info` (#66035) Add a new command ``` target modules dump separate-debug-info [-j] [ [ [...]]] ``` or ``` image dump separate-debug-info [-j] [ [ [...]]] ``` (since `image` is an alias for `target modules`). This lists the separate debug info files and their current status (loaded or not loaded) for the specified modules. This diff implements this command for mach-O files with OSO and ELF files with dwo. Example dwo: ``` (lldb) image dump separate-debug-info Symbol file: /home/toyang/workspace/dwo-scratch/a.out Type: "dwo" Dwo ID Err Dwo Path ------------------ --- ----------------------------------------- 0x9a429da5abb6faae /home/toyang/workspace/scratch-dwo/a-main.dwo 0xbcc129959e76ff33 /home/toyang/workspace/scratch-dwo/a-foo.dwo (lldb) image dump separate-debug-info -j [ { "separate-debug-info-files": [ { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 11115620165179865774, "dwo_name": "a-main.dwo", "loaded": true, "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a-main.dwo" }, { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 13601198072221073203, "dwo_name": "a-foo.dwo", "loaded": true, "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a-foo.dwo" } ], "symfile": "/home/toyang/workspace/dwo-scratch/a.out", "type": "dwo" } ] ``` Example dwo with missing dwo: ``` (lldb) image dump separate-debug-info Symbol file: /home/toyang/workspace/dwo-scratch/a.out Type: "dwo" Dwo ID Err Dwo Path ------------------ --- ----------------------------------------- 0x9a429da5abb6faae E unable to locate .dwo debug file "/home/toyang/workspace/scratch-dwo/b.out-main.dwo" for skeleton DIE 0x0000000000000014 0xbcc129959e76ff33 E unable to locate .dwo debug file "/home/toyang/workspace/scratch-dwo/b.out-foo.dwo" for skeleton DIE 0x000000000000003c (lldb) image dump separate-debug-info -j [ { "separate-debug-info-files": [ { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 11115620165179865774, "dwo_name": "a-main.dwo", "error": "unable to locate .dwo debug file \"/home/toyang/workspace/dwo-scratch/a-main.dwo\" for skeleton DIE 0x0000000000000014", "loaded": false }, { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 13601198072221073203, "dwo_name": "a-foo.dwo", "error": "unable to locate .dwo debug file \"/home/toyang/workspace/dwo-scratch/a-foo.dwo\" for skeleton DIE 0x000000000000003c", "loaded": false } ], "symfile": "/home/toyang/workspace/dwo-scratch/a.out", "type": "dwo" } ] ``` Example output with dwp: ``` (lldb) image dump separate-debug-info Symbol file: /home/toyang/workspace/dwo-scratch/a.out Type: "dwo" Dwo ID Err Dwo Path ------------------ --- ----------------------------------------- 0x9a429da5abb6faae /home/toyang/workspace/dwo-scratch/a.out.dwp(a-main.dwo) 0xbcc129959e76ff33 /home/toyang/workspace/dwo-scratch/a.out.dwp(a-foo.dwo) (lldb) image dump separate-debug-info -j [ { "separate-debug-info-files": [ { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 11115620165179865774, "dwo_name": "a-main.dwo", "loaded": true, "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a.out.dwp" }, { "comp_dir": "/home/toyang/workspace/dwo-scratch", "dwo_id": 13601198072221073203, "dwo_name": "a-foo.dwo", "loaded": true, "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a.out.dwp" } ], "symfile": "/home/toyang/workspace/dwo-scratch/a.out", "type": "dwo" } ] ``` Example oso on my Mac: ``` (lldb) image dump separate-debug-info Symbol file: /Users/toyang/workspace/scratch/a.out Type: "oso" Mod Time Err Oso Path ------------------ --- --------------------- 0x0000000064e64868 /Users/toyang/workspace/scratch/foo.a(foo.o) 0x0000000064e64868 /Users/toyang/workspace/scratch/foo.a(main.o) (lldb) image dump separate-debug-info -j [ { "separate-debug-info-files": [ { "loaded": true, "oso_mod_time": 1692813416, "oso_path": "/Users/toyang/workspace/scratch/foo.a(foo.o)", "so_file": "/Users/toyang/workspace/scratch/foo.cpp" }, { "loaded": true, "oso_mod_time": 1692813416, "oso_path": "/Users/toyang/workspace/scratch/foo.a(main.o)", "so_file": "/Users/toyang/workspace/scratch/main.cpp" } ], "symfile": "/Users/toyang/workspace/scratch/a.out", "type": "oso" } ] ``` Test Plan: Tested on Mac OS and Linux. ``` lldb-dotest -p TestDumpDwo lldb-dotest -p TestDumpOso ``` --------- Co-authored-by: Tom Yang --- lldb/include/lldb/Symbol/SymbolFile.h | 13 + lldb/source/Commands/CommandObjectTarget.cpp | 260 +++++++++++++++++- lldb/source/Commands/Options.td | 5 + .../SymbolFile/DWARF/SymbolFileDWARF.cpp | 71 ++++- .../SymbolFile/DWARF/SymbolFileDWARF.h | 5 + .../DWARF/SymbolFileDWARFDebugMap.cpp | 39 ++- .../DWARF/SymbolFileDWARFDebugMap.h | 5 + lldb/source/Symbol/SymbolFile.cpp | 1 + .../dump-separate-debug-info/dwo/Makefile | 4 + .../dwo/TestDumpDwo.py | 122 ++++++++ .../dump-separate-debug-info/dwo/foo.cpp | 3 + .../target/dump-separate-debug-info/dwo/foo.h | 6 + .../dump-separate-debug-info/dwo/main.cpp | 3 + .../dump-separate-debug-info/oso/Makefile | 3 + .../oso/TestDumpOso.py | 120 ++++++++ .../dump-separate-debug-info/oso/foo.cpp | 3 + .../target/dump-separate-debug-info/oso/foo.h | 6 + .../dump-separate-debug-info/oso/main.cpp | 3 + 18 files changed, 667 insertions(+), 5 deletions(-) create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/dwo/Makefile create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/dwo/TestDumpDwo.py create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.cpp create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.h create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/dwo/main.cpp create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/oso/Makefile create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.cpp create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.h create mode 100644 lldb/test/API/commands/target/dump-separate-debug-info/oso/main.cpp diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h index 8de752816cf94e..512dd9acb86db6 100644 --- a/lldb/include/lldb/Symbol/SymbolFile.h +++ b/lldb/include/lldb/Symbol/SymbolFile.h @@ -22,6 +22,7 @@ #include "lldb/Symbol/TypeList.h" #include "lldb/Symbol/TypeSystem.h" #include "lldb/Target/Statistics.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/XcodeSDK.h" #include "lldb/lldb-private.h" #include "llvm/ADT/DenseSet.h" @@ -434,6 +435,18 @@ class SymbolFile : public PluginInterface { virtual bool GetDebugInfoHadFrameVariableErrors() const = 0; virtual void SetDebugInfoHadFrameVariableErrors() = 0; + /// Return true if separate debug info files are supported and this function + /// succeeded, false otherwise. + /// + /// \param[out] d + /// If this function succeeded, then this will be a dictionary that + /// contains the keys "type", "symfile", and "separate-debug-info-files". + /// "type" can be used to assume the structure of each object in + /// "separate-debug-info-files". + virtual bool GetSeparateDebugInfo(StructuredData::Dictionary &d) { + return false; + }; + virtual lldb::TypeSP MakeType(lldb::user_id_t uid, ConstString name, std::optional byte_size, SymbolContextScope *context, diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 33330ef0926d61..0c378b069086d0 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -52,6 +52,7 @@ #include "lldb/Utility/FileSpec.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/State.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" #include "lldb/lldb-enumerations.h" #include "lldb/lldb-private-enumerations.h" @@ -61,6 +62,7 @@ #include "clang/Frontend/CompilerInvocation.h" #include "clang/Frontend/FrontendActions.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatAdapters.h" @@ -1462,6 +1464,87 @@ static bool DumpModuleSymbolFile(Stream &strm, Module *module) { return false; } +static bool GetSeparateDebugInfoList(StructuredData::Array &list, + Module *module) { + if (module) { + if (SymbolFile *symbol_file = module->GetSymbolFile(/*can_create=*/true)) { + StructuredData::Dictionary d; + if (symbol_file->GetSeparateDebugInfo(d)) { + list.AddItem( + std::make_shared(std::move(d))); + return true; + } + } + } + return false; +} + +static void DumpDwoFilesTable(Stream &strm, + StructuredData::Array &dwo_listings) { + strm.PutCString("Dwo ID Err Dwo Path"); + strm.EOL(); + strm.PutCString( + "------------------ --- -----------------------------------------"); + strm.EOL(); + dwo_listings.ForEach([&strm](StructuredData::Object *dwo) { + StructuredData::Dictionary *dict = dwo->GetAsDictionary(); + if (!dict) + return false; + + uint64_t dwo_id; + if (dict->GetValueForKeyAsInteger("dwo_id", dwo_id)) + strm.Printf("0x%16.16" PRIx64 " ", dwo_id); + else + strm.Printf("0x???????????????? "); + + llvm::StringRef error; + if (dict->GetValueForKeyAsString("error", error)) + strm << "E " << error; + else { + llvm::StringRef resolved_dwo_path; + if (dict->GetValueForKeyAsString("resolved_dwo_path", + resolved_dwo_path)) { + strm << " " << resolved_dwo_path; + if (resolved_dwo_path.ends_with(".dwp")) { + llvm::StringRef dwo_name; + if (dict->GetValueForKeyAsString("dwo_name", dwo_name)) + strm << "(" << dwo_name << ")"; + } + } + } + strm.EOL(); + return true; + }); +} + +static void DumpOsoFilesTable(Stream &strm, + StructuredData::Array &oso_listings) { + strm.PutCString("Mod Time Err Oso Path"); + strm.EOL(); + strm.PutCString("------------------ --- ---------------------"); + strm.EOL(); + oso_listings.ForEach([&strm](StructuredData::Object *oso) { + StructuredData::Dictionary *dict = oso->GetAsDictionary(); + if (!dict) + return false; + + uint32_t oso_mod_time; + if (dict->GetValueForKeyAsInteger("oso_mod_time", oso_mod_time)) + strm.Printf("0x%16.16" PRIx32 " ", oso_mod_time); + + llvm::StringRef error; + if (dict->GetValueForKeyAsString("error", error)) + strm << "E " << error; + else { + llvm::StringRef oso_path; + if (dict->GetValueForKeyAsString("oso_path", oso_path)) + strm << " " << oso_path; + } + strm.EOL(); + return true; + }); +} + static void DumpAddress(ExecutionContextScope *exe_scope, const Address &so_addr, bool verbose, bool all_ranges, Stream &strm) { @@ -2462,6 +2545,176 @@ class CommandObjectTargetModulesDumpLineTable CommandOptions m_options; }; +#pragma mark CommandObjectTargetModulesDumpSeparateDebugInfoFiles +#define LLDB_OPTIONS_target_modules_dump_separate_debug_info +#include "CommandOptions.inc" + +// Image debug separate debug info dumping command + +class CommandObjectTargetModulesDumpSeparateDebugInfoFiles + : public CommandObjectTargetModulesModuleAutoComplete { +public: + CommandObjectTargetModulesDumpSeparateDebugInfoFiles( + CommandInterpreter &interpreter) + : CommandObjectTargetModulesModuleAutoComplete( + interpreter, "target modules dump separate-debug-info", + "List the separate debug info symbol files for one or more target " + "modules.", + nullptr, eCommandRequiresTarget) {} + + ~CommandObjectTargetModulesDumpSeparateDebugInfoFiles() override = default; + + Options *GetOptions() override { return &m_options; } + + 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 = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'j': + m_json.SetCurrentValue(true); + m_json.SetOptionWasSet(); + break; + + default: + llvm_unreachable("Unimplemented option"); + } + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_json.Clear(); + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::ArrayRef(g_target_modules_dump_separate_debug_info_options); + } + + OptionValueBoolean m_json = false; + }; + +protected: + bool DoExecute(Args &command, CommandReturnObject &result) override { + Target &target = GetSelectedTarget(); + uint32_t num_dumped = 0; + + uint32_t addr_byte_size = target.GetArchitecture().GetAddressByteSize(); + result.GetOutputStream().SetAddressByteSize(addr_byte_size); + result.GetErrorStream().SetAddressByteSize(addr_byte_size); + + StructuredData::Array separate_debug_info_lists_by_module; + if (command.GetArgumentCount() == 0) { + // Dump all sections for all modules images + const ModuleList &target_modules = target.GetImages(); + std::lock_guard guard(target_modules.GetMutex()); + const size_t num_modules = target_modules.GetSize(); + if (num_modules == 0) { + result.AppendError("the target has no associated executable images"); + return false; + } + for (ModuleSP module_sp : target_modules.ModulesNoLocking()) { + if (INTERRUPT_REQUESTED( + GetDebugger(), + "Interrupted in dumping all " + "separate debug info with {0} of {1} modules dumped", + num_dumped, num_modules)) + break; + + if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module, + module_sp.get())) + num_dumped++; + } + } else { + // Dump specified images (by basename or fullpath) + const char *arg_cstr; + for (int arg_idx = 0; + (arg_cstr = command.GetArgumentAtIndex(arg_idx)) != nullptr; + ++arg_idx) { + ModuleList module_list; + const size_t num_matches = + FindModulesByName(&target, arg_cstr, module_list, true); + if (num_matches > 0) { + for (size_t i = 0; i < num_matches; ++i) { + if (INTERRUPT_REQUESTED(GetDebugger(), + "Interrupted dumping {0} " + "of {1} requested modules", + i, num_matches)) + break; + Module *module = module_list.GetModulePointerAtIndex(i); + if (GetSeparateDebugInfoList(separate_debug_info_lists_by_module, + module)) + num_dumped++; + } + } else + result.AppendWarningWithFormat( + "Unable to find an image that matches '%s'.\n", arg_cstr); + } + } + + if (num_dumped > 0) { + Stream &strm = result.GetOutputStream(); + if (m_options.m_json) { + separate_debug_info_lists_by_module.Dump(strm, + /*pretty_print=*/true); + } else { + // List the debug info files in human readable form. + separate_debug_info_lists_by_module.ForEach( + [&result, &strm](StructuredData::Object *obj) { + if (!obj) { + return false; + } + + // Each item in `separate_debug_info_lists_by_module` should be a + // valid structured data dictionary. + StructuredData::Dictionary *separate_debug_info_list = + obj->GetAsDictionary(); + if (!separate_debug_info_list) { + return false; + } + + llvm::StringRef type; + llvm::StringRef symfile; + StructuredData::Array *files; + assert(separate_debug_info_list->GetValueForKeyAsString("type", + type)); + assert(separate_debug_info_list->GetValueForKeyAsString("symfile", + symfile)); + assert(separate_debug_info_list->GetValueForKeyAsArray( + "separate-debug-info-files", files)); + + strm << "Symbol file: " << symfile; + strm.EOL(); + strm << "Type: \"" << type << "\""; + strm.EOL(); + if (type == "dwo") { + DumpDwoFilesTable(strm, *files); + } else if (type == "oso") { + DumpOsoFilesTable(strm, *files); + } else { + result.AppendWarningWithFormat( + "Found unsupported debug info type '%s'.\n", + type.str().c_str()); + } + return true; + }); + } + result.SetStatus(eReturnStatusSuccessFinishResult); + } else { + result.AppendError("no matching executable images found"); + } + return result.Succeeded(); + } + + CommandOptions m_options; +}; + #pragma mark CommandObjectTargetModulesDump // Dump multi-word command for target modules @@ -2475,7 +2728,8 @@ class CommandObjectTargetModulesDump : public CommandObjectMultiword { "Commands for dumping information about one or more target " "modules.", "target modules dump " - "[objfile|symtab|sections|ast|symfile|line-table|pcm-info] " + "[objfile|symtab|sections|ast|symfile|line-table|pcm-info|separate-" + "debug-info] " "[ ...]") { LoadSubCommand("objfile", CommandObjectSP( @@ -2499,6 +2753,10 @@ class CommandObjectTargetModulesDump : public CommandObjectMultiword { "pcm-info", CommandObjectSP( new CommandObjectTargetModulesDumpClangPCMInfo(interpreter))); + LoadSubCommand("separate-debug-info", + CommandObjectSP( + new CommandObjectTargetModulesDumpSeparateDebugInfoFiles( + interpreter))); } ~CommandObjectTargetModulesDump() override = default; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index 7af20e93a16d43..078b23e09e4fa8 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -8,6 +8,11 @@ let Command = "target modules dump symtab" in { Desc<"Do not demangle symbol names before showing them.">; } +let Command = "target modules dump separate debug info" in { + def tm_json : Option<"json", "j">, Group<1>, + Desc<"Output the details in JSON format.">; +} + let Command = "help" in { def help_hide_aliases : Option<"hide-aliases", "a">, Desc<"Hide aliases in the command list.">; diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp index e472074545a6f0..f52a095bf16752 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp @@ -10,6 +10,7 @@ #include "llvm/DebugInfo/DWARF/DWARFDebugLoc.h" #include "llvm/Support/Casting.h" +#include "llvm/Support/Format.h" #include "llvm/Support/Threading.h" #include "lldb/Core/Module.h" @@ -24,6 +25,7 @@ #include "lldb/Utility/RegularExpression.h" #include "lldb/Utility/Scalar.h" #include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" #include "Plugins/ExpressionParser/Clang/ClangModulesDeclVendor.h" @@ -1752,11 +1754,10 @@ SymbolFileDWARF::GetDwoSymbolFileForCompileUnit( // it. Or it's absolute. found = FileSystem::Instance().Exists(dwo_file); + const char *comp_dir = + cu_die.GetAttributeValueAsString(dwarf_cu, DW_AT_comp_dir, nullptr); if (!found) { // It could be a relative path that also uses DW_AT_COMP_DIR. - const char *comp_dir = - cu_die.GetAttributeValueAsString(dwarf_cu, DW_AT_comp_dir, nullptr); - if (comp_dir) { dwo_file.SetFile(comp_dir, FileSpec::Style::native); if (!dwo_file.IsRelative()) { @@ -4226,6 +4227,70 @@ void SymbolFileDWARF::DumpClangAST(Stream &s) { clang->Dump(s.AsRawOstream()); } +bool SymbolFileDWARF::GetSeparateDebugInfo(StructuredData::Dictionary &d) { + StructuredData::Array separate_debug_info_files; + DWARFDebugInfo &info = DebugInfo(); + const size_t num_cus = info.GetNumUnits(); + for (size_t cu_idx = 0; cu_idx < num_cus; cu_idx++) { + DWARFUnit *unit = info.GetUnitAtIndex(cu_idx); + DWARFCompileUnit *dwarf_cu = llvm::dyn_cast(unit); + if (dwarf_cu == nullptr) + continue; + + // Check if this is a DWO unit by checking if it has a DWO ID. + // NOTE: it seems that `DWARFUnit::IsDWOUnit` is always false? + if (!dwarf_cu->GetDWOId().has_value()) + continue; + + StructuredData::DictionarySP dwo_data = + std::make_shared(); + const uint64_t dwo_id = dwarf_cu->GetDWOId().value(); + dwo_data->AddIntegerItem("dwo_id", dwo_id); + + if (const DWARFBaseDIE die = dwarf_cu->GetUnitDIEOnly()) { + const char *dwo_name = GetDWOName(*dwarf_cu, *die.GetDIE()); + if (dwo_name) { + dwo_data->AddStringItem("dwo_name", dwo_name); + } else { + dwo_data->AddStringItem("error", "missing dwo name"); + } + + const char *comp_dir = die.GetDIE()->GetAttributeValueAsString( + dwarf_cu, DW_AT_comp_dir, nullptr); + if (comp_dir) { + dwo_data->AddStringItem("comp_dir", comp_dir); + } + } else { + dwo_data->AddStringItem( + "error", + llvm::formatv("unable to get unit DIE for DWARFUnit at {0:x}", + dwarf_cu->GetOffset()) + .str()); + } + + // If we have a DWO symbol file, that means we were able to successfully + // load it. + SymbolFile *dwo_symfile = dwarf_cu->GetDwoSymbolFile(); + if (dwo_symfile) { + dwo_data->AddStringItem( + "resolved_dwo_path", + dwo_symfile->GetObjectFile()->GetFileSpec().GetPath()); + } else { + dwo_data->AddStringItem("error", + dwarf_cu->GetDwoError().AsCString("unknown")); + } + dwo_data->AddBooleanItem("loaded", dwo_symfile != nullptr); + separate_debug_info_files.AddItem(dwo_data); + } + + d.AddStringItem("type", "dwo"); + d.AddStringItem("symfile", GetMainObjectFile()->GetFileSpec().GetPath()); + d.AddItem("separate-debug-info-files", + std::make_shared( + std::move(separate_debug_info_files))); + return true; +} + SymbolFileDWARFDebugMap *SymbolFileDWARF::GetDebugMapSymfile() { if (m_debug_map_symfile == nullptr) { lldb::ModuleSP module_sp(m_debug_map_module_wp.lock()); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h index 5aaf8bd270ef7b..a32c0609d3fdbf 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h @@ -30,6 +30,7 @@ #include "lldb/Utility/ConstString.h" #include "lldb/Utility/Flags.h" #include "lldb/Utility/RangeMap.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/lldb-private.h" #include "DWARFContext.h" @@ -285,6 +286,10 @@ class SymbolFileDWARF : public lldb_private::SymbolFileCommon { void DumpClangAST(lldb_private::Stream &s) override; + /// List separate dwo files. + bool + GetSeparateDebugInfo(lldb_private::StructuredData::Dictionary &d) override; + lldb_private::DWARFContext &GetDWARFContext() { return m_context; } const std::shared_ptr &GetDwpSymbolFile(); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp index eadedd32e1a4aa..4e194939814b68 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp @@ -18,8 +18,9 @@ #include "lldb/Host/FileSystem.h" #include "lldb/Utility/RangeMap.h" #include "lldb/Utility/RegularExpression.h" -#include "lldb/Utility/Timer.h" #include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StructuredData.h" +#include "lldb/Utility/Timer.h" //#define DEBUG_OSO_DMAP // DO NOT CHECKIN WITH THIS NOT COMMENTED OUT @@ -1271,6 +1272,42 @@ void SymbolFileDWARFDebugMap::DumpClangAST(Stream &s) { }); } +bool SymbolFileDWARFDebugMap::GetSeparateDebugInfo( + lldb_private::StructuredData::Dictionary &d) { + StructuredData::Array separate_debug_info_files; + const uint32_t cu_count = GetNumCompileUnits(); + for (uint32_t cu_idx = 0; cu_idx < cu_count; ++cu_idx) { + const auto &info = m_compile_unit_infos[cu_idx]; + StructuredData::DictionarySP oso_data = + std::make_shared(); + oso_data->AddStringItem("so_file", info.so_file.GetPath()); + oso_data->AddStringItem("oso_path", info.oso_path); + oso_data->AddIntegerItem("oso_mod_time", + (uint32_t)llvm::sys::toTimeT(info.oso_mod_time)); + + bool loaded_successfully = false; + if (GetModuleByOSOIndex(cu_idx)) { + // If we have a valid pointer to the module, we successfully + // loaded the oso if there are no load errors. + if (!info.oso_load_error.Fail()) { + loaded_successfully = true; + } + } + if (!loaded_successfully) { + oso_data->AddStringItem("error", info.oso_load_error.AsCString()); + } + oso_data->AddBooleanItem("loaded", loaded_successfully); + separate_debug_info_files.AddItem(oso_data); + } + + d.AddStringItem("type", "oso"); + d.AddStringItem("symfile", GetMainObjectFile()->GetFileSpec().GetPath()); + d.AddItem("separate-debug-info-files", + std::make_shared( + std::move(separate_debug_info_files))); + return true; +} + lldb::CompUnitSP SymbolFileDWARFDebugMap::GetCompileUnit(SymbolFileDWARF *oso_dwarf, DWARFCompileUnit &dwarf_cu) { if (oso_dwarf) { diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h index 881fd4c45ff05a..0dc4235cf090f7 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h @@ -19,6 +19,7 @@ #include #include "UniqueDWARFASTType.h" +#include "lldb/Utility/StructuredData.h" class SymbolFileDWARF; class DWARFCompileUnit; @@ -148,6 +149,10 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFileCommon { void DumpClangAST(lldb_private::Stream &s) override; + /// List separate oso files. + bool + GetSeparateDebugInfo(lldb_private::StructuredData::Dictionary &d) override; + // PluginInterface protocol llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } diff --git a/lldb/source/Symbol/SymbolFile.cpp b/lldb/source/Symbol/SymbolFile.cpp index b271efd07bfe36..7dcee8ced0ea11 100644 --- a/lldb/source/Symbol/SymbolFile.cpp +++ b/lldb/source/Symbol/SymbolFile.cpp @@ -18,6 +18,7 @@ #include "lldb/Symbol/VariableList.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/StreamString.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/lldb-private.h" #include diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/dwo/Makefile b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/Makefile new file mode 100644 index 00000000000000..3b6d788b2b0130 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/Makefile @@ -0,0 +1,4 @@ +CXX_SOURCES := main.cpp foo.cpp +CFLAGS_EXTRAS := -gsplit-dwarf + +include Makefile.rules diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/dwo/TestDumpDwo.py b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/TestDumpDwo.py new file mode 100644 index 00000000000000..c58ffdefb4587b --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/TestDumpDwo.py @@ -0,0 +1,122 @@ +""" +Test 'target modules dump separate-debug-info' for dwo files. +""" + +import json +import os + +from lldbsuite.test import lldbtest, lldbutil +from lldbsuite.test.decorators import * + + +class TestDumpDWO(lldbtest.TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def get_dwos_from_json(self): + """Returns a dictionary of `symfile` -> {`dwo_name` -> dwo_info object}.""" + result = {} + output = json.loads(self.res.GetOutput()) + for symfile_entry in output: + dwo_dict = {} + for dwo_entry in symfile_entry["separate-debug-info-files"]: + dwo_dict[dwo_entry["dwo_name"]] = dwo_entry + result[symfile_entry["symfile"]] = dwo_dict + return result + + @skipIfRemote + @skipIfDarwin + def test_dwos_loaded_json_output(self): + self.build() + exe = self.getBuildArtifact("a.out") + main_dwo = self.getBuildArtifact("main.dwo") + foo_dwo = self.getBuildArtifact("foo.dwo") + + # Make sure dwo files exist + self.assertTrue(os.path.exists(main_dwo), f'Make sure "{main_dwo}" file exists') + self.assertTrue(os.path.exists(foo_dwo), f'Make sure "{foo_dwo}" file exists') + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.runCmd("target modules dump separate-debug-info --json") + + # Check the output + output = self.get_dwos_from_json() + self.assertTrue(output[exe]["main.dwo"]["loaded"]) + self.assertTrue(output[exe]["foo.dwo"]["loaded"]) + + @skipIfRemote + @skipIfDarwin + def test_dwos_not_loaded_json_output(self): + self.build() + exe = self.getBuildArtifact("a.out") + main_dwo = self.getBuildArtifact("main.dwo") + foo_dwo = self.getBuildArtifact("foo.dwo") + + # REMOVE the dwo files + os.unlink(main_dwo) + os.unlink(foo_dwo) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.runCmd("target modules dump separate-debug-info --json") + + # Check the output + output = self.get_dwos_from_json() + self.assertFalse(output[exe]["main.dwo"]["loaded"]) + self.assertFalse(output[exe]["foo.dwo"]["loaded"]) + self.assertIn("error", output[exe]["main.dwo"]) + self.assertIn("error", output[exe]["foo.dwo"]) + + @skipIfRemote + @skipIfDarwin + def test_dwos_loaded_table_output(self): + self.build() + exe = self.getBuildArtifact("a.out") + main_dwo = self.getBuildArtifact("main.dwo") + foo_dwo = self.getBuildArtifact("foo.dwo") + + # Make sure dwo files exist + self.assertTrue(os.path.exists(main_dwo), f'Make sure "{main_dwo}" file exists') + self.assertTrue(os.path.exists(foo_dwo), f'Make sure "{foo_dwo}" file exists') + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.expect( + "target modules dump separate-debug-info", + patterns=[ + "Symbol file: .*?a\.out", + 'Type: "dwo"', + "Dwo ID\s+Err\s+Dwo Path", + "0x[a-zA-Z0-9]{16}\s+.*main\.dwo", + "0x[a-zA-Z0-9]{16}\s+.*foo\.dwo", + ], + ) + + @skipIfRemote + @skipIfDarwin + def test_dwos_not_loaded_table_output(self): + self.build() + exe = self.getBuildArtifact("a.out") + main_dwo = self.getBuildArtifact("main.dwo") + foo_dwo = self.getBuildArtifact("foo.dwo") + + # REMOVE the dwo files + os.unlink(main_dwo) + os.unlink(foo_dwo) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.expect( + "target modules dump separate-debug-info", + patterns=[ + "Symbol file: .*?a\.out", + 'Type: "dwo"', + "Dwo ID\s+Err\s+Dwo Path", + "0x[a-zA-Z0-9]{16}\s+E\s+.*main\.dwo", + "0x[a-zA-Z0-9]{16}\s+E\s+.*foo\.dwo", + ], + ) diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.cpp b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.cpp new file mode 100644 index 00000000000000..28e2b6e768df4e --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.cpp @@ -0,0 +1,3 @@ +#include "foo.h" + +int foo() { return 1; } diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.h b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.h new file mode 100644 index 00000000000000..4ec598ad513eb9 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/foo.h @@ -0,0 +1,6 @@ +#ifndef FOO_H +#define FOO_H + +int foo(); + +#endif diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/dwo/main.cpp b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/main.cpp new file mode 100644 index 00000000000000..8087e682432798 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/main.cpp @@ -0,0 +1,3 @@ +#include "foo.h" + +int main() { return foo(); } diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/oso/Makefile b/lldb/test/API/commands/target/dump-separate-debug-info/oso/Makefile new file mode 100644 index 00000000000000..7df22699c57d57 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp foo.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py b/lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py new file mode 100644 index 00000000000000..05beed0eacfb00 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py @@ -0,0 +1,120 @@ +""" +Test 'target modules dump separate-debug-info' for oso files. +""" + +import json +import os + +from lldbsuite.test import lldbtest, lldbutil +from lldbsuite.test.decorators import * + + +class TestDumpOso(lldbtest.TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def get_osos_from_json(self): + """Returns a dictionary of `symfile` -> {`OSO_PATH` -> oso_info object}.""" + result = {} + output = json.loads(self.res.GetOutput()) + for symfile_entry in output: + oso_dict = {} + for oso_entry in symfile_entry["separate-debug-info-files"]: + oso_dict[oso_entry["oso_path"]] = oso_entry + result[symfile_entry["symfile"]] = oso_dict + return result + + @skipIfRemote + @skipUnlessDarwin + def test_shows_oso_loaded_json_output(self): + self.build(debug_info="dwarf") + exe = self.getBuildArtifact("a.out") + main_o = self.getBuildArtifact("main.o") + foo_o = self.getBuildArtifact("foo.o") + + # Make sure o files exist + self.assertTrue(os.path.exists(main_o), f'Make sure "{main_o}" file exists') + self.assertTrue(os.path.exists(foo_o), f'Make sure "{foo_o}" file exists') + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.runCmd("target modules dump separate-debug-info --json") + + # Check the output + osos = self.get_osos_from_json() + self.assertTrue(osos[exe][main_o]["loaded"]) + self.assertTrue(osos[exe][foo_o]["loaded"]) + + @skipIfRemote + @skipUnlessDarwin + def test_shows_oso_not_loaded_json_output(self): + self.build(debug_info="dwarf") + exe = self.getBuildArtifact("a.out") + main_o = self.getBuildArtifact("main.o") + foo_o = self.getBuildArtifact("foo.o") + + # REMOVE the o files + os.unlink(main_o) + os.unlink(foo_o) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.runCmd("target modules dump separate-debug-info --json") + + # Check the output + osos = self.get_osos_from_json() + self.assertFalse(osos[exe][main_o]["loaded"]) + self.assertFalse(osos[exe][foo_o]["loaded"]) + + @skipIfRemote + @skipUnlessDarwin + def test_shows_oso_loaded_table_output(self): + self.build(debug_info="dwarf") + exe = self.getBuildArtifact("a.out") + main_o = self.getBuildArtifact("main.o") + foo_o = self.getBuildArtifact("foo.o") + + # Make sure o files exist + self.assertTrue(os.path.exists(main_o), f'Make sure "{main_o}" file exists') + self.assertTrue(os.path.exists(foo_o), f'Make sure "{foo_o}" file exists') + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.expect( + "target modules dump separate-debug-info", + patterns=[ + "Symbol file: .*?a\.out", + 'Type: "oso"', + "Mod Time\s+Err\s+Oso Path", + "0x[a-zA-Z0-9]{16}\s+.*main\.o", + "0x[a-zA-Z0-9]{16}\s+.*foo\.o", + ], + ) + + @skipIfRemote + @skipUnlessDarwin + def test_shows_oso_not_loaded_table_output(self): + self.build(debug_info="dwarf") + exe = self.getBuildArtifact("a.out") + main_o = self.getBuildArtifact("main.o") + foo_o = self.getBuildArtifact("foo.o") + + # REMOVE the o files + os.unlink(main_o) + os.unlink(foo_o) + + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + self.expect( + "target modules dump separate-debug-info", + patterns=[ + "Symbol file: .*?a\.out", + 'Type: "oso"', + "Mod Time\s+Err\s+Oso Path", + "0x[a-zA-Z0-9]{16}\s+E\s+.*main\.o", + "0x[a-zA-Z0-9]{16}\s+E\s+.*foo\.o", + ], + ) diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.cpp b/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.cpp new file mode 100644 index 00000000000000..28e2b6e768df4e --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.cpp @@ -0,0 +1,3 @@ +#include "foo.h" + +int foo() { return 1; } diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.h b/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.h new file mode 100644 index 00000000000000..4ec598ad513eb9 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/foo.h @@ -0,0 +1,6 @@ +#ifndef FOO_H +#define FOO_H + +int foo(); + +#endif diff --git a/lldb/test/API/commands/target/dump-separate-debug-info/oso/main.cpp b/lldb/test/API/commands/target/dump-separate-debug-info/oso/main.cpp new file mode 100644 index 00000000000000..8087e682432798 --- /dev/null +++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/main.cpp @@ -0,0 +1,3 @@ +#include "foo.h" + +int main() { return foo(); }