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

Tom Yang via lldb-commits lldb-commits at lists.llvm.org
Thu Sep 14 12:52:48 PDT 2023


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

>From 94b834f747fe66a50288e23fec2d00918f4fc8ef 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 [-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         |  19 ++
 lldb/source/Commands/CommandObjectTarget.cpp  | 276 +++++++++++++++++-
 lldb/source/Commands/Options.td               |   5 +
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      |  66 ++++-
 .../SymbolFile/DWARF/SymbolFileDWARF.h        |   5 +
 .../DWARF/SymbolFileDWARFDebugMap.cpp         |  33 ++-
 .../DWARF/SymbolFileDWARFDebugMap.h           |   6 +
 lldb/source/Symbol/SymbolFile.cpp             |  16 +
 .../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 +
 18 files changed, 580 insertions(+), 13 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..5362ea77c10984b 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,16 @@ 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.
+  ///
+  /// \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".
+  bool ListSeparateDebugInfoFiles(StructuredData::Dictionary &d);
+
   /// Metrics gathering functions
 
   /// Return the size in bytes of all debug information in the symbol file.
@@ -459,6 +470,14 @@ class SymbolFile : public PluginInterface {
   virtual void GetCompileOptions(
       std::unordered_map<lldb::CompUnitSP, lldb_private::Args> &args) {}
 
+  /// If separate debug info files are supported and this function succeeded,
+  /// return some string representing the type of debug info. E.g. "dwo" or
+  /// "oso". Otherwise, this returns None.
+  virtual std::optional<ConstString>
+  GetSeparateDebugInfoFiles(StructuredData::Array &array) {
+    return std::nullopt;
+  };
+
 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..6549f0dc2acb1e0 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->ListSeparateDebugInfoFiles(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) {
@@ -2005,7 +2088,7 @@ 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))
             break;
@@ -2035,8 +2118,8 @@ class CommandObjectTargetModulesDumpSymtab
                 result.GetOutputStream().EOL();
                 result.GetOutputStream().EOL();
               }
-              if (INTERRUPT_REQUESTED(GetDebugger(), 
-                    "Interrupted in dump symtab list with {0} of {1} dumped.", 
+              if (INTERRUPT_REQUESTED(GetDebugger(),
+                    "Interrupted in dump symtab list with {0} of {1} dumped.",
                     num_dumped, num_matches))
                 break;
 
@@ -2099,7 +2182,7 @@ 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(), 
+        if (INTERRUPT_REQUESTED(GetDebugger(),
               "Interrupted in dump all sections with {0} of {1} dumped",
               image_idx, num_modules))
           break;
@@ -2120,7 +2203,7 @@ 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(), 
+            if (INTERRUPT_REQUESTED(GetDebugger(),
                   "Interrupted in dump section list with {0} of {1} dumped.",
                   i, num_matches))
               break;
@@ -2265,7 +2348,7 @@ class CommandObjectTargetModulesDumpClangAST
       }
 
       for (size_t i = 0; i < num_matches; ++i) {
-        if (INTERRUPT_REQUESTED(GetDebugger(), 
+        if (INTERRUPT_REQUESTED(GetDebugger(),
               "Interrupted in dump clang ast list with {0} of {1} dumped.",
               i, num_matches))
           break;
@@ -2406,9 +2489,9 @@ 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, 
+                                    "{0} of {1} dumped", num_dumped,
                                     num_modules))
               break;
 
@@ -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..ccf8747e325c6f4 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,65 @@ void SymbolFileDWARF::DumpClangAST(Stream &s) {
   clang->Dump(s.AsRawOstream());
 }
 
+std::optional<ConstString>
+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());
+    } else {
+      dwo_data->AddStringItem("error",
+                              dwarf_cu->GetDwoError().AsCString("unknown"));
+    }
+    dwo_data->AddBooleanItem("loaded", dwo_symfile != nullptr);
+    array.AddItem(dwo_data);
+  }
+
+  return ConstString("dwo");
+}
+
 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..9461c991975dac1 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 external dwo file details.
+  std::optional<lldb_private::ConstString> 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..4be99b527c72265 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) {
   });
 }
 
+std::optional<ConstString> 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 ConstString("oso");
+}
+
 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..ee4350a8feb7eca 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -11,6 +11,7 @@
 
 #include "DIERef.h"
 #include "lldb/Symbol/SymbolFile.h"
+#include "lldb/Utility/ConstString.h"
 #include "lldb/Utility/RangeMap.h"
 #include "llvm/Support/Chrono.h"
 #include <bitset>
@@ -19,6 +20,7 @@
 #include <vector>
 
 #include "UniqueDWARFASTType.h"
+#include "lldb/Utility/StructuredData.h"
 
 class SymbolFileDWARF;
 class DWARFCompileUnit;
@@ -148,6 +150,10 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFileCommon {
 
   void DumpClangAST(lldb_private::Stream &s) override;
 
+  /// Get the external oso file details.
+  std::optional<lldb_private::ConstString> 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..ba4d5c684dce8d3 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,21 @@ void SymbolFile::AssertModuleLock() {
 
 SymbolFile::RegisterInfoResolver::~RegisterInfoResolver() = default;
 
+bool SymbolFile::ListSeparateDebugInfoFiles(StructuredData::Dictionary &d) {
+  StructuredData::Array array;
+  std::optional<ConstString> debug_info_type = GetSeparateDebugInfoFiles(array);
+  if (!debug_info_type) {
+    return false;
+  }
+
+  d.AddStringItem("type", debug_info_type.value());
+  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..04da2e0386f52dc
--- /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 --json")
+
+        # 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 --json")
+
+        # 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..4c55fd0e02470a2
--- /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 --json")
+
+        # 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 --json")
+
+        # 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