https://github.com/zhyty updated https://github.com/llvm/llvm-project/pull/66035
>From 9a52ac5193af2a8ddca2a5d922684935b043d0ef Mon Sep 17 00:00:00 2001 From: Tom Yang <toy...@fb.com> Date: Mon, 11 Sep 2023 17:17:13 -0700 Subject: [PATCH 1/2] Add `target modules dump separate-debug-info` Summary: Add a new command ``` target modules dump separate-debug-info [-j] [<filename> [<filename> [...]]] ``` or ``` image dump separate-debug-info [-j] [<filename> [<filename> [...]]] ``` (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 Dwo Path ------------------ ----------------------------------------- 0x9a429da5abb6faae /home/toyang/workspace/dwo-scratch/a-main.dwo 0xbcc129959e76ff33 /home/toyang/workspace/dwo-scratch/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 Dwo Path ------------------ ----------------------------------------- 0x9a429da5abb6faae error: unable to locate .dwo debug file "/home/toyang/workspace/dwo-scratch/a-main.dwo" for skeleton DIE 0x0000000000000014 0xbcc129959e76ff33 error: unable to locate .dwo debug file "/home/toyang/workspace/dwo-scratch/a-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 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 (after manipulating the mod times with `touch`): ``` (lldb) image dump separate-debug-info Symbol file: /Users/toyang/workspace/scratch/a.out Type: "oso" Mod Time 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 ``` Reviewers: Subscribers: Tasks: Tags: --- 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 8de752816cf94ee..512dd9acb86db61 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<uint64_t> byte_size, SymbolContextScope *context, diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 33330ef0926d61f..82b83a142c6000c 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(true)) { + StructuredData::Dictionary d; + if (symbol_file->GetSeparateDebugInfo(d)) { + list.AddItem( + std::make_shared<StructuredData::Dictionary>(std::move(d))); + return true; + } + } + } + return false; +} + +static void DumpDwoFilesTable(Stream &strm, + StructuredData::Array &dwo_listings) { + strm.PutCString("Dwo ID 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 << "error: " << 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 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 << "error: " << 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<OptionDefinition> 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<std::recursive_mutex> 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] " "[<file1> <file2> ...]") { 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 04830b8b990efae..ce00d81db3c5efb 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 04c729e333a9854..2c8e71eb012f21e 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" @@ -1740,11 +1742,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()) { @@ -4214,6 +4215,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<DWARFCompileUnit>(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<StructuredData::Dictionary>(); + 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<StructuredData::Array>( + 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 191a5abcf265abd..f75d1ebac9cb8f1 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" @@ -282,6 +283,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<SymbolFileDWARFDwo> &GetDwpSymbolFile(); diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp index eadedd32e1a4aaf..4e194939814b686 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<StructuredData::Dictionary>(); + 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<StructuredData::Array>( + 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 881fd4c45ff05a0..0dc4235cf090f7b 100644 --- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h +++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h @@ -19,6 +19,7 @@ #include <vector> #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 b271efd07bfe36f..7dcee8ced0ea11b 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 <future> 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 000000000000000..3b6d788b2b0130a --- /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 000000000000000..7e19f5208842418 --- /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+Dwo Path", + "0x[a-zA-Z0-9]{16}.*main\.dwo", + "0x[a-zA-Z0-9]{16}.*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+Dwo Path", + "0x[a-zA-Z0-9]{16}.*error:.*main\.dwo", + "0x[a-zA-Z0-9]{16}.*error:.*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 000000000000000..28e2b6e768df4e7 --- /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 000000000000000..4ec598ad513eb91 --- /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 000000000000000..8087e682432798b --- /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 000000000000000..7df22699c57d573 --- /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 000000000000000..5d1c7d182264ced --- /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+Oso Path", + "0x[a-zA-Z0-9]{16}.*main\.o", + "0x[a-zA-Z0-9]{16}.*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+Oso Path", + "0x[a-zA-Z0-9]{16}.*error:.*main\.o", + "0x[a-zA-Z0-9]{16}.*error:.*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 000000000000000..28e2b6e768df4e7 --- /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 000000000000000..4ec598ad513eb91 --- /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 000000000000000..8087e682432798b --- /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(); } >From ba71d3f2ac1bd9e3f393b195c9158d5b5bc8419b Mon Sep 17 00:00:00 2001 From: Tom Yang <toy...@fb.com> Date: Mon, 18 Sep 2023 10:26:40 -0700 Subject: [PATCH 2/2] Add "err" column --- lldb/source/Commands/CommandObjectTarget.cpp | 16 ++++++++-------- .../dump-separate-debug-info/dwo/TestDumpDwo.py | 12 ++++++------ .../dump-separate-debug-info/oso/TestDumpOso.py | 12 ++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp index 82b83a142c6000c..e234e50837e1295 100644 --- a/lldb/source/Commands/CommandObjectTarget.cpp +++ b/lldb/source/Commands/CommandObjectTarget.cpp @@ -1481,10 +1481,10 @@ static bool GetSeparateDebugInfoList(StructuredData::Array &list, static void DumpDwoFilesTable(Stream &strm, StructuredData::Array &dwo_listings) { - strm.PutCString("Dwo ID Dwo Path"); + 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(); @@ -1499,12 +1499,12 @@ static void DumpDwoFilesTable(Stream &strm, llvm::StringRef error; if (dict->GetValueForKeyAsString("error", error)) - strm << "error: " << error; + strm << "E " << error; else { llvm::StringRef resolved_dwo_path; if (dict->GetValueForKeyAsString("resolved_dwo_path", resolved_dwo_path)) { - strm << 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)) @@ -1519,9 +1519,9 @@ static void DumpDwoFilesTable(Stream &strm, static void DumpOsoFilesTable(Stream &strm, StructuredData::Array &oso_listings) { - strm.PutCString("Mod Time Oso Path"); + strm.PutCString("Mod Time Err Oso Path"); strm.EOL(); - strm.PutCString("------------------ ---------------------"); + strm.PutCString("------------------ --- ---------------------"); strm.EOL(); oso_listings.ForEach([&strm](StructuredData::Object *oso) { StructuredData::Dictionary *dict = oso->GetAsDictionary(); @@ -1534,11 +1534,11 @@ static void DumpOsoFilesTable(Stream &strm, llvm::StringRef error; if (dict->GetValueForKeyAsString("error", error)) - strm << "error: " << error; + strm << "E " << error; else { llvm::StringRef oso_path; if (dict->GetValueForKeyAsString("oso_path", oso_path)) - strm << oso_path; + strm << " " << oso_path; } strm.EOL(); return true; 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 index 7e19f5208842418..c58ffdefb4587bd 100644 --- 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 @@ -89,9 +89,9 @@ def test_dwos_loaded_table_output(self): patterns=[ "Symbol file: .*?a\.out", 'Type: "dwo"', - "Dwo ID\s+Dwo Path", - "0x[a-zA-Z0-9]{16}.*main\.dwo", - "0x[a-zA-Z0-9]{16}.*foo\.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", ], ) @@ -115,8 +115,8 @@ def test_dwos_not_loaded_table_output(self): patterns=[ "Symbol file: .*?a\.out", 'Type: "dwo"', - "Dwo ID\s+Dwo Path", - "0x[a-zA-Z0-9]{16}.*error:.*main\.dwo", - "0x[a-zA-Z0-9]{16}.*error:.*foo\.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/oso/TestDumpOso.py b/lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py index 5d1c7d182264ced..05beed0eacfb00b 100644 --- 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 @@ -87,9 +87,9 @@ def test_shows_oso_loaded_table_output(self): patterns=[ "Symbol file: .*?a\.out", 'Type: "oso"', - "Mod Time\s+Oso Path", - "0x[a-zA-Z0-9]{16}.*main\.o", - "0x[a-zA-Z0-9]{16}.*foo\.o", + "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", ], ) @@ -113,8 +113,8 @@ def test_shows_oso_not_loaded_table_output(self): patterns=[ "Symbol file: .*?a\.out", 'Type: "oso"', - "Mod Time\s+Oso Path", - "0x[a-zA-Z0-9]{16}.*error:.*main\.o", - "0x[a-zA-Z0-9]{16}.*error:.*foo\.o", + "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", ], ) _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits