[Lldb-commits] [lldb] Add `target modules dump separate-debug-info` (PR #66035)

Tom Yang via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 11 17:31:17 PDT 2023


https://github.com/zhyty updated https://github.com/llvm/llvm-project/pull/66035:

>From 0f4cf3648bd1a8d6e9114965e6eb6cdbc7ed01dd Mon Sep 17 00:00:00 2001
From: Tom Yang <toyang at fb.com>
Date: Mon, 11 Sep 2023 17:17:13 -0700
Subject: [PATCH] Add `target modules dump separate-debug-info`

Summary:

Add a new command
```
target modules dump separate-debug-info [<file1> ...]
```
or
```
image dump separate-debug-info [<file1> ...]
```
(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
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": ".",
        "dwo_id": 7516252579671439727,
        "dwo_name": "a-main.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwo-scratch/a-main.dwo"
      },
      {
        "comp_dir": ".",
        "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"
  }
]
```

Example dwo with missing dwo:
```
warning: (x86_64) /home/toyang/workspace/dwp/a.out unable to locate separate debug file (dwo, dwp). Debugging will be degraded. (troubleshoot with https://fburl.com/missing_dwo)
Current executable set to '/home/toyang/workspace/dwp/a.out' (x86_64).
(lldb) image dump separate-debug-info
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": "/home/toyang/workspace/dwp",
        "dwo_id": 11115620165179865774,
        "dwo_name": "a-main.dwo",
        "loaded": false
      },
      {
        "comp_dir": "/home/toyang/workspace/dwp",
        "dwo_id": 13601198072221073203,
        "dwo_name": "a-foo.dwo",
        "loaded": false
      }
    ],
    "symfile": "/home/toyang/workspace/dwp/a.out"
  }
]
```

Example output with dwp:
```
(lldb) image dump separate-debug-info
[
  {
    "separate-debug-info-files": [
      {
        "comp_dir": "/home/toyang/workspace/dwp",
        "dwo_id": 11115620165179865774,
        "dwo_name": "a-main.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwp/a.out.dwp"
      },
      {
        "comp_dir": "/home/toyang/workspace/dwp",
        "dwo_id": 13601198072221073203,
        "dwo_name": "a-foo.dwo",
        "loaded": true,
        "resolved_dwo_path": "/home/toyang/workspace/dwp/a.out.dwp"
      }
    ],
    "symfile": "/home/toyang/workspace/dwp/a.out"
  }
]
```

Example oso on my Mac (after manipulating the mod times with `touch`):
```
[
  {
    "separate-debug-info-files": [
      {
        "error": "debug map object file \"/Users/toyang/workspace/scratch/main.o\" changed (actual: 0x64e64868, debug map: 0x64e4fb23) since this executable was linked, debug info will not be loaded",
        "loaded": false,
        "oso_mod_time": 1692728099,
        "oso_path": "/Users/toyang/workspace/scratch/main.o",
        "so_file": "/Users/toyang/workspace/scratch/main.cpp"
      },
      {
        "error": "debug map object file \"/Users/toyang/workspace/scratch/foo.o\" changed (actual: 0x64e64868, debug map: 0x64e4fb23) since this executable was linked, debug info will not be loaded",
        "loaded": false,
        "oso_mod_time": 1692728099,
        "oso_path": "/Users/toyang/workspace/scratch/foo.o",
        "so_file": "/Users/toyang/workspace/scratch/foo.cpp"
      }
    ],
    "symfile": "/Users/toyang/workspace/scratch/a-oso.out"
  }
]
```

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         |  11 ++
 lldb/source/Commands/CommandObjectTarget.cpp  | 146 ++++++++++++++++--
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      |  58 +++++++
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |   5 +
 .../DWARF/SymbolFileDWARFDebugMap.cpp         |  33 +++-
 .../DWARF/SymbolFileDWARFDebugMap.h           |   5 +
 lldb/source/Symbol/SymbolFile.cpp             |  14 ++
 .../dump-separate-debug-info/dwo/Makefile     |   4 +
 .../dwo/TestDumpDwo.py                        |  68 ++++++++
 .../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                        |  68 ++++++++
 .../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 +
 17 files changed, 421 insertions(+), 18 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..347bfc445caa8b5 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"
@@ -377,6 +378,10 @@ class SymbolFile : public PluginInterface {
 
   virtual void Dump(Stream &s) = 0;
 
+  /// Return true if separate debug info files are supported and this function
+  /// succeeded, false otherwise.
+  bool DumpSeparateDebugInfoFiles(StructuredData::Dictionary &d);
+
   /// Metrics gathering functions
 
   /// Return the size in bytes of all debug information in the symbol file.
@@ -459,6 +464,12 @@ class SymbolFile : public PluginInterface {
   virtual void GetCompileOptions(
       std::unordered_map<lldb::CompUnitSP, lldb_private::Args> &args) {}
 
+  /// Return true if separate debug info files are supported and this function
+  /// succeeded, false otherwise.
+  virtual bool GetSeparateDebugInfoFiles(StructuredData::Array &array) {
+    return false;
+  };
+
 private:
   SymbolFile(const SymbolFile &) = delete;
   const SymbolFile &operator=(const SymbolFile &) = delete;
diff --git a/lldb/source/Commands/CommandObjectTarget.cpp b/lldb/source/Commands/CommandObjectTarget.cpp
index 33330ef0926d61f..276d85977c4ef34 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"
@@ -1462,6 +1463,21 @@ static bool DumpModuleSymbolFile(Stream &strm, Module *module) {
   return false;
 }
 
+static bool DumpModuleSeparateDebugInfoFiles(
+    StructuredData::Array &separate_debug_info_files, Module *module) {
+  if (module) {
+    if (SymbolFile *symbol_file = module->GetSymbolFile(true)) {
+      StructuredData::Dictionary d;
+      if (symbol_file->DumpSeparateDebugInfoFiles(d)) {
+        separate_debug_info_files.AddItem(
+            std::make_shared<StructuredData::Dictionary>(std::move(d)));
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
 static void DumpAddress(ExecutionContextScope *exe_scope,
                         const Address &so_addr, bool verbose, bool all_ranges,
                         Stream &strm) {
@@ -2005,9 +2021,10 @@ class CommandObjectTargetModulesDumpSymtab
             result.GetOutputStream().EOL();
             result.GetOutputStream().EOL();
           }
-          if (INTERRUPT_REQUESTED(GetDebugger(), 
+          if (INTERRUPT_REQUESTED(GetDebugger(),
                                   "Interrupted in dump all symtabs with {0} "
-                                  "of {1} dumped.", num_dumped, num_modules))
+                                  "of {1} dumped.",
+                                  num_dumped, num_modules))
             break;
 
           num_dumped++;
@@ -2035,9 +2052,10 @@ class CommandObjectTargetModulesDumpSymtab
                 result.GetOutputStream().EOL();
                 result.GetOutputStream().EOL();
               }
-              if (INTERRUPT_REQUESTED(GetDebugger(), 
-                    "Interrupted in dump symtab list with {0} of {1} dumped.", 
-                    num_dumped, num_matches))
+              if (INTERRUPT_REQUESTED(
+                      GetDebugger(),
+                      "Interrupted in dump symtab list with {0} of {1} dumped.",
+                      num_dumped, num_matches))
                 break;
 
               num_dumped++;
@@ -2099,9 +2117,10 @@ class CommandObjectTargetModulesDumpSections
       result.GetOutputStream().Format("Dumping sections for {0} modules.\n",
                                       num_modules);
       for (size_t image_idx = 0; image_idx < num_modules; ++image_idx) {
-        if (INTERRUPT_REQUESTED(GetDebugger(), 
-              "Interrupted in dump all sections with {0} of {1} dumped",
-              image_idx, num_modules))
+        if (INTERRUPT_REQUESTED(
+                GetDebugger(),
+                "Interrupted in dump all sections with {0} of {1} dumped",
+                image_idx, num_modules))
           break;
 
         num_dumped++;
@@ -2120,9 +2139,10 @@ class CommandObjectTargetModulesDumpSections
             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 in dump section list with {0} of {1} dumped.",
-                  i, num_matches))
+            if (INTERRUPT_REQUESTED(
+                    GetDebugger(),
+                    "Interrupted in dump section list with {0} of {1} dumped.",
+                    i, num_matches))
               break;
 
             Module *module = module_list.GetModulePointerAtIndex(i);
@@ -2265,9 +2285,10 @@ class CommandObjectTargetModulesDumpClangAST
       }
 
       for (size_t i = 0; i < num_matches; ++i) {
-        if (INTERRUPT_REQUESTED(GetDebugger(), 
-              "Interrupted in dump clang ast list with {0} of {1} dumped.",
-              i, num_matches))
+        if (INTERRUPT_REQUESTED(
+                GetDebugger(),
+                "Interrupted in dump clang ast list with {0} of {1} dumped.", i,
+                num_matches))
           break;
 
         Module *m = module_list.GetModulePointerAtIndex(i);
@@ -2406,10 +2427,10 @@ class CommandObjectTargetModulesDumpLineTable
         if (num_modules > 0) {
           uint32_t num_dumped = 0;
           for (ModuleSP module_sp : target_modules.ModulesNoLocking()) {
-            if (INTERRUPT_REQUESTED(GetDebugger(), 
+            if (INTERRUPT_REQUESTED(GetDebugger(),
                                     "Interrupted in dump all line tables with "
-                                    "{0} of {1} dumped", num_dumped, 
-                                    num_modules))
+                                    "{0} of {1} dumped",
+                                    num_dumped, num_modules))
               break;
 
             if (DumpCompileUnitLineTable(
@@ -2462,6 +2483,93 @@ class CommandObjectTargetModulesDumpLineTable
   CommandOptions m_options;
 };
 
+#pragma mark CommandObjectTargetModulesDumpSeparateDebugInfoFiles
+
+// Image debug dwo dumping command
+
+class CommandObjectTargetModulesDumpSeparateDebugInfoFiles
+    : public CommandObjectTargetModulesModuleAutoComplete {
+public:
+  CommandObjectTargetModulesDumpSeparateDebugInfoFiles(
+      CommandInterpreter &interpreter)
+      : CommandObjectTargetModulesModuleAutoComplete(
+            interpreter, "target modules dump separate-debug-info",
+            "Dump the separate debug info symbol files for one or more target "
+            "modules.",
+            //"target modules dump separate-debug-info [<file1> ...]")
+            nullptr, eCommandRequiresTarget) {}
+
+  ~CommandObjectTargetModulesDumpSeparateDebugInfoFiles() override = default;
+
+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_files;
+    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 (DumpModuleSeparateDebugInfoFiles(separate_debug_info_files,
+                                             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 (DumpModuleSeparateDebugInfoFiles(separate_debug_info_files,
+                                                 module))
+              num_dumped++;
+          }
+        } else
+          result.AppendWarningWithFormat(
+              "Unable to find an image that matches '%s'.\n", arg_cstr);
+      }
+    }
+
+    if (num_dumped > 0) {
+      separate_debug_info_files.Dump(result.GetOutputStream(),
+                                     /* pretty_print */ true);
+      result.SetStatus(eReturnStatusSuccessFinishResult);
+    } else {
+      result.AppendError("no matching executable images found");
+    }
+    return result.Succeeded();
+  }
+};
+
 #pragma mark CommandObjectTargetModulesDump
 
 // Dump multi-word command for target modules
@@ -2499,6 +2607,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/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 04c729e333a9854..695f6b43f9e4331 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"
@@ -4214,6 +4216,62 @@ void SymbolFileDWARF::DumpClangAST(Stream &s) {
   clang->Dump(s.AsRawOstream());
 }
 
+bool SymbolFileDWARF::GetSeparateDebugInfoFiles(StructuredData::Array &array) {
+  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());
+    }
+    dwo_data->AddBooleanItem("loaded", dwo_symfile != nullptr);
+    array.AddItem(dwo_data);
+  }
+
+  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..79bcdbbba5d5963 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;
 
+  /// Retrieve the external dwo files.
+  bool GetSeparateDebugInfoFiles(
+      lldb_private::StructuredData::Array &array) 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..dd9add83f0d4267 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,36 @@ void SymbolFileDWARFDebugMap::DumpClangAST(Stream &s) {
   });
 }
 
+bool SymbolFileDWARFDebugMap::GetSeparateDebugInfoFiles(
+    lldb_private::StructuredData::Array &array) {
+  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);
+    array.AddItem(oso_data);
+  }
+
+  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..b486e82eab73d20 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;
 
+  /// Get the external oso files.
+  bool GetSeparateDebugInfoFiles(
+      lldb_private::StructuredData::Array &array) 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..ad02a8b2c861907 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>
@@ -162,6 +163,19 @@ void SymbolFile::AssertModuleLock() {
 
 SymbolFile::RegisterInfoResolver::~RegisterInfoResolver() = default;
 
+bool SymbolFile::DumpSeparateDebugInfoFiles(StructuredData::Dictionary &d) {
+  StructuredData::Array array;
+  if (!GetSeparateDebugInfoFiles(array)) {
+    return false;
+  }
+
+  d.AddStringItem("symfile", GetMainObjectFile()->GetFileSpec().GetPath());
+  d.AddItem("separate-debug-info-files",
+            std::make_shared<StructuredData::Array>(std::move(array)));
+
+  return true;
+}
+
 Symtab *SymbolFileCommon::GetSymtab() {
   std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
   // Fetch the symtab from the main object file.
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..b74f371e3862d37
--- /dev/null
+++ b/lldb/test/API/commands/target/dump-separate-debug-info/dwo/TestDumpDwo.py
@@ -0,0 +1,68 @@
+"""
+Test 'target modules dump separate-debug-info' for dwo files.
+"""
+
+import os
+import json
+
+from lldbsuite.test import lldbtest, lldbutil
+from lldbsuite.test.decorators import *
+
+
+class TestDumpDWO(lldbtest.TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def get_dwos_from_command_output(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_shows_dwo_loaded(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")
+
+        # Check the output
+        output = self.get_dwos_from_command_output()
+        self.assertTrue(output[exe]["main.dwo"]["loaded"])
+        self.assertTrue(output[exe]["foo.dwo"]["loaded"])
+
+    @skipIfRemote
+    @skipIfDarwin
+    def test_shows_dwo_not_loaded(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")
+
+        # Check the output
+        output = self.get_dwos_from_command_output()
+        self.assertFalse(output[exe]["main.dwo"]["loaded"])
+        self.assertFalse(output[exe]["foo.dwo"]["loaded"])
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..9021f597bdf806b
--- /dev/null
+++ b/lldb/test/API/commands/target/dump-separate-debug-info/oso/TestDumpOso.py
@@ -0,0 +1,68 @@
+"""
+Test 'target modules dump separate-debug-info' for oso files.
+"""
+
+import os
+import json
+
+from lldbsuite.test import lldbtest, lldbutil
+from lldbsuite.test.decorators import *
+
+
+class TestDumpOso(lldbtest.TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def get_osos_from_command_output(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(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")
+
+        # Check the output
+        osos = self.get_osos_from_command_output()
+        self.assertTrue(osos[exe][main_o]["loaded"])
+        self.assertTrue(osos[exe][foo_o]["loaded"])
+
+    @skipIfRemote
+    @skipUnlessDarwin
+    def test_shows_oso_not_loaded(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")
+
+        # Check the output
+        osos = self.get_osos_from_command_output()
+        self.assertFalse(osos[exe][main_o]["loaded"])
+        self.assertFalse(osos[exe][foo_o]["loaded"])
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(); }



More information about the lldb-commits mailing list